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