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