Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
QtPositioning.java
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4package org.qtproject.qt.android.positioning;
5
6import android.content.Context;
7import android.location.altitude.AltitudeConverter;
8import android.location.Location;
9import android.location.LocationListener;
10import android.location.LocationManager;
11import android.location.GnssStatus;
12import android.location.GnssStatus.Callback;
13import android.os.Bundle;
14import android.os.Handler;
15import android.os.Looper;
16import android.os.Build;
17import java.util.ArrayList;
18import java.util.HashMap;
19import java.util.Iterator;
20import java.util.List;
21
22import android.util.Log;
23
24class QtPositioning implements LocationListener
25{
26
27 private static final String TAG = "qt.positioning.android";
28 static LocationManager locationManager = null;
29 static Object m_syncObject = new Object();
30 static HashMap<Integer, QtPositioning> runningListeners = new HashMap<Integer, QtPositioning>();
31
32 /*
33 The positionInfo instance to which this
34 QtPositioning instance is attached to.
35 */
36 private int nativeClassReference = 0;
37
38 /*
39 The provider type requested by Qt
40 */
41 private int expectedProviders = 0;
42
43 static final int QT_GPS_PROVIDER = 1;
44 static final int QT_NETWORK_PROVIDER = 2;
45
46 /* The following values must match the corresponding error enums in the Qt API*/
47 static final int QT_ACCESS_ERROR = 0;
48 static final int QT_CLOSED_ERROR = 1;
49 static final int QT_POSITION_UNKNOWN_SOURCE_ERROR = 2;
50 static final int QT_POSITION_NO_ERROR = 3;
51 static final int QT_SATELLITE_NO_ERROR = 2;
52 static final int QT_SATELLITE_UNKNOWN_SOURCE_ERROR = -1;
53
54 /* True, if updates were caused by requestUpdate() */
55 private boolean isSingleUpdate = false;
56 /* The length requested for regular intervals in msec. */
57 private int updateIntervalTime = 0;
58
59 /* The last received GPS update */
60 private Location lastGps = null;
61 /* The last received network update */
62 private Location lastNetwork = null;
63 /* If true this class acts as satellite signal monitor rather than location monitor */
64 private boolean isSatelliteUpdate = false;
65 /* Try to convert the altitude to MSL or not */
66 private boolean useAltitudeConverter = false;
67
68 private PositioningLooperGnss looperThread;
69
70 private boolean isLocationProvidersDisabledInvoked = false;
71
72 private static Context appContext = null;
73 private static AltitudeConverter altitudeConverter = null;
74
75 static void setContext(Context context)
76 {
77 try {
78 appContext = context;
79 locationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
80 } catch(Exception e) {
81 e.printStackTrace();
82 }
83 }
84
85 static private int[] providerList()
86 {
87 if (locationManager == null) {
88 Log.w(TAG, "No locationManager available in QtPositioning");
89 return new int[0];
90 }
91 List<String> providers = locationManager.getProviders(true);
92 int retList[] = new int[providers.size()];
93 for (int i = 0; i < providers.size(); i++) {
94 if (providers.get(i).equals(LocationManager.GPS_PROVIDER)) {
95 //must be in sync with AndroidPositioning::PositionProvider::PROVIDER_GPS
96 retList[i] = 0;
97 } else if (providers.get(i).equals(LocationManager.NETWORK_PROVIDER)) {
98 //must be in sync with AndroidPositioning::PositionProvider::PROVIDER_NETWORK
99 retList[i] = 1;
100 } else if (providers.get(i).equals(LocationManager.PASSIVE_PROVIDER)) {
101 //must be in sync with AndroidPositioning::PositionProvider::PROVIDER_PASSIVE
102 retList[i] = 2;
103 } else {
104 retList[i] = -1;
105 }
106 }
107 return retList;
108 }
109
110 static private void addMslAltitude(Location location)
111 {
112 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
113 if (location.hasMslAltitude()) // Nothing to be done
114 return;
115 if (altitudeConverter == null)
116 altitudeConverter = new AltitudeConverter();
117 try {
118 altitudeConverter.addMslAltitudeToLocation(appContext, location);
119 } catch (Exception e) {
120 e.printStackTrace();
121 }
122 }
123 }
124
125 static Location lastKnownPosition(boolean fromSatelliteOnly,
126 boolean useAltitudeConverter)
127 {
128 Location gps = null;
129 Location network = null;
130 try {
131 gps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
132 if (useAltitudeConverter)
133 addMslAltitude(gps);
134 } catch (Exception e) {
135 // This can throw if we only use ACCESS_COARSE_LOCATION. However,
136 // if we didn't set fromSatelliteOnly to true, that is not an error.
137 if (fromSatelliteOnly)
138 e.printStackTrace();
139 gps = null;
140 }
141 if (!fromSatelliteOnly) {
142 try {
143 network = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
144 if (useAltitudeConverter)
145 addMslAltitude(network);
146 } catch(Exception e) {
147 e.printStackTrace();
148 network = null;
149 }
150 }
151
152 if (gps != null && network != null) {
153 //we return the most recent location but slightly prefer GPS
154 //prefer GPS if it is max 4 hrs older than network
155 long delta = network.getTime() - gps.getTime();
156 if (delta < 4*60*60*1000) {
157 return gps;
158 } else {
159 return network;
160 }
161 } else if (gps != null ) {
162 return gps;
163 } else if (network != null) {
164 return network;
165 }
166
167 return null;
168 }
169
170 /* Returns true if at least on of the given providers is enabled. */
171 static private boolean expectedProvidersAvailable(int desiredProviders)
172 {
173 List<String> enabledProviders = locationManager.getProviders(true);
174 if ((desiredProviders & QT_GPS_PROVIDER) > 0) { //gps desired
175 if (enabledProviders.contains(LocationManager.GPS_PROVIDER)) {
176 return true;
177 }
178 }
179 if ((desiredProviders & QT_NETWORK_PROVIDER) > 0) { //network desired
180 if (enabledProviders.contains(LocationManager.NETWORK_PROVIDER)) {
181 return true;
182 }
183 }
184
185 return false;
186 }
187
188
189 static private void addActiveListener(QtPositioning listener, String provider, long minTime, float minDistance)
190 {
191 int androidClassKey = listener.nativeClassReference;
192 //start update thread
193 listener.setActiveLooper(true);
194
195 if (runningListeners.containsKey(androidClassKey) && runningListeners.get(androidClassKey) != listener) {
196 removeActiveListener(androidClassKey);
197 }
198
199 locationManager.requestLocationUpdates(provider,
200 minTime, minDistance,
201 listener,
202 listener.looper());
203
204 runningListeners.put(androidClassKey, listener);
205 }
206
207
208 static private void removeActiveListener(QtPositioning listener)
209 {
210 removeActiveListener(listener.nativeClassReference);
211 }
212
213
214 static private void removeActiveListener(int androidClassKey)
215 {
216 QtPositioning listener = runningListeners.remove(androidClassKey);
217
218 if (listener != null) {
219 locationManager.removeUpdates(listener);
220 listener.setActiveLooper(false);
221 }
222 }
223
224
225 static int startUpdates(int androidClassKey, int locationProvider, int updateInterval,
226 boolean useAltitudeConverter)
227 {
228 synchronized (m_syncObject) {
229 try {
230 boolean providerStarted = false;
231 SecurityException lastException = null;
232 QtPositioning positioningListener = new QtPositioning();
233 positioningListener.nativeClassReference = androidClassKey;
234 positioningListener.expectedProviders = locationProvider;
235 positioningListener.isSatelliteUpdate = false;
236 positioningListener.useAltitudeConverter = useAltitudeConverter;
237
238 if (updateInterval == 0)
239 updateInterval = 50; //don't update more often than once per 50ms
240
241 positioningListener.updateIntervalTime = updateInterval;
242 if ((locationProvider & QT_GPS_PROVIDER) > 0) {
243 Log.d(TAG, "Regular updates using GPS " + updateInterval);
244 try {
245 addActiveListener(positioningListener,
246 LocationManager.GPS_PROVIDER,
247 updateInterval, 0);
248 providerStarted = true;
249 } catch (SecurityException se) {
250 lastException = se;
251 }
252 }
253
254 if ((locationProvider & QT_NETWORK_PROVIDER) > 0) {
255 Log.d(TAG, "Regular updates using network " + updateInterval);
256 try {
257 addActiveListener(positioningListener,
258 LocationManager.NETWORK_PROVIDER,
259 updateInterval, 0);
260 providerStarted = true;
261 } catch (SecurityException se) {
262 lastException = se;
263 }
264 }
265 // The preferred positioning methods and the granted permissions
266 // might not match, so raise an error only if none of the
267 // requested providers could be started.
268 if (!providerStarted && lastException != null) {
269 lastException.printStackTrace();
270 removeActiveListener(positioningListener);
271 return QT_ACCESS_ERROR;
272 }
273
274 if (!expectedProvidersAvailable(locationProvider)) {
275 //all location providers unavailbe -> when they come back we resume automatically
276 return QT_CLOSED_ERROR;
277 }
278
279 } catch(Exception e) {
280 e.printStackTrace();
281 return QT_POSITION_UNKNOWN_SOURCE_ERROR;
282 }
283
284 return QT_POSITION_NO_ERROR;
285 }
286 }
287
288 static void stopUpdates(int androidClassKey)
289 {
290 synchronized (m_syncObject) {
291 try {
292 Log.d(TAG, "Stopping updates");
293 removeActiveListener(androidClassKey);
294 } catch(Exception e) {
295 e.printStackTrace();
296 return;
297 }
298 }
299 }
300
301 static int requestUpdate(int androidClassKey, int locationProvider, int timeout,
302 boolean useAltitudeConverter)
303 {
304 synchronized (m_syncObject) {
305 try {
306 boolean providerStarted = false;
307 SecurityException lastException = null;
308 QtPositioning positioningListener = new QtPositioning();
309 positioningListener.nativeClassReference = androidClassKey;
310 positioningListener.isSingleUpdate = true;
311 positioningListener.expectedProviders = locationProvider;
312 positioningListener.isSatelliteUpdate = false;
313 positioningListener.useAltitudeConverter = useAltitudeConverter;
314
315 if ((locationProvider & QT_GPS_PROVIDER) > 0) {
316 Log.d(TAG, "Single update using GPS");
317 try {
318 addActiveListener(positioningListener, LocationManager.GPS_PROVIDER,
319 timeout, 0);
320 providerStarted = true;
321 } catch (SecurityException se) {
322 lastException = se;
323 }
324 }
325
326 if ((locationProvider & QT_NETWORK_PROVIDER) > 0) {
327 Log.d(TAG, "Single update using network");
328 try {
329 addActiveListener(positioningListener, LocationManager.NETWORK_PROVIDER,
330 timeout, 0);
331 providerStarted = true;
332 } catch (SecurityException se) {
333 lastException = se;
334 }
335 }
336 // The preferred positioning methods and the granted permissions
337 // might not match, so raise an error only if none of the
338 // requested providers could be started.
339 if (!providerStarted && lastException != null) {
340 lastException.printStackTrace();
341 removeActiveListener(positioningListener);
342 return QT_ACCESS_ERROR;
343 }
344
345 if (!expectedProvidersAvailable(locationProvider)) {
346 //all location providers unavailable -> when they come back we resume automatically
347 //in the mean time return ClosedError
348 return QT_CLOSED_ERROR;
349 }
350
351 } catch(Exception e) {
352 e.printStackTrace();
353 return QT_POSITION_UNKNOWN_SOURCE_ERROR;
354 }
355
356 return QT_POSITION_NO_ERROR;
357 }
358 }
359
360 static int startSatelliteUpdates(int androidClassKey, int updateInterval, boolean isSingleRequest)
361 {
362 synchronized (m_syncObject) {
363 try {
364 boolean exceptionOccurred = false;
365 QtPositioning positioningListener = new QtPositioning();
366 positioningListener.isSatelliteUpdate = true;
367 positioningListener.nativeClassReference = androidClassKey;
368 positioningListener.expectedProviders = 1; //always satellite provider
369 positioningListener.isSingleUpdate = isSingleRequest;
370
371 if (updateInterval == 0)
372 updateInterval = 50; //don't update more often than once per 50ms
373
374 if (isSingleRequest)
375 Log.d(TAG, "Single update for Satellites " + updateInterval);
376 else
377 Log.d(TAG, "Regular updates for Satellites " + updateInterval);
378 try {
379 addActiveListener(positioningListener, LocationManager.GPS_PROVIDER,
380 updateInterval, 0);
381 } catch (SecurityException se) {
382 se.printStackTrace();
383 exceptionOccurred = true;
384 }
385
386 if (exceptionOccurred) {
387 removeActiveListener(positioningListener);
388 return QT_ACCESS_ERROR;
389 }
390
391 if (!expectedProvidersAvailable(positioningListener.expectedProviders)) {
392 //all location providers unavailable -> when they come back we resume automatically
393 //in the mean time return ClosedError
394 return QT_CLOSED_ERROR;
395 }
396
397 } catch(Exception e) {
398 e.printStackTrace();
399 return QT_SATELLITE_UNKNOWN_SOURCE_ERROR;
400 }
401
402 return QT_SATELLITE_NO_ERROR;
403 }
404 }
405
406 QtPositioning()
407 {
408 looperThread = new PositioningLooperGnss();
409 }
410
411 Looper looper()
412 {
413 return looperThread.looper();
414 }
415
416 private void setActiveLooper(boolean setActive)
417 {
418 try{
419 if (setActive) {
420 if (looperThread.isAlive())
421 return;
422
423 if (isSatelliteUpdate)
424 looperThread.isSatelliteListener(true);
425
426 long start = System.currentTimeMillis();
427 looperThread.start();
428
429 //busy wait but lasts ~20-30 ms only
430 while (!looperThread.isReady());
431
432 long stop = System.currentTimeMillis();
433 Log.d(TAG, "Looper Thread startup time in ms: " + (stop-start));
434 } else {
435 looperThread.quitLooper();
436 }
437 } catch(Exception e) {
438 e.printStackTrace();
439 }
440 }
441
442 private class PositioningGnssListener extends GnssStatus.Callback
443 {
444 @Override
445 public void onSatelliteStatusChanged(GnssStatus status)
446 {
447 satelliteGnssUpdated(status, nativeClassReference, isSingleUpdate);
448 }
449 }
450
451 private class PositioningLooperGnss extends Thread
452 {
453 private boolean looperRunning;
454 private Looper posLooper;
455 private boolean isSatelliteLooper = false;
456 private PositioningGnssListener gnssListener;
457
458 private PositioningLooperGnss()
459 {
460 looperRunning = false;
461 gnssListener = new PositioningGnssListener();
462 }
463
464 @Override
465 public void run()
466 {
467 Looper.prepare();
468
469 if (isSatelliteLooper)
470 addSatelliteInfoListener();
471
472 posLooper = Looper.myLooper();
473 synchronized (this) {
474 looperRunning = true;
475 }
476 Looper.loop();
477 synchronized (this) {
478 looperRunning = false;
479 }
480 }
481
482 void quitLooper()
483 {
484 if (isSatelliteLooper)
485 removeSatelliteInfoListener();
486 looper().quit();
487 }
488
489 synchronized boolean isReady()
490 {
491 return looperRunning;
492 }
493
494 void isSatelliteListener(boolean isListener)
495 {
496 isSatelliteLooper = isListener;
497 }
498
499 Looper looper()
500 {
501 return posLooper;
502 }
503
504 private void addSatelliteInfoListener()
505 {
506 try {
507 locationManager.registerGnssStatusCallback(gnssListener, null);
508 } catch(Exception e) {
509 e.printStackTrace();
510 }
511 }
512
513 private void removeSatelliteInfoListener()
514 {
515 locationManager.unregisterGnssStatusCallback(gnssListener);
516 }
517 }
518
519 static native void positionUpdated(Location update, int androidClassKey, boolean isSingleUpdate);
520 static native void locationProvidersDisabled(int androidClassKey);
521 static native void locationProvidersChanged(int androidClassKey);
522 static native void satelliteGnssUpdated(GnssStatus update, int androidClassKey, boolean isSingleUpdate);
523
524 @Override
525 public void onLocationChanged(Location location) {
526 //Log.d(TAG, "**** Position Update ****: " + location.toString() + " " + isSingleUpdate);
527 if (location == null)
528 return;
529
530 if (useAltitudeConverter)
531 addMslAltitude(location);
532
533 if (isSatelliteUpdate) //we are a QGeoSatelliteInfoSource -> ignore
534 return;
535
536 if (isSingleUpdate || expectedProviders < 3) {
537 positionUpdated(location, nativeClassReference, isSingleUpdate);
538 return;
539 }
540
541 /*
542 We can use GPS and Network, pick the better location provider.
543 Generally we prefer GPS data due to their higher accurancy but we
544 let Network data pass until GPS fix is available
545 */
546
547 if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
548 lastGps = location;
549
550 // assumption: GPS always better -> pass it on
551 positionUpdated(location, nativeClassReference, isSingleUpdate);
552 } else if (location.getProvider().equals(LocationManager.NETWORK_PROVIDER)) {
553 lastNetwork = location;
554
555 if (lastGps == null) { //no GPS fix yet use network location
556 positionUpdated(location, nativeClassReference, isSingleUpdate);
557 return;
558 }
559
560 long delta = location.getTime() - lastGps.getTime();
561
562 // Ignore if network update is older than last GPS (delta < 0)
563 // Ignore if gps update still has time to provide next location (delta < updateInterval)
564 if (delta < updateIntervalTime)
565 return;
566
567 // Use network data -> GPS has timed out on updateInterval
568 positionUpdated(location, nativeClassReference, isSingleUpdate);
569 }
570 }
571
572 // This method is deprecated starting from API Level 29, but for older
573 // versions it should still be implemented!
574 @SuppressWarnings("deprecation")
575 @Override
576 public void onStatusChanged(String provider, int status, Bundle extras) {}
577
578 @Override
579 public void onProviderEnabled(String provider) {
580 Log.d(TAG, "Enabled provider: " + provider);
581 locationProvidersChanged(nativeClassReference);
582 if (isLocationProvidersDisabledInvoked && expectedProvidersAvailable(expectedProviders))
583 isLocationProvidersDisabledInvoked = false;
584 }
585
586 @Override
587 public void onProviderDisabled(String provider) {
588 Log.d(TAG, "Disabled provider: " + provider);
589 locationProvidersChanged(nativeClassReference);
590 if (!isLocationProvidersDisabledInvoked && !expectedProvidersAvailable(expectedProviders)) {
591 isLocationProvidersDisabledInvoked = true;
592 locationProvidersDisabled(nativeClassReference);
593 }
594 }
595}
The Location class provides a way to mark a location in a file.
Definition location.h:20
QPainter Context
static const QString context()
Definition java.cpp:396
static void positionUpdated(JNIEnv *env, jobject thiz, QtJniTypes::Location location, jint androidClassKey, jboolean isSingleUpdate)
static void satelliteGnssUpdated(JNIEnv *env, jobject thiz, QtJniTypes::GnssStatus gnssStatus, jint androidClassKey, jboolean isSingleUpdate)
static void locationProvidersChanged(JNIEnv *env, jobject thiz, jint androidClassKey)
static void locationProvidersDisabled(JNIEnv *env, jobject thiz, jint androidClassKey)
QMap< Name, StatePointer > Bundle
Definition lalr.h:46
[vector_of_multirole_objects_0]
Definition main.cpp:188
#define TAG(x)
GLuint start
GLbitfield GLuint64 timeout
GLint location
QT_BEGIN_NAMESPACE typedef void(* Callback)(QQmlNotifierEndpoint *, void **)