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