1 package org.robolectric.shadows; 2 3 import static android.location.LocationManager.GPS_PROVIDER; 4 import static android.location.LocationManager.NETWORK_PROVIDER; 5 import static android.location.LocationManager.PASSIVE_PROVIDER; 6 import static android.os.Build.VERSION_CODES.P; 7 import static android.provider.Settings.Secure.LOCATION_MODE; 8 import static android.provider.Settings.Secure.LOCATION_MODE_BATTERY_SAVING; 9 import static android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY; 10 import static android.provider.Settings.Secure.LOCATION_MODE_OFF; 11 import static android.provider.Settings.Secure.LOCATION_MODE_SENSORS_ONLY; 12 import static android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED; 13 import static java.util.concurrent.TimeUnit.NANOSECONDS; 14 15 import android.app.PendingIntent; 16 import android.app.PendingIntent.CanceledException; 17 import android.content.Context; 18 import android.content.Intent; 19 import android.location.Criteria; 20 import android.location.GnssAntennaInfo; 21 import android.location.GnssMeasurementsEvent; 22 import android.location.GnssStatus; 23 import android.location.GpsStatus; 24 import android.location.Location; 25 import android.location.LocationListener; 26 import android.location.LocationManager; 27 import android.location.LocationProvider; 28 import android.location.LocationRequest; 29 import android.location.OnNmeaMessageListener; 30 import android.os.Build.VERSION_CODES; 31 import android.os.Bundle; 32 import android.os.CancellationSignal; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.os.Process; 36 import android.os.SystemClock; 37 import android.os.UserHandle; 38 import android.os.WorkSource; 39 import android.provider.Settings.Secure; 40 import android.text.TextUtils; 41 import android.util.Log; 42 import androidx.annotation.GuardedBy; 43 import androidx.annotation.Nullable; 44 import androidx.annotation.RequiresApi; 45 import com.google.common.collect.ImmutableList; 46 import com.google.common.collect.Iterables; 47 import java.lang.reflect.Constructor; 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.Collections; 51 import java.util.HashSet; 52 import java.util.List; 53 import java.util.Objects; 54 import java.util.Set; 55 import java.util.concurrent.CopyOnWriteArrayList; 56 import java.util.concurrent.Executor; 57 import java.util.concurrent.RejectedExecutionException; 58 import java.util.function.Consumer; 59 import org.robolectric.RuntimeEnvironment; 60 import org.robolectric.annotation.Implementation; 61 import org.robolectric.annotation.Implements; 62 import org.robolectric.annotation.RealObject; 63 import org.robolectric.annotation.Resetter; 64 import org.robolectric.shadows.ShadowSettings.ShadowSecure; 65 import org.robolectric.util.ReflectionHelpers; 66 import org.robolectric.util.ReflectionHelpers.ClassParameter; 67 68 /** 69 * Shadow for {@link LocationManager}. Note that the default state of location on Android devices is 70 * location on, gps provider enabled, network provider disabled. 71 */ 72 @SuppressWarnings("deprecation") 73 @Implements(value = LocationManager.class, looseSignatures = true) 74 public class ShadowLocationManager { 75 76 private static final String TAG = "ShadowLocationManager"; 77 78 private static final long GET_CURRENT_LOCATION_TIMEOUT_MS = 30 * 1000; 79 private static final long MAX_CURRENT_LOCATION_AGE_MS = 10 * 1000; 80 81 /** 82 * ProviderProperties is not public prior to S, so a new class is required to represent it prior 83 * to that platform. 84 */ 85 public static class ProviderProperties { 86 @Nullable private final Object properties; 87 88 private final boolean requiresNetwork; 89 private final boolean requiresSatellite; 90 private final boolean requiresCell; 91 private final boolean hasMonetaryCost; 92 private final boolean supportsAltitude; 93 private final boolean supportsSpeed; 94 private final boolean supportsBearing; 95 private final int powerRequirement; 96 private final int accuracy; 97 98 @RequiresApi(VERSION_CODES.S) ProviderProperties(android.location.provider.ProviderProperties properties)99 ProviderProperties(android.location.provider.ProviderProperties properties) { 100 this.properties = Objects.requireNonNull(properties); 101 this.requiresNetwork = false; 102 this.requiresSatellite = false; 103 this.requiresCell = false; 104 this.hasMonetaryCost = false; 105 this.supportsAltitude = false; 106 this.supportsSpeed = false; 107 this.supportsBearing = false; 108 this.powerRequirement = 0; 109 this.accuracy = 0; 110 } 111 ProviderProperties( boolean requiresNetwork, boolean requiresSatellite, boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy)112 public ProviderProperties( 113 boolean requiresNetwork, 114 boolean requiresSatellite, 115 boolean requiresCell, 116 boolean hasMonetaryCost, 117 boolean supportsAltitude, 118 boolean supportsSpeed, 119 boolean supportsBearing, 120 int powerRequirement, 121 int accuracy) { 122 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.S) { 123 properties = 124 new android.location.provider.ProviderProperties.Builder() 125 .setHasNetworkRequirement(requiresNetwork) 126 .setHasSatelliteRequirement(requiresSatellite) 127 .setHasCellRequirement(requiresCell) 128 .setHasMonetaryCost(hasMonetaryCost) 129 .setHasAltitudeSupport(supportsAltitude) 130 .setHasSpeedSupport(supportsSpeed) 131 .setHasBearingSupport(supportsBearing) 132 .setPowerUsage(powerRequirement) 133 .setAccuracy(accuracy) 134 .build(); 135 } else { 136 properties = null; 137 } 138 139 this.requiresNetwork = requiresNetwork; 140 this.requiresSatellite = requiresSatellite; 141 this.requiresCell = requiresCell; 142 this.hasMonetaryCost = hasMonetaryCost; 143 this.supportsAltitude = supportsAltitude; 144 this.supportsSpeed = supportsSpeed; 145 this.supportsBearing = supportsBearing; 146 this.powerRequirement = powerRequirement; 147 this.accuracy = accuracy; 148 } 149 ProviderProperties(Criteria criteria)150 public ProviderProperties(Criteria criteria) { 151 this( 152 false, 153 false, 154 false, 155 criteria.isCostAllowed(), 156 criteria.isAltitudeRequired(), 157 criteria.isSpeedRequired(), 158 criteria.isBearingRequired(), 159 criteria.getPowerRequirement(), 160 criteria.getAccuracy()); 161 } 162 163 @RequiresApi(VERSION_CODES.S) getProviderProperties()164 android.location.provider.ProviderProperties getProviderProperties() { 165 return (android.location.provider.ProviderProperties) Objects.requireNonNull(properties); 166 } 167 getLegacyProviderProperties()168 Object getLegacyProviderProperties() { 169 try { 170 return ReflectionHelpers.callConstructor( 171 Class.forName("com.android.internal.location.ProviderProperties"), 172 ClassParameter.from(boolean.class, requiresNetwork), 173 ClassParameter.from(boolean.class, requiresSatellite), 174 ClassParameter.from(boolean.class, requiresCell), 175 ClassParameter.from(boolean.class, hasMonetaryCost), 176 ClassParameter.from(boolean.class, supportsAltitude), 177 ClassParameter.from(boolean.class, supportsSpeed), 178 ClassParameter.from(boolean.class, supportsBearing), 179 ClassParameter.from(int.class, powerRequirement), 180 ClassParameter.from(int.class, accuracy)); 181 } catch (ClassNotFoundException c) { 182 throw new RuntimeException("Unable to load old ProviderProperties class", c); 183 } 184 } 185 hasNetworkRequirement()186 public boolean hasNetworkRequirement() { 187 if (properties != null) { 188 return ((android.location.provider.ProviderProperties) properties).hasNetworkRequirement(); 189 } else { 190 return requiresNetwork; 191 } 192 } 193 hasSatelliteRequirement()194 public boolean hasSatelliteRequirement() { 195 if (properties != null) { 196 return ((android.location.provider.ProviderProperties) properties) 197 .hasSatelliteRequirement(); 198 } else { 199 return requiresSatellite; 200 } 201 } 202 isRequiresCell()203 public boolean isRequiresCell() { 204 if (properties != null) { 205 return ((android.location.provider.ProviderProperties) properties).hasCellRequirement(); 206 } else { 207 return requiresCell; 208 } 209 } 210 isHasMonetaryCost()211 public boolean isHasMonetaryCost() { 212 if (properties != null) { 213 return ((android.location.provider.ProviderProperties) properties).hasMonetaryCost(); 214 } else { 215 return hasMonetaryCost; 216 } 217 } 218 hasAltitudeSupport()219 public boolean hasAltitudeSupport() { 220 if (properties != null) { 221 return ((android.location.provider.ProviderProperties) properties).hasAltitudeSupport(); 222 } else { 223 return supportsAltitude; 224 } 225 } 226 hasSpeedSupport()227 public boolean hasSpeedSupport() { 228 if (properties != null) { 229 return ((android.location.provider.ProviderProperties) properties).hasSpeedSupport(); 230 } else { 231 return supportsSpeed; 232 } 233 } 234 hasBearingSupport()235 public boolean hasBearingSupport() { 236 if (properties != null) { 237 return ((android.location.provider.ProviderProperties) properties).hasBearingSupport(); 238 } else { 239 return supportsBearing; 240 } 241 } 242 getPowerUsage()243 public int getPowerUsage() { 244 if (properties != null) { 245 return ((android.location.provider.ProviderProperties) properties).getPowerUsage(); 246 } else { 247 return powerRequirement; 248 } 249 } 250 getAccuracy()251 public int getAccuracy() { 252 if (properties != null) { 253 return ((android.location.provider.ProviderProperties) properties).getAccuracy(); 254 } else { 255 return accuracy; 256 } 257 } 258 meetsCriteria(Criteria criteria)259 boolean meetsCriteria(Criteria criteria) { 260 if (criteria.getAccuracy() != Criteria.NO_REQUIREMENT 261 && criteria.getAccuracy() < getAccuracy()) { 262 return false; 263 } 264 if (criteria.getPowerRequirement() != Criteria.NO_REQUIREMENT 265 && criteria.getPowerRequirement() < getPowerUsage()) { 266 return false; 267 } 268 if (criteria.isAltitudeRequired() && !hasAltitudeSupport()) { 269 return false; 270 } 271 if (criteria.isSpeedRequired() && !hasSpeedSupport()) { 272 return false; 273 } 274 if (criteria.isBearingRequired() && !hasBearingSupport()) { 275 return false; 276 } 277 if (!criteria.isCostAllowed() && hasMonetaryCost) { 278 return false; 279 } 280 return true; 281 } 282 } 283 284 @GuardedBy("ShadowLocationManager.class") 285 @Nullable 286 private static Constructor<LocationProvider> locationProviderConstructor; 287 288 @RealObject private LocationManager realLocationManager; 289 290 @GuardedBy("providers") 291 private final HashSet<ProviderEntry> providers = new HashSet<>(); 292 293 @GuardedBy("gpsStatusListeners") 294 private final HashSet<GpsStatus.Listener> gpsStatusListeners = new HashSet<>(); 295 296 @GuardedBy("gnssStatusTransports") 297 private final CopyOnWriteArrayList<GnssStatusCallbackTransport> gnssStatusTransports = 298 new CopyOnWriteArrayList<>(); 299 300 @GuardedBy("nmeaMessageTransports") 301 private final CopyOnWriteArrayList<OnNmeaMessageListenerTransport> nmeaMessageTransports = 302 new CopyOnWriteArrayList<>(); 303 304 @GuardedBy("gnssMeasurementTransports") 305 private final CopyOnWriteArrayList<GnssMeasurementsEventCallbackTransport> 306 gnssMeasurementTransports = new CopyOnWriteArrayList<>(); 307 308 @GuardedBy("gnssAntennaInfoTransports") 309 private final CopyOnWriteArrayList<GnssAntennaInfoListenerTransport> gnssAntennaInfoTransports = 310 new CopyOnWriteArrayList<>(); 311 312 @Nullable private String gnssHardwareModelName; 313 314 private int gnssYearOfHardware; 315 316 private int gnssBatchSize; 317 ShadowLocationManager()318 public ShadowLocationManager() { 319 // create default providers 320 providers.add( 321 new ProviderEntry( 322 GPS_PROVIDER, 323 new ProviderProperties( 324 true, 325 true, 326 false, 327 false, 328 true, 329 true, 330 true, 331 Criteria.POWER_HIGH, 332 Criteria.ACCURACY_FINE))); 333 providers.add( 334 new ProviderEntry( 335 NETWORK_PROVIDER, 336 new ProviderProperties( 337 false, 338 false, 339 false, 340 false, 341 true, 342 true, 343 true, 344 Criteria.POWER_LOW, 345 Criteria.ACCURACY_COARSE))); 346 providers.add( 347 new ProviderEntry( 348 PASSIVE_PROVIDER, 349 new ProviderProperties( 350 false, 351 false, 352 false, 353 false, 354 false, 355 false, 356 false, 357 Criteria.POWER_LOW, 358 Criteria.ACCURACY_COARSE))); 359 } 360 361 @Implementation getAllProviders()362 protected List<String> getAllProviders() { 363 ArrayList<String> allProviders = new ArrayList<>(); 364 for (ProviderEntry providerEntry : getProviderEntries()) { 365 allProviders.add(providerEntry.getName()); 366 } 367 return allProviders; 368 } 369 370 @Implementation 371 @Nullable getProvider(String name)372 protected LocationProvider getProvider(String name) { 373 if (RuntimeEnvironment.getApiLevel() < VERSION_CODES.KITKAT) { 374 // jelly bean has no way to properly construct a LocationProvider, we give up 375 return null; 376 } 377 378 ProviderEntry providerEntry = getProviderEntry(name); 379 if (providerEntry == null) { 380 return null; 381 } 382 383 ProviderProperties properties = providerEntry.getProperties(); 384 if (properties == null) { 385 return null; 386 } 387 388 try { 389 synchronized (ShadowLocationManager.class) { 390 if (locationProviderConstructor == null) { 391 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.S) { 392 locationProviderConstructor = 393 LocationProvider.class.getDeclaredConstructor( 394 String.class, android.location.provider.ProviderProperties.class); 395 } else { 396 locationProviderConstructor = 397 LocationProvider.class.getDeclaredConstructor( 398 String.class, 399 Class.forName("com.android.internal.location.ProviderProperties")); 400 } 401 } 402 locationProviderConstructor.setAccessible(true); 403 404 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.S) { 405 return locationProviderConstructor.newInstance(name, properties.getProviderProperties()); 406 } else { 407 return locationProviderConstructor.newInstance( 408 name, properties.getLegacyProviderProperties()); 409 } 410 } 411 } catch (ReflectiveOperationException e) { 412 throw new LinkageError(e.getMessage(), e); 413 } 414 } 415 416 @Implementation getProviders(boolean enabledOnly)417 protected List<String> getProviders(boolean enabledOnly) { 418 return getProviders(null, enabledOnly); 419 } 420 421 @Implementation getProviders(@ullable Criteria criteria, boolean enabled)422 protected List<String> getProviders(@Nullable Criteria criteria, boolean enabled) { 423 ArrayList<String> matchingProviders = new ArrayList<>(); 424 for (ProviderEntry providerEntry : getProviderEntries()) { 425 if (enabled && !isProviderEnabled(providerEntry.getName())) { 426 continue; 427 } 428 if (criteria != null && !providerEntry.meetsCriteria(criteria)) { 429 continue; 430 } 431 matchingProviders.add(providerEntry.getName()); 432 } 433 return matchingProviders; 434 } 435 436 @Implementation 437 @Nullable getBestProvider(Criteria criteria, boolean enabled)438 protected String getBestProvider(Criteria criteria, boolean enabled) { 439 List<String> providers = getProviders(criteria, enabled); 440 if (providers.isEmpty()) { 441 providers = getProviders(null, enabled); 442 } 443 444 if (!providers.isEmpty()) { 445 if (providers.contains(GPS_PROVIDER)) { 446 return GPS_PROVIDER; 447 } else if (providers.contains(NETWORK_PROVIDER)) { 448 return NETWORK_PROVIDER; 449 } else { 450 return providers.get(0); 451 } 452 } 453 454 return null; 455 } 456 457 @Implementation(minSdk = VERSION_CODES.S) 458 @Nullable getProviderProperties(Object providerStr)459 protected Object getProviderProperties(Object providerStr) { 460 String provider = (String) providerStr; 461 if (provider == null) { 462 throw new IllegalArgumentException(); 463 } 464 465 ProviderEntry providerEntry = getProviderEntry(provider); 466 if (providerEntry == null) { 467 return null; 468 } 469 470 ProviderProperties properties = providerEntry.getProperties(); 471 if (properties == null) { 472 return null; 473 } 474 475 return properties.getProviderProperties(); 476 } 477 478 @Implementation(minSdk = VERSION_CODES.S) hasProvider(String provider)479 protected boolean hasProvider(String provider) { 480 if (provider == null) { 481 throw new IllegalArgumentException(); 482 } 483 484 return getProviderEntry(provider) != null; 485 } 486 487 @Implementation isProviderEnabled(String provider)488 protected boolean isProviderEnabled(String provider) { 489 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.P) { 490 if (!isLocationEnabled()) { 491 return false; 492 } 493 } 494 495 ProviderEntry entry = getProviderEntry(provider); 496 return entry != null && entry.isEnabled(); 497 } 498 499 /** Completely removes a provider. */ removeProvider(String name)500 public void removeProvider(String name) { 501 removeProviderEntry(name); 502 } 503 504 /** 505 * Sets the properties of the given provider. The provider will be created if it doesn't exist 506 * already. This overload functions for all Android SDK levels. 507 */ setProviderProperties(String name, @Nullable ProviderProperties properties)508 public void setProviderProperties(String name, @Nullable ProviderProperties properties) { 509 getOrCreateProviderEntry(Objects.requireNonNull(name)).setProperties(properties); 510 } 511 512 /** 513 * Sets the given provider enabled or disabled. The provider will be created if it doesn't exist 514 * already. On P and above, location must also be enabled via {@link #setLocationEnabled(boolean)} 515 * in order for a provider to be considered enabled. 516 */ setProviderEnabled(String name, boolean enabled)517 public void setProviderEnabled(String name, boolean enabled) { 518 getOrCreateProviderEntry(name).setEnabled(enabled); 519 } 520 521 // @SystemApi 522 @Implementation(minSdk = VERSION_CODES.P) isLocationEnabledForUser(UserHandle userHandle)523 protected boolean isLocationEnabledForUser(UserHandle userHandle) { 524 return isLocationEnabled(); 525 } 526 527 @Implementation(minSdk = P) isLocationEnabled()528 protected boolean isLocationEnabled() { 529 return getLocationMode() != LOCATION_MODE_OFF; 530 } 531 532 // @SystemApi 533 @Implementation(minSdk = VERSION_CODES.P) setLocationEnabledForUser(boolean enabled, UserHandle userHandle)534 protected void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) { 535 setLocationModeInternal(enabled ? LOCATION_MODE_HIGH_ACCURACY : LOCATION_MODE_OFF); 536 } 537 538 /** 539 * On P and above, turns location on or off. On pre-P devices, sets the location mode to {@link 540 * android.provider.Settings.Secure#LOCATION_MODE_HIGH_ACCURACY} or {@link 541 * android.provider.Settings.Secure#LOCATION_MODE_OFF}. 542 */ setLocationEnabled(boolean enabled)543 public void setLocationEnabled(boolean enabled) { 544 setLocationEnabledForUser(enabled, Process.myUserHandle()); 545 } 546 getLocationMode()547 private int getLocationMode() { 548 return Secure.getInt(getContext().getContentResolver(), LOCATION_MODE, LOCATION_MODE_OFF); 549 } 550 551 /** 552 * On pre-P devices, sets the device location mode. For P and above, use {@link 553 * #setLocationEnabled(boolean)} and {@link #setProviderEnabled(String, boolean)} in combination 554 * to achieve the desired effect. 555 */ setLocationMode(int locationMode)556 public void setLocationMode(int locationMode) { 557 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.P) { 558 throw new AssertionError( 559 "Tests may not set location mode directly on P and above. Instead, use" 560 + " setLocationEnabled() and setProviderEnabled() in combination to achieve the" 561 + " desired result."); 562 } 563 564 setLocationModeInternal(locationMode); 565 } 566 setLocationModeInternal(int locationMode)567 private void setLocationModeInternal(int locationMode) { 568 Secure.putInt(getContext().getContentResolver(), LOCATION_MODE, locationMode); 569 } 570 571 @Implementation 572 @Nullable getLastKnownLocation(String provider)573 protected Location getLastKnownLocation(String provider) { 574 ProviderEntry providerEntry = getProviderEntry(provider); 575 if (providerEntry == null) { 576 return null; 577 } 578 579 return providerEntry.getLastLocation(); 580 } 581 582 /** 583 * @deprecated Use {@link #simulateLocation(Location)} to update the last location for a provider. 584 */ 585 @Deprecated setLastKnownLocation(String provider, @Nullable Location location)586 public void setLastKnownLocation(String provider, @Nullable Location location) { 587 getOrCreateProviderEntry(provider).setLastLocation(location); 588 } 589 590 @RequiresApi(api = VERSION_CODES.R) 591 @Implementation(minSdk = VERSION_CODES.R) getCurrentLocation( String provider, @Nullable CancellationSignal cancellationSignal, Executor executor, Consumer<Location> consumer)592 protected void getCurrentLocation( 593 String provider, 594 @Nullable CancellationSignal cancellationSignal, 595 Executor executor, 596 Consumer<Location> consumer) { 597 getCurrentLocationInternal( 598 provider, LocationRequest.create(), cancellationSignal, executor, consumer); 599 } 600 601 @RequiresApi(api = VERSION_CODES.S) 602 @Implementation(minSdk = VERSION_CODES.S) getCurrentLocation( String provider, LocationRequest request, @Nullable CancellationSignal cancellationSignal, Executor executor, Consumer<Location> consumer)603 protected void getCurrentLocation( 604 String provider, 605 LocationRequest request, 606 @Nullable CancellationSignal cancellationSignal, 607 Executor executor, 608 Consumer<Location> consumer) { 609 getCurrentLocationInternal(provider, request, cancellationSignal, executor, consumer); 610 } 611 612 @RequiresApi(api = VERSION_CODES.R) getCurrentLocationInternal( String provider, LocationRequest request, @Nullable CancellationSignal cancellationSignal, Executor executor, Consumer<Location> consumer)613 private void getCurrentLocationInternal( 614 String provider, 615 LocationRequest request, 616 @Nullable CancellationSignal cancellationSignal, 617 Executor executor, 618 Consumer<Location> consumer) { 619 if (cancellationSignal != null) { 620 cancellationSignal.throwIfCanceled(); 621 } 622 623 final Location location = getLastKnownLocation(provider); 624 if (location != null) { 625 long locationAgeMs = 626 SystemClock.elapsedRealtime() - NANOSECONDS.toMillis(location.getElapsedRealtimeNanos()); 627 if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) { 628 executor.execute(() -> consumer.accept(location)); 629 return; 630 } 631 } 632 633 CurrentLocationTransport listener = new CurrentLocationTransport(executor, consumer); 634 requestLocationUpdatesInternal( 635 provider, new RoboLocationRequest(request), Runnable::run, listener); 636 637 if (cancellationSignal != null) { 638 cancellationSignal.setOnCancelListener(listener::cancel); 639 } 640 641 listener.startTimeout(GET_CURRENT_LOCATION_TIMEOUT_MS); 642 } 643 644 @Implementation requestSingleUpdate( String provider, LocationListener listener, @Nullable Looper looper)645 protected void requestSingleUpdate( 646 String provider, LocationListener listener, @Nullable Looper looper) { 647 if (looper == null) { 648 looper = Looper.myLooper(); 649 if (looper == null) { 650 // forces appropriate exception 651 new Handler(); 652 } 653 } 654 requestLocationUpdatesInternal( 655 provider, 656 new RoboLocationRequest(provider, 0, 0, true), 657 new HandlerExecutor(new Handler(looper)), 658 listener); 659 } 660 661 @Implementation requestSingleUpdate( Criteria criteria, LocationListener listener, @Nullable Looper looper)662 protected void requestSingleUpdate( 663 Criteria criteria, LocationListener listener, @Nullable Looper looper) { 664 String bestProvider = getBestProvider(criteria, true); 665 if (bestProvider == null) { 666 throw new IllegalArgumentException("no providers found for criteria"); 667 } 668 if (looper == null) { 669 looper = Looper.myLooper(); 670 if (looper == null) { 671 // forces appropriate exception 672 new Handler(); 673 } 674 } 675 requestLocationUpdatesInternal( 676 bestProvider, 677 new RoboLocationRequest(bestProvider, 0, 0, true), 678 new HandlerExecutor(new Handler(looper)), 679 listener); 680 } 681 682 @Implementation requestSingleUpdate(String provider, PendingIntent pendingIntent)683 protected void requestSingleUpdate(String provider, PendingIntent pendingIntent) { 684 requestLocationUpdatesInternal( 685 provider, new RoboLocationRequest(provider, 0, 0, true), pendingIntent); 686 } 687 688 @Implementation requestSingleUpdate(Criteria criteria, PendingIntent pendingIntent)689 protected void requestSingleUpdate(Criteria criteria, PendingIntent pendingIntent) { 690 String bestProvider = getBestProvider(criteria, true); 691 if (bestProvider == null) { 692 throw new IllegalArgumentException("no providers found for criteria"); 693 } 694 requestLocationUpdatesInternal( 695 bestProvider, new RoboLocationRequest(bestProvider, 0, 0, true), pendingIntent); 696 } 697 698 @Implementation requestLocationUpdates( String provider, long minTime, float minDistance, LocationListener listener)699 protected void requestLocationUpdates( 700 String provider, long minTime, float minDistance, LocationListener listener) { 701 requestLocationUpdatesInternal( 702 provider, 703 new RoboLocationRequest(provider, minTime, minDistance, false), 704 new HandlerExecutor(new Handler()), 705 listener); 706 } 707 708 @Implementation requestLocationUpdates( String provider, long minTime, float minDistance, LocationListener listener, @Nullable Looper looper)709 protected void requestLocationUpdates( 710 String provider, 711 long minTime, 712 float minDistance, 713 LocationListener listener, 714 @Nullable Looper looper) { 715 if (looper == null) { 716 looper = Looper.myLooper(); 717 if (looper == null) { 718 // forces appropriate exception 719 new Handler(); 720 } 721 } 722 requestLocationUpdatesInternal( 723 provider, 724 new RoboLocationRequest(provider, minTime, minDistance, false), 725 new HandlerExecutor(new Handler(looper)), 726 listener); 727 } 728 729 @Implementation(minSdk = VERSION_CODES.R) requestLocationUpdates( String provider, long minTime, float minDistance, Executor executor, LocationListener listener)730 protected void requestLocationUpdates( 731 String provider, 732 long minTime, 733 float minDistance, 734 Executor executor, 735 LocationListener listener) { 736 requestLocationUpdatesInternal( 737 provider, 738 new RoboLocationRequest(provider, minTime, minDistance, false), 739 executor, 740 listener); 741 } 742 743 @Implementation requestLocationUpdates( long minTime, float minDistance, Criteria criteria, LocationListener listener, @Nullable Looper looper)744 protected void requestLocationUpdates( 745 long minTime, 746 float minDistance, 747 Criteria criteria, 748 LocationListener listener, 749 @Nullable Looper looper) { 750 String bestProvider = getBestProvider(criteria, true); 751 if (bestProvider == null) { 752 throw new IllegalArgumentException("no providers found for criteria"); 753 } 754 if (looper == null) { 755 looper = Looper.myLooper(); 756 if (looper == null) { 757 // forces appropriate exception 758 new Handler(); 759 } 760 } 761 requestLocationUpdatesInternal( 762 bestProvider, 763 new RoboLocationRequest(bestProvider, minTime, minDistance, false), 764 new HandlerExecutor(new Handler(looper)), 765 listener); 766 } 767 768 @Implementation(minSdk = VERSION_CODES.R) requestLocationUpdates( long minTime, float minDistance, Criteria criteria, Executor executor, LocationListener listener)769 protected void requestLocationUpdates( 770 long minTime, 771 float minDistance, 772 Criteria criteria, 773 Executor executor, 774 LocationListener listener) { 775 String bestProvider = getBestProvider(criteria, true); 776 if (bestProvider == null) { 777 throw new IllegalArgumentException("no providers found for criteria"); 778 } 779 requestLocationUpdatesInternal( 780 bestProvider, 781 new RoboLocationRequest(bestProvider, minTime, minDistance, false), 782 executor, 783 listener); 784 } 785 786 @Implementation requestLocationUpdates( String provider, long minTime, float minDistance, PendingIntent pendingIntent)787 protected void requestLocationUpdates( 788 String provider, long minTime, float minDistance, PendingIntent pendingIntent) { 789 requestLocationUpdatesInternal( 790 provider, new RoboLocationRequest(provider, minTime, minDistance, false), pendingIntent); 791 } 792 793 @Implementation requestLocationUpdates( long minTime, float minDistance, Criteria criteria, PendingIntent pendingIntent)794 protected void requestLocationUpdates( 795 long minTime, float minDistance, Criteria criteria, PendingIntent pendingIntent) { 796 String bestProvider = getBestProvider(criteria, true); 797 if (bestProvider == null) { 798 throw new IllegalArgumentException("no providers found for criteria"); 799 } 800 requestLocationUpdatesInternal( 801 bestProvider, 802 new RoboLocationRequest(bestProvider, minTime, minDistance, false), 803 pendingIntent); 804 } 805 806 @Implementation(minSdk = VERSION_CODES.R) requestLocationUpdates( @ullable LocationRequest request, Executor executor, LocationListener listener)807 protected void requestLocationUpdates( 808 @Nullable LocationRequest request, Executor executor, LocationListener listener) { 809 if (request == null) { 810 request = LocationRequest.create(); 811 } 812 requestLocationUpdatesInternal( 813 request.getProvider(), new RoboLocationRequest(request), executor, listener); 814 } 815 816 @Implementation(minSdk = VERSION_CODES.KITKAT) requestLocationUpdates( @ullable LocationRequest request, LocationListener listener, Looper looper)817 protected void requestLocationUpdates( 818 @Nullable LocationRequest request, LocationListener listener, Looper looper) { 819 if (request == null) { 820 request = LocationRequest.create(); 821 } 822 if (looper == null) { 823 looper = Looper.myLooper(); 824 if (looper == null) { 825 // forces appropriate exception 826 new Handler(); 827 } 828 } 829 requestLocationUpdatesInternal( 830 request.getProvider(), 831 new RoboLocationRequest(request), 832 new HandlerExecutor(new Handler(looper)), 833 listener); 834 } 835 836 @Implementation(minSdk = VERSION_CODES.KITKAT) requestLocationUpdates( @ullable LocationRequest request, PendingIntent pendingIntent)837 protected void requestLocationUpdates( 838 @Nullable LocationRequest request, PendingIntent pendingIntent) { 839 if (request == null) { 840 request = LocationRequest.create(); 841 } 842 requestLocationUpdatesInternal( 843 request.getProvider(), new RoboLocationRequest(request), pendingIntent); 844 } 845 846 @Implementation(minSdk = VERSION_CODES.S) requestLocationUpdates( String provider, LocationRequest request, Executor executor, LocationListener listener)847 protected void requestLocationUpdates( 848 String provider, LocationRequest request, Executor executor, LocationListener listener) { 849 requestLocationUpdatesInternal(provider, new RoboLocationRequest(request), executor, listener); 850 } 851 852 @Implementation(minSdk = VERSION_CODES.S) requestLocationUpdates( String provider, LocationRequest request, PendingIntent pendingIntent)853 protected void requestLocationUpdates( 854 String provider, LocationRequest request, PendingIntent pendingIntent) { 855 requestLocationUpdatesInternal(provider, new RoboLocationRequest(request), pendingIntent); 856 } 857 requestLocationUpdatesInternal( String provider, RoboLocationRequest request, Executor executor, LocationListener listener)858 private void requestLocationUpdatesInternal( 859 String provider, RoboLocationRequest request, Executor executor, LocationListener listener) { 860 if (provider == null || request == null || executor == null || listener == null) { 861 throw new IllegalArgumentException(); 862 } 863 getOrCreateProviderEntry(provider).addListener(listener, request, executor); 864 } 865 requestLocationUpdatesInternal( String provider, RoboLocationRequest request, PendingIntent pendingIntent)866 private void requestLocationUpdatesInternal( 867 String provider, RoboLocationRequest request, PendingIntent pendingIntent) { 868 if (provider == null || request == null || pendingIntent == null) { 869 throw new IllegalArgumentException(); 870 } 871 getOrCreateProviderEntry(provider).addListener(pendingIntent, request); 872 } 873 874 @Implementation removeUpdates(LocationListener listener)875 protected void removeUpdates(LocationListener listener) { 876 removeUpdatesInternal(listener); 877 } 878 879 @Implementation removeUpdates(PendingIntent pendingIntent)880 protected void removeUpdates(PendingIntent pendingIntent) { 881 removeUpdatesInternal(pendingIntent); 882 } 883 removeUpdatesInternal(Object key)884 private void removeUpdatesInternal(Object key) { 885 for (ProviderEntry providerEntry : getProviderEntries()) { 886 providerEntry.removeListener(key); 887 } 888 } 889 890 @Implementation(minSdk = VERSION_CODES.S) requestFlush(String provider, LocationListener listener, int requestCode)891 protected void requestFlush(String provider, LocationListener listener, int requestCode) { 892 ProviderEntry entry = getProviderEntry(provider); 893 if (entry == null) { 894 throw new IllegalArgumentException("unknown provider \"" + provider + "\""); 895 } 896 897 entry.requestFlush(listener, requestCode); 898 } 899 900 @Implementation(minSdk = VERSION_CODES.S) requestFlush(String provider, PendingIntent pendingIntent, int requestCode)901 protected void requestFlush(String provider, PendingIntent pendingIntent, int requestCode) { 902 ProviderEntry entry = getProviderEntry(provider); 903 if (entry == null) { 904 throw new IllegalArgumentException("unknown provider \"" + provider + "\""); 905 } 906 907 entry.requestFlush(pendingIntent, requestCode); 908 } 909 910 /** 911 * Returns the list of {@link LocationRequest} currently registered under the given provider. 912 * Clients compiled against the public Android SDK should only use this method on S+, clients 913 * compiled against the system Android SDK may only use this method on Kitkat+. 914 * 915 * <p>Prior to Android S {@link LocationRequest} equality is not well defined, so prefer using 916 * {@link #getLegacyLocationRequests(String)} instead if equality is required for testing. 917 */ 918 @RequiresApi(VERSION_CODES.KITKAT) getLocationRequests(String provider)919 public List<LocationRequest> getLocationRequests(String provider) { 920 ProviderEntry providerEntry = getProviderEntry(provider); 921 if (providerEntry == null) { 922 return ImmutableList.of(); 923 } 924 925 return ImmutableList.copyOf( 926 Iterables.transform( 927 providerEntry.getTransports(), 928 transport -> transport.getRequest().getLocationRequest())); 929 } 930 931 /** 932 * Returns the list of {@link RoboLocationRequest} currently registered under the given provider. 933 * Since {@link LocationRequest} was not publicly visible prior to S, and did not exist prior to 934 * Kitkat, {@link RoboLocationRequest} allows querying the location requests prior to those 935 * platforms, and also implements proper equality comparisons for testing. 936 */ getLegacyLocationRequests(String provider)937 public List<RoboLocationRequest> getLegacyLocationRequests(String provider) { 938 ProviderEntry providerEntry = getProviderEntry(provider); 939 if (providerEntry == null) { 940 return ImmutableList.of(); 941 } 942 943 return ImmutableList.copyOf( 944 Iterables.transform(providerEntry.getTransports(), LocationTransport::getRequest)); 945 } 946 947 @Implementation(minSdk = VERSION_CODES.P) injectLocation(Location location)948 protected boolean injectLocation(Location location) { 949 return false; 950 } 951 952 @Implementation(minSdk = VERSION_CODES.O) getGnssBatchSize()953 protected int getGnssBatchSize() { 954 return gnssBatchSize; 955 } 956 957 /** 958 * Sets the GNSS hardware batch size. Values greater than 0 enables hardware GNSS batching APIs. 959 */ setGnssBatchSize(int gnssBatchSize)960 public void setGnssBatchSize(int gnssBatchSize) { 961 this.gnssBatchSize = gnssBatchSize; 962 } 963 964 @Implementation(minSdk = VERSION_CODES.O) registerGnssBatchedLocationCallback( Object periodNanos, Object wakeOnFifoFull, Object callback, Object handler)965 protected boolean registerGnssBatchedLocationCallback( 966 Object periodNanos, Object wakeOnFifoFull, Object callback, Object handler) { 967 getOrCreateProviderEntry(GPS_PROVIDER) 968 .setLegacyBatchedListener( 969 callback, 970 new HandlerExecutor((Handler) handler), 971 gnssBatchSize, 972 (Boolean) wakeOnFifoFull); 973 return true; 974 } 975 976 @Implementation(minSdk = VERSION_CODES.O) flushGnssBatch()977 protected void flushGnssBatch() { 978 ProviderEntry e = getProviderEntry(GPS_PROVIDER); 979 if (e != null) { 980 e.flushLegacyBatch(); 981 } 982 } 983 984 @Implementation(minSdk = VERSION_CODES.O) unregisterGnssBatchedLocationCallback(Object callback)985 protected boolean unregisterGnssBatchedLocationCallback(Object callback) { 986 ProviderEntry e = getProviderEntry(GPS_PROVIDER); 987 if (e != null) { 988 e.clearLegacyBatchedListener(); 989 } 990 return true; 991 } 992 993 @Implementation(minSdk = VERSION_CODES.P) 994 @Nullable getGnssHardwareModelName()995 protected String getGnssHardwareModelName() { 996 return gnssHardwareModelName; 997 } 998 999 /** 1000 * Sets the GNSS hardware model name returned by {@link 1001 * LocationManager#getGnssHardwareModelName()}. 1002 */ setGnssHardwareModelName(@ullable String gnssHardwareModelName)1003 public void setGnssHardwareModelName(@Nullable String gnssHardwareModelName) { 1004 this.gnssHardwareModelName = gnssHardwareModelName; 1005 } 1006 1007 @Implementation(minSdk = VERSION_CODES.P) getGnssYearOfHardware()1008 protected int getGnssYearOfHardware() { 1009 return gnssYearOfHardware; 1010 } 1011 1012 /** Sets the GNSS year of hardware returned by {@link LocationManager#getGnssYearOfHardware()}. */ setGnssYearOfHardware(int gnssYearOfHardware)1013 public void setGnssYearOfHardware(int gnssYearOfHardware) { 1014 this.gnssYearOfHardware = gnssYearOfHardware; 1015 } 1016 1017 @Implementation addGpsStatusListener(GpsStatus.Listener listener)1018 protected boolean addGpsStatusListener(GpsStatus.Listener listener) { 1019 if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.R) { 1020 throw new UnsupportedOperationException( 1021 "GpsStatus APIs not supported, please use GnssStatus APIs instead"); 1022 } 1023 1024 synchronized (gpsStatusListeners) { 1025 gpsStatusListeners.add(listener); 1026 } 1027 1028 return true; 1029 } 1030 1031 @Implementation removeGpsStatusListener(GpsStatus.Listener listener)1032 protected void removeGpsStatusListener(GpsStatus.Listener listener) { 1033 if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.R) { 1034 throw new UnsupportedOperationException( 1035 "GpsStatus APIs not supported, please use GnssStatus APIs instead"); 1036 } 1037 1038 synchronized (gpsStatusListeners) { 1039 gpsStatusListeners.remove(listener); 1040 } 1041 } 1042 1043 /** Returns the list of currently registered {@link GpsStatus.Listener}s. */ getGpsStatusListeners()1044 public List<GpsStatus.Listener> getGpsStatusListeners() { 1045 synchronized (gpsStatusListeners) { 1046 return new ArrayList<>(gpsStatusListeners); 1047 } 1048 } 1049 1050 @Implementation(minSdk = VERSION_CODES.N) registerGnssStatusCallback(GnssStatus.Callback callback, Handler handler)1051 protected boolean registerGnssStatusCallback(GnssStatus.Callback callback, Handler handler) { 1052 if (handler == null) { 1053 handler = new Handler(); 1054 } 1055 1056 return registerGnssStatusCallback(new HandlerExecutor(handler), callback); 1057 } 1058 1059 @Implementation(minSdk = VERSION_CODES.R) registerGnssStatusCallback(Executor executor, GnssStatus.Callback listener)1060 protected boolean registerGnssStatusCallback(Executor executor, GnssStatus.Callback listener) { 1061 synchronized (gnssStatusTransports) { 1062 Iterables.removeIf(gnssStatusTransports, transport -> transport.getListener() == listener); 1063 gnssStatusTransports.add(new GnssStatusCallbackTransport(executor, listener)); 1064 } 1065 1066 return true; 1067 } 1068 1069 @Implementation(minSdk = VERSION_CODES.N) unregisterGnssStatusCallback(GnssStatus.Callback listener)1070 protected void unregisterGnssStatusCallback(GnssStatus.Callback listener) { 1071 synchronized (gnssStatusTransports) { 1072 Iterables.removeIf(gnssStatusTransports, transport -> transport.getListener() == listener); 1073 } 1074 } 1075 1076 /** Simulates a GNSS status started event. */ 1077 @RequiresApi(VERSION_CODES.N) simulateGnssStatusStarted()1078 public void simulateGnssStatusStarted() { 1079 List<GnssStatusCallbackTransport> transports; 1080 synchronized (gnssStatusTransports) { 1081 transports = gnssStatusTransports; 1082 } 1083 1084 for (GnssStatusCallbackTransport transport : transports) { 1085 transport.onStarted(); 1086 } 1087 } 1088 1089 /** Simulates a GNSS status first fix event. */ 1090 @RequiresApi(VERSION_CODES.N) simulateGnssStatusFirstFix(int ttff)1091 public void simulateGnssStatusFirstFix(int ttff) { 1092 List<GnssStatusCallbackTransport> transports; 1093 synchronized (gnssStatusTransports) { 1094 transports = gnssStatusTransports; 1095 } 1096 1097 for (GnssStatusCallbackTransport transport : transports) { 1098 transport.onFirstFix(ttff); 1099 } 1100 } 1101 1102 /** Simulates a GNSS status event. */ 1103 @RequiresApi(VERSION_CODES.N) simulateGnssStatus(GnssStatus status)1104 public void simulateGnssStatus(GnssStatus status) { 1105 List<GnssStatusCallbackTransport> transports; 1106 synchronized (gnssStatusTransports) { 1107 transports = gnssStatusTransports; 1108 } 1109 1110 for (GnssStatusCallbackTransport transport : transports) { 1111 transport.onSatelliteStatusChanged(status); 1112 } 1113 } 1114 1115 /** 1116 * @deprecated Use {@link #simulateGnssStatus(GnssStatus)} instead. 1117 */ 1118 @Deprecated 1119 @RequiresApi(VERSION_CODES.N) sendGnssStatus(GnssStatus status)1120 public void sendGnssStatus(GnssStatus status) { 1121 simulateGnssStatus(status); 1122 } 1123 1124 /** Simulates a GNSS status stopped event. */ 1125 @RequiresApi(VERSION_CODES.N) simulateGnssStatusStopped()1126 public void simulateGnssStatusStopped() { 1127 List<GnssStatusCallbackTransport> transports; 1128 synchronized (gnssStatusTransports) { 1129 transports = gnssStatusTransports; 1130 } 1131 1132 for (GnssStatusCallbackTransport transport : transports) { 1133 transport.onStopped(); 1134 } 1135 } 1136 1137 @Implementation(minSdk = VERSION_CODES.N) addNmeaListener(OnNmeaMessageListener listener, Handler handler)1138 protected boolean addNmeaListener(OnNmeaMessageListener listener, Handler handler) { 1139 if (handler == null) { 1140 handler = new Handler(); 1141 } 1142 1143 return addNmeaListener(new HandlerExecutor(handler), listener); 1144 } 1145 1146 @Implementation(minSdk = VERSION_CODES.R) addNmeaListener(Executor executor, OnNmeaMessageListener listener)1147 protected boolean addNmeaListener(Executor executor, OnNmeaMessageListener listener) { 1148 synchronized (nmeaMessageTransports) { 1149 Iterables.removeIf(nmeaMessageTransports, transport -> transport.getListener() == listener); 1150 nmeaMessageTransports.add(new OnNmeaMessageListenerTransport(executor, listener)); 1151 } 1152 1153 return true; 1154 } 1155 1156 @Implementation(minSdk = VERSION_CODES.N) removeNmeaListener(OnNmeaMessageListener listener)1157 protected void removeNmeaListener(OnNmeaMessageListener listener) { 1158 synchronized (nmeaMessageTransports) { 1159 Iterables.removeIf(nmeaMessageTransports, transport -> transport.getListener() == listener); 1160 } 1161 } 1162 1163 /** Simulates a NMEA message. */ 1164 @RequiresApi(api = VERSION_CODES.N) simulateNmeaMessage(String message, long timestamp)1165 public void simulateNmeaMessage(String message, long timestamp) { 1166 List<OnNmeaMessageListenerTransport> transports; 1167 synchronized (nmeaMessageTransports) { 1168 transports = nmeaMessageTransports; 1169 } 1170 1171 for (OnNmeaMessageListenerTransport transport : transports) { 1172 transport.onNmeaMessage(message, timestamp); 1173 } 1174 } 1175 1176 /** 1177 * @deprecated Use {@link #simulateNmeaMessage(String, long)} instead. 1178 */ 1179 @Deprecated 1180 @RequiresApi(api = VERSION_CODES.N) sendNmeaMessage(String message, long timestamp)1181 public void sendNmeaMessage(String message, long timestamp) { 1182 simulateNmeaMessage(message, timestamp); 1183 } 1184 1185 @Implementation(minSdk = VERSION_CODES.N) registerGnssMeasurementsCallback( GnssMeasurementsEvent.Callback listener, Handler handler)1186 protected boolean registerGnssMeasurementsCallback( 1187 GnssMeasurementsEvent.Callback listener, Handler handler) { 1188 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.R) { 1189 if (handler == null) { 1190 handler = new Handler(); 1191 } 1192 1193 return registerGnssMeasurementsCallback(new HandlerExecutor(handler), listener); 1194 } else { 1195 return registerGnssMeasurementsCallback(Runnable::run, listener); 1196 } 1197 } 1198 1199 @Implementation(minSdk = VERSION_CODES.R) 1200 @RequiresApi(api = VERSION_CODES.R) registerGnssMeasurementsCallback( Object request, Object executor, Object callback)1201 protected boolean registerGnssMeasurementsCallback( 1202 Object request, Object executor, Object callback) { 1203 return registerGnssMeasurementsCallback( 1204 (Executor) executor, (GnssMeasurementsEvent.Callback) callback); 1205 } 1206 1207 @Implementation(minSdk = VERSION_CODES.R) registerGnssMeasurementsCallback( Executor executor, GnssMeasurementsEvent.Callback listener)1208 protected boolean registerGnssMeasurementsCallback( 1209 Executor executor, GnssMeasurementsEvent.Callback listener) { 1210 synchronized (gnssMeasurementTransports) { 1211 Iterables.removeIf( 1212 gnssMeasurementTransports, transport -> transport.getListener() == listener); 1213 gnssMeasurementTransports.add(new GnssMeasurementsEventCallbackTransport(executor, listener)); 1214 } 1215 1216 return true; 1217 } 1218 1219 @Implementation(minSdk = VERSION_CODES.N) unregisterGnssMeasurementsCallback(GnssMeasurementsEvent.Callback listener)1220 protected void unregisterGnssMeasurementsCallback(GnssMeasurementsEvent.Callback listener) { 1221 synchronized (gnssMeasurementTransports) { 1222 Iterables.removeIf( 1223 gnssMeasurementTransports, transport -> transport.getListener() == listener); 1224 } 1225 } 1226 1227 /** Simulates a GNSS measurements event. */ 1228 @RequiresApi(api = VERSION_CODES.N) simulateGnssMeasurementsEvent(GnssMeasurementsEvent event)1229 public void simulateGnssMeasurementsEvent(GnssMeasurementsEvent event) { 1230 List<GnssMeasurementsEventCallbackTransport> transports; 1231 synchronized (gnssMeasurementTransports) { 1232 transports = gnssMeasurementTransports; 1233 } 1234 1235 for (GnssMeasurementsEventCallbackTransport transport : transports) { 1236 transport.onGnssMeasurementsReceived(event); 1237 } 1238 } 1239 1240 /** 1241 * @deprecated Use {@link #simulateGnssMeasurementsEvent(GnssMeasurementsEvent)} instead. 1242 */ 1243 @Deprecated 1244 @RequiresApi(api = VERSION_CODES.N) sendGnssMeasurementsEvent(GnssMeasurementsEvent event)1245 public void sendGnssMeasurementsEvent(GnssMeasurementsEvent event) { 1246 simulateGnssMeasurementsEvent(event); 1247 } 1248 1249 /** Simulates a GNSS measurements status change. */ 1250 @RequiresApi(api = VERSION_CODES.N) simulateGnssMeasurementsStatus(int status)1251 public void simulateGnssMeasurementsStatus(int status) { 1252 List<GnssMeasurementsEventCallbackTransport> transports; 1253 synchronized (gnssMeasurementTransports) { 1254 transports = gnssMeasurementTransports; 1255 } 1256 1257 for (GnssMeasurementsEventCallbackTransport transport : transports) { 1258 transport.onStatusChanged(status); 1259 } 1260 } 1261 1262 @Implementation(minSdk = VERSION_CODES.R) registerAntennaInfoListener(Object executor, Object listener)1263 protected Object registerAntennaInfoListener(Object executor, Object listener) { 1264 synchronized (gnssAntennaInfoTransports) { 1265 Iterables.removeIf( 1266 gnssAntennaInfoTransports, transport -> transport.getListener() == listener); 1267 gnssAntennaInfoTransports.add( 1268 new GnssAntennaInfoListenerTransport( 1269 (Executor) executor, (GnssAntennaInfo.Listener) listener)); 1270 } 1271 return true; 1272 } 1273 1274 @Implementation(minSdk = VERSION_CODES.R) unregisterAntennaInfoListener(Object listener)1275 protected void unregisterAntennaInfoListener(Object listener) { 1276 synchronized (gnssAntennaInfoTransports) { 1277 Iterables.removeIf( 1278 gnssAntennaInfoTransports, transport -> transport.getListener() == listener); 1279 } 1280 } 1281 1282 /** Simulates a GNSS antenna info event. */ 1283 @RequiresApi(api = VERSION_CODES.R) simulateGnssAntennaInfo(List<GnssAntennaInfo> antennaInfos)1284 public void simulateGnssAntennaInfo(List<GnssAntennaInfo> antennaInfos) { 1285 List<GnssAntennaInfoListenerTransport> transports; 1286 synchronized (gnssAntennaInfoTransports) { 1287 transports = gnssAntennaInfoTransports; 1288 } 1289 1290 for (GnssAntennaInfoListenerTransport transport : transports) { 1291 transport.onGnssAntennaInfoReceived(new ArrayList<>(antennaInfos)); 1292 } 1293 } 1294 1295 /** 1296 * @deprecated Use {@link #simulateGnssAntennaInfo(List)} instead. 1297 */ 1298 @Deprecated 1299 @RequiresApi(api = VERSION_CODES.R) sendGnssAntennaInfo(List<GnssAntennaInfo> antennaInfos)1300 public void sendGnssAntennaInfo(List<GnssAntennaInfo> antennaInfos) { 1301 simulateGnssAntennaInfo(antennaInfos); 1302 } 1303 1304 /** 1305 * A convenience function equivalent to invoking {@link #simulateLocation(String, Location)} with 1306 * the provider of the given location. 1307 */ simulateLocation(Location location)1308 public void simulateLocation(Location location) { 1309 simulateLocation(location.getProvider(), location); 1310 } 1311 1312 /** 1313 * Delivers to the given provider (which will be created if necessary) a new location which will 1314 * be delivered to appropriate listeners and updates state accordingly. Delivery will ignore the 1315 * enabled/disabled state of providers, unlike location on a real device. 1316 * 1317 * <p>The location will also be delivered to the passive provider. 1318 */ simulateLocation(String provider, Location... locations)1319 public void simulateLocation(String provider, Location... locations) { 1320 ProviderEntry providerEntry = getOrCreateProviderEntry(provider); 1321 if (!PASSIVE_PROVIDER.equals(providerEntry.getName())) { 1322 providerEntry.simulateLocation(locations); 1323 } 1324 1325 ProviderEntry passiveProviderEntry = getProviderEntry(PASSIVE_PROVIDER); 1326 if (passiveProviderEntry != null) { 1327 passiveProviderEntry.simulateLocation(locations); 1328 } 1329 } 1330 1331 /** 1332 * @deprecated Do not test listeners, instead use {@link #simulateLocation(Location)} and test the 1333 * results of those listeners being invoked. 1334 */ 1335 @Deprecated getRequestLocationUpdateListeners()1336 public List<LocationListener> getRequestLocationUpdateListeners() { 1337 return getLocationUpdateListeners(); 1338 } 1339 1340 /** 1341 * @deprecated Do not test listeners, instead use {@link #simulateLocation(Location)} and test the 1342 * results of those listeners being invoked. 1343 */ 1344 @Deprecated getLocationUpdateListeners()1345 public List<LocationListener> getLocationUpdateListeners() { 1346 HashSet<LocationListener> listeners = new HashSet<>(); 1347 for (ProviderEntry providerEntry : getProviderEntries()) { 1348 Iterables.addAll( 1349 listeners, 1350 Iterables.transform( 1351 Iterables.filter(providerEntry.getTransports(), LocationListenerTransport.class), 1352 LocationTransport::getKey)); 1353 } 1354 return new ArrayList<>(listeners); 1355 } 1356 1357 /** 1358 * @deprecated Do not test listeners, instead use {@link #simulateLocation(Location)} and test the 1359 * results of those listeners being invoked. 1360 */ 1361 @Deprecated getLocationUpdateListeners(String provider)1362 public List<LocationListener> getLocationUpdateListeners(String provider) { 1363 ProviderEntry providerEntry = getProviderEntry(provider); 1364 if (providerEntry == null) { 1365 return Collections.emptyList(); 1366 } 1367 1368 HashSet<LocationListener> listeners = new HashSet<>(); 1369 Iterables.addAll( 1370 listeners, 1371 Iterables.transform( 1372 Iterables.filter(providerEntry.getTransports(), LocationListenerTransport.class), 1373 LocationTransport::getKey)); 1374 return new ArrayList<>(listeners); 1375 } 1376 1377 /** 1378 * @deprecated Do not test pending intents, instead use {@link #simulateLocation(Location)} and 1379 * test the results of those pending intent being invoked. 1380 */ 1381 @Deprecated getLocationUpdatePendingIntents()1382 public List<PendingIntent> getLocationUpdatePendingIntents() { 1383 HashSet<PendingIntent> listeners = new HashSet<>(); 1384 for (ProviderEntry providerEntry : getProviderEntries()) { 1385 Iterables.addAll( 1386 listeners, 1387 Iterables.transform( 1388 Iterables.filter(providerEntry.getTransports(), LocationPendingIntentTransport.class), 1389 LocationTransport::getKey)); 1390 } 1391 return new ArrayList<>(listeners); 1392 } 1393 1394 /** 1395 * Retrieves a list of all currently registered pending intents for the given provider. 1396 * 1397 * @deprecated Do not test pending intents, instead use {@link #simulateLocation(Location)} and 1398 * test the results of those pending intent being invoked. 1399 */ 1400 @Deprecated getLocationUpdatePendingIntents(String provider)1401 public List<PendingIntent> getLocationUpdatePendingIntents(String provider) { 1402 ProviderEntry providerEntry = getProviderEntry(provider); 1403 if (providerEntry == null) { 1404 return Collections.emptyList(); 1405 } 1406 1407 HashSet<PendingIntent> listeners = new HashSet<>(); 1408 Iterables.addAll( 1409 listeners, 1410 Iterables.transform( 1411 Iterables.filter(providerEntry.getTransports(), LocationPendingIntentTransport.class), 1412 LocationTransport::getKey)); 1413 return new ArrayList<>(listeners); 1414 } 1415 getContext()1416 private Context getContext() { 1417 return ReflectionHelpers.getField(realLocationManager, "mContext"); 1418 } 1419 getOrCreateProviderEntry(String name)1420 private ProviderEntry getOrCreateProviderEntry(String name) { 1421 if (name == null) { 1422 throw new IllegalArgumentException("cannot use a null provider"); 1423 } 1424 1425 synchronized (providers) { 1426 ProviderEntry providerEntry = getProviderEntry(name); 1427 if (providerEntry == null) { 1428 providerEntry = new ProviderEntry(name, null); 1429 providers.add(providerEntry); 1430 } 1431 return providerEntry; 1432 } 1433 } 1434 1435 @Nullable getProviderEntry(String name)1436 private ProviderEntry getProviderEntry(String name) { 1437 if (name == null) { 1438 return null; 1439 } 1440 1441 synchronized (providers) { 1442 for (ProviderEntry providerEntry : providers) { 1443 if (name.equals(providerEntry.getName())) { 1444 return providerEntry; 1445 } 1446 } 1447 } 1448 1449 return null; 1450 } 1451 getProviderEntries()1452 private Set<ProviderEntry> getProviderEntries() { 1453 synchronized (providers) { 1454 return providers; 1455 } 1456 } 1457 removeProviderEntry(String name)1458 private void removeProviderEntry(String name) { 1459 synchronized (providers) { 1460 providers.remove(getProviderEntry(name)); 1461 } 1462 } 1463 1464 // provider enabled logic is complicated due to many changes over different versions of android. a 1465 // brief explanation of how the logic works in this shadow (which is subtly different and more 1466 // complicated from how the logic works in real android): 1467 // 1468 // 1) prior to P, the source of truth for whether a provider is enabled must be the 1469 // LOCATION_PROVIDERS_ALLOWED setting, so that direct writes into that setting are respected. 1470 // changes to the network and gps providers must change LOCATION_MODE appropriately as well. 1471 // 2) for P, providers are considered enabled if the LOCATION_MODE setting is not off AND they are 1472 // enabled via LOCATION_PROVIDERS_ALLOWED. direct writes into LOCATION_PROVIDERS_ALLOWED should 1473 // be respected (if the LOCATION_MODE is not off). changes to LOCATION_MODE will change the 1474 // state of the network and gps providers. 1475 // 3) for Q/R, providers are considered enabled if the LOCATION_MODE settings is not off AND they 1476 // are enabled, but the store for the enabled state may not be LOCATION_PROVIDERS_ALLOWED, as 1477 // writes into LOCATION_PROVIDERS_ALLOWED should not be respected. LOCATION_PROVIDERS_ALLOWED 1478 // should still be updated so that provider state changes can be listened to via that setting. 1479 // changes to LOCATION_MODE should not change the state of the network and gps provider. 1480 // 5) the passive provider is always special-cased at all API levels - it's state is controlled 1481 // programmatically, and should never be determined by LOCATION_PROVIDERS_ALLOWED. 1482 private final class ProviderEntry { 1483 1484 private final String name; 1485 1486 @GuardedBy("this") 1487 private final CopyOnWriteArrayList<LocationTransport<?>> locationTransports = 1488 new CopyOnWriteArrayList<>(); 1489 1490 @GuardedBy("this") 1491 @Nullable 1492 private LegacyBatchedTransport legacyBatchedTransport; 1493 1494 @GuardedBy("this") 1495 @Nullable 1496 private ProviderProperties properties; 1497 1498 @GuardedBy("this") 1499 private boolean enabled; 1500 1501 @GuardedBy("this") 1502 @Nullable 1503 private Location lastLocation; 1504 ProviderEntry(String name, @Nullable ProviderProperties properties)1505 ProviderEntry(String name, @Nullable ProviderProperties properties) { 1506 this.name = name; 1507 1508 this.properties = properties; 1509 1510 switch (name) { 1511 case PASSIVE_PROVIDER: 1512 // passive provider always starts enabled 1513 enabled = true; 1514 break; 1515 case GPS_PROVIDER: 1516 enabled = ShadowSecure.INITIAL_GPS_PROVIDER_STATE; 1517 break; 1518 case NETWORK_PROVIDER: 1519 enabled = ShadowSecure.INITIAL_NETWORK_PROVIDER_STATE; 1520 break; 1521 default: 1522 enabled = false; 1523 break; 1524 } 1525 } 1526 getName()1527 public String getName() { 1528 return name; 1529 } 1530 getTransports()1531 public synchronized List<LocationTransport<?>> getTransports() { 1532 return locationTransports; 1533 } 1534 1535 @Nullable getProperties()1536 public synchronized ProviderProperties getProperties() { 1537 return properties; 1538 } 1539 setProperties(@ullable ProviderProperties properties)1540 public synchronized void setProperties(@Nullable ProviderProperties properties) { 1541 this.properties = properties; 1542 } 1543 isEnabled()1544 public boolean isEnabled() { 1545 if (PASSIVE_PROVIDER.equals(name) || RuntimeEnvironment.getApiLevel() >= VERSION_CODES.Q) { 1546 synchronized (this) { 1547 return enabled; 1548 } 1549 } else { 1550 String allowedProviders = 1551 Secure.getString(getContext().getContentResolver(), LOCATION_PROVIDERS_ALLOWED); 1552 if (TextUtils.isEmpty(allowedProviders)) { 1553 return false; 1554 } else { 1555 return Arrays.asList(allowedProviders.split(",")).contains(name); 1556 } 1557 } 1558 } 1559 setEnabled(boolean enabled)1560 public void setEnabled(boolean enabled) { 1561 List<LocationTransport<?>> transports; 1562 synchronized (this) { 1563 if (PASSIVE_PROVIDER.equals(name)) { 1564 // the passive provider cannot be disabled, but the passive provider didn't exist in 1565 // previous versions of this shadow. for backwards compatibility, we let the passive 1566 // provider be disabled. this also help emulate the situation where an app only has COARSE 1567 // permissions, which this shadow normally can't emulate. 1568 this.enabled = enabled; 1569 return; 1570 } 1571 1572 int oldLocationMode = getLocationMode(); 1573 int newLocationMode = oldLocationMode; 1574 if (RuntimeEnvironment.getApiLevel() < VERSION_CODES.P) { 1575 if (GPS_PROVIDER.equals(name)) { 1576 if (enabled) { 1577 switch (oldLocationMode) { 1578 case LOCATION_MODE_OFF: 1579 newLocationMode = LOCATION_MODE_SENSORS_ONLY; 1580 break; 1581 case LOCATION_MODE_BATTERY_SAVING: 1582 newLocationMode = LOCATION_MODE_HIGH_ACCURACY; 1583 break; 1584 default: 1585 break; 1586 } 1587 } else { 1588 switch (oldLocationMode) { 1589 case LOCATION_MODE_SENSORS_ONLY: 1590 newLocationMode = LOCATION_MODE_OFF; 1591 break; 1592 case LOCATION_MODE_HIGH_ACCURACY: 1593 newLocationMode = LOCATION_MODE_BATTERY_SAVING; 1594 break; 1595 default: 1596 break; 1597 } 1598 } 1599 } else if (NETWORK_PROVIDER.equals(name)) { 1600 if (enabled) { 1601 switch (oldLocationMode) { 1602 case LOCATION_MODE_OFF: 1603 newLocationMode = LOCATION_MODE_BATTERY_SAVING; 1604 break; 1605 case LOCATION_MODE_SENSORS_ONLY: 1606 newLocationMode = LOCATION_MODE_HIGH_ACCURACY; 1607 break; 1608 default: 1609 break; 1610 } 1611 } else { 1612 switch (oldLocationMode) { 1613 case LOCATION_MODE_BATTERY_SAVING: 1614 newLocationMode = LOCATION_MODE_OFF; 1615 break; 1616 case LOCATION_MODE_HIGH_ACCURACY: 1617 newLocationMode = LOCATION_MODE_SENSORS_ONLY; 1618 break; 1619 default: 1620 break; 1621 } 1622 } 1623 } 1624 } 1625 1626 if (newLocationMode != oldLocationMode) { 1627 // this sets LOCATION_MODE and LOCATION_PROVIDERS_ALLOWED 1628 setLocationModeInternal(newLocationMode); 1629 } else if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.Q) { 1630 if (enabled == this.enabled) { 1631 return; 1632 } 1633 1634 this.enabled = enabled; 1635 // set LOCATION_PROVIDERS_ALLOWED directly, without setting LOCATION_MODE. do this even 1636 // though LOCATION_PROVIDERS_ALLOWED is not the source of truth - we keep it up to date, 1637 // but ignore any direct writes to it 1638 ShadowSettings.ShadowSecure.updateEnabledProviders( 1639 getContext().getContentResolver(), name, enabled); 1640 } else { 1641 if (enabled == this.enabled) { 1642 return; 1643 } 1644 1645 this.enabled = enabled; 1646 // set LOCATION_PROVIDERS_ALLOWED directly, without setting LOCATION_MODE 1647 ShadowSettings.ShadowSecure.updateEnabledProviders( 1648 getContext().getContentResolver(), name, enabled); 1649 } 1650 1651 transports = locationTransports; 1652 } 1653 1654 for (LocationTransport<?> transport : transports) { 1655 if (!transport.invokeOnProviderEnabled(name, enabled)) { 1656 synchronized (this) { 1657 Iterables.removeIf(locationTransports, current -> current == transport); 1658 } 1659 } 1660 } 1661 } 1662 1663 @Nullable getLastLocation()1664 public synchronized Location getLastLocation() { 1665 return lastLocation; 1666 } 1667 setLastLocation(@ullable Location location)1668 public synchronized void setLastLocation(@Nullable Location location) { 1669 lastLocation = location; 1670 } 1671 simulateLocation(Location... locations)1672 public void simulateLocation(Location... locations) { 1673 List<LocationTransport<?>> transports; 1674 LegacyBatchedTransport batchedTransport; 1675 synchronized (this) { 1676 lastLocation = new Location(locations[locations.length - 1]); 1677 transports = locationTransports; 1678 batchedTransport = legacyBatchedTransport; 1679 } 1680 1681 if (batchedTransport != null) { 1682 batchedTransport.invokeOnLocations(locations); 1683 } 1684 1685 for (LocationTransport<?> transport : transports) { 1686 if (!transport.invokeOnLocations(locations)) { 1687 synchronized (this) { 1688 Iterables.removeIf(locationTransports, current -> current == transport); 1689 } 1690 } 1691 } 1692 } 1693 meetsCriteria(Criteria criteria)1694 public synchronized boolean meetsCriteria(Criteria criteria) { 1695 if (PASSIVE_PROVIDER.equals(name)) { 1696 return false; 1697 } 1698 1699 if (properties == null) { 1700 return false; 1701 } 1702 return properties.meetsCriteria(criteria); 1703 } 1704 addListener( LocationListener listener, RoboLocationRequest request, Executor executor)1705 public void addListener( 1706 LocationListener listener, RoboLocationRequest request, Executor executor) { 1707 addListenerInternal(new LocationListenerTransport(listener, request, executor)); 1708 } 1709 addListener(PendingIntent pendingIntent, RoboLocationRequest request)1710 public void addListener(PendingIntent pendingIntent, RoboLocationRequest request) { 1711 addListenerInternal(new LocationPendingIntentTransport(getContext(), pendingIntent, request)); 1712 } 1713 setLegacyBatchedListener( Object callback, Executor executor, int batchSize, boolean flushOnFifoFull)1714 public void setLegacyBatchedListener( 1715 Object callback, Executor executor, int batchSize, boolean flushOnFifoFull) { 1716 synchronized (this) { 1717 legacyBatchedTransport = 1718 new LegacyBatchedTransport(callback, executor, batchSize, flushOnFifoFull); 1719 } 1720 } 1721 flushLegacyBatch()1722 public void flushLegacyBatch() { 1723 LegacyBatchedTransport batchedTransport; 1724 synchronized (this) { 1725 batchedTransport = legacyBatchedTransport; 1726 } 1727 1728 if (batchedTransport != null) { 1729 batchedTransport.invokeFlush(); 1730 } 1731 } 1732 clearLegacyBatchedListener()1733 public void clearLegacyBatchedListener() { 1734 synchronized (this) { 1735 legacyBatchedTransport = null; 1736 } 1737 } 1738 addListenerInternal(LocationTransport<?> transport)1739 private void addListenerInternal(LocationTransport<?> transport) { 1740 boolean invokeOnProviderEnabled; 1741 synchronized (this) { 1742 Iterables.removeIf(locationTransports, current -> current.getKey() == transport.getKey()); 1743 locationTransports.add(transport); 1744 invokeOnProviderEnabled = !enabled; 1745 } 1746 1747 if (invokeOnProviderEnabled) { 1748 if (!transport.invokeOnProviderEnabled(name, false)) { 1749 synchronized (this) { 1750 Iterables.removeIf(locationTransports, current -> current == transport); 1751 } 1752 } 1753 } 1754 } 1755 removeListener(Object key)1756 public synchronized void removeListener(Object key) { 1757 Iterables.removeIf(locationTransports, transport -> transport.getKey() == key); 1758 } 1759 requestFlush(Object key, int requestCode)1760 public void requestFlush(Object key, int requestCode) { 1761 LocationTransport<?> transport; 1762 synchronized (this) { 1763 transport = Iterables.tryFind(locationTransports, t -> t.getKey() == key).orNull(); 1764 } 1765 1766 if (transport == null) { 1767 throw new IllegalArgumentException("unregistered listener cannot be flushed"); 1768 } 1769 1770 if (!transport.invokeOnFlush(requestCode)) { 1771 synchronized (this) { 1772 Iterables.removeIf(locationTransports, current -> current == transport); 1773 } 1774 } 1775 } 1776 1777 @Override equals(Object o)1778 public boolean equals(Object o) { 1779 if (o instanceof ProviderEntry) { 1780 ProviderEntry that = (ProviderEntry) o; 1781 return Objects.equals(name, that.name); 1782 } 1783 1784 return false; 1785 } 1786 1787 @Override hashCode()1788 public int hashCode() { 1789 return Objects.hashCode(name); 1790 } 1791 } 1792 1793 /** 1794 * LocationRequest doesn't exist prior to Kitkat, and is not public prior to S, so a new class is 1795 * required to represent it prior to those platforms. 1796 */ 1797 public static final class RoboLocationRequest { 1798 @Nullable private final Object locationRequest; 1799 1800 // all these parameters are meaningless if locationRequest is set 1801 private final long intervalMillis; 1802 private final float minUpdateDistanceMeters; 1803 private final boolean singleShot; 1804 1805 @RequiresApi(VERSION_CODES.KITKAT) RoboLocationRequest(LocationRequest locationRequest)1806 public RoboLocationRequest(LocationRequest locationRequest) { 1807 this.locationRequest = Objects.requireNonNull(locationRequest); 1808 intervalMillis = 0; 1809 minUpdateDistanceMeters = 0; 1810 singleShot = false; 1811 } 1812 RoboLocationRequest( String provider, long intervalMillis, float minUpdateDistanceMeters, boolean singleShot)1813 public RoboLocationRequest( 1814 String provider, long intervalMillis, float minUpdateDistanceMeters, boolean singleShot) { 1815 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.KITKAT) { 1816 locationRequest = 1817 LocationRequest.createFromDeprecatedProvider( 1818 provider, intervalMillis, minUpdateDistanceMeters, singleShot); 1819 } else { 1820 locationRequest = null; 1821 } 1822 1823 this.intervalMillis = intervalMillis; 1824 this.minUpdateDistanceMeters = minUpdateDistanceMeters; 1825 this.singleShot = singleShot; 1826 } 1827 1828 @RequiresApi(VERSION_CODES.KITKAT) getLocationRequest()1829 public LocationRequest getLocationRequest() { 1830 return (LocationRequest) Objects.requireNonNull(locationRequest); 1831 } 1832 getIntervalMillis()1833 public long getIntervalMillis() { 1834 if (locationRequest != null) { 1835 return ((LocationRequest) locationRequest).getInterval(); 1836 } else { 1837 return intervalMillis; 1838 } 1839 } 1840 getMinUpdateDistanceMeters()1841 public float getMinUpdateDistanceMeters() { 1842 if (locationRequest != null) { 1843 return ((LocationRequest) locationRequest).getSmallestDisplacement(); 1844 } else { 1845 return minUpdateDistanceMeters; 1846 } 1847 } 1848 isSingleShot()1849 public boolean isSingleShot() { 1850 if (locationRequest != null) { 1851 return ((LocationRequest) locationRequest).getNumUpdates() == 1; 1852 } else { 1853 return singleShot; 1854 } 1855 } 1856 getMinUpdateIntervalMillis()1857 long getMinUpdateIntervalMillis() { 1858 if (locationRequest != null) { 1859 return ((LocationRequest) locationRequest).getFastestInterval(); 1860 } else { 1861 return intervalMillis; 1862 } 1863 } 1864 getMaxUpdates()1865 int getMaxUpdates() { 1866 if (locationRequest != null) { 1867 return ((LocationRequest) locationRequest).getNumUpdates(); 1868 } else { 1869 return singleShot ? 1 : Integer.MAX_VALUE; 1870 } 1871 } 1872 1873 @Override equals(Object o)1874 public boolean equals(Object o) { 1875 if (o instanceof RoboLocationRequest) { 1876 RoboLocationRequest that = (RoboLocationRequest) o; 1877 1878 // location request equality is not well-defined prior to S 1879 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.S) { 1880 return Objects.equals(locationRequest, that.locationRequest); 1881 } else { 1882 if (intervalMillis != that.intervalMillis 1883 || singleShot != that.singleShot 1884 || Float.compare(that.minUpdateDistanceMeters, minUpdateDistanceMeters) != 0 1885 || (locationRequest == null) != (that.locationRequest == null)) { 1886 return false; 1887 } 1888 1889 if (locationRequest != null) { 1890 LocationRequest lr = (LocationRequest) locationRequest; 1891 LocationRequest thatLr = (LocationRequest) that.locationRequest; 1892 1893 if (lr.getQuality() != thatLr.getQuality() 1894 || lr.getInterval() != thatLr.getInterval() 1895 || lr.getFastestInterval() != thatLr.getFastestInterval() 1896 || lr.getExpireAt() != thatLr.getExpireAt() 1897 || lr.getNumUpdates() != thatLr.getNumUpdates() 1898 || lr.getSmallestDisplacement() != thatLr.getSmallestDisplacement() 1899 || lr.getHideFromAppOps() != thatLr.getHideFromAppOps() 1900 || !Objects.equals(lr.getProvider(), thatLr.getProvider())) { 1901 return false; 1902 } 1903 1904 // allow null worksource to match empty worksource 1905 WorkSource workSource = 1906 lr.getWorkSource() == null ? new WorkSource() : lr.getWorkSource(); 1907 WorkSource thatWorkSource = 1908 thatLr.getWorkSource() == null ? new WorkSource() : thatLr.getWorkSource(); 1909 if (!workSource.equals(thatWorkSource)) { 1910 return false; 1911 } 1912 1913 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.Q) { 1914 if (lr.isLowPowerMode() != thatLr.isLowPowerMode() 1915 || lr.isLocationSettingsIgnored() != thatLr.isLocationSettingsIgnored()) { 1916 return false; 1917 } 1918 } 1919 } 1920 1921 return true; 1922 } 1923 } 1924 1925 return false; 1926 } 1927 1928 @Override hashCode()1929 public int hashCode() { 1930 if (locationRequest != null) { 1931 return locationRequest.hashCode(); 1932 } else { 1933 return Objects.hash(intervalMillis, singleShot, minUpdateDistanceMeters); 1934 } 1935 } 1936 1937 @Override toString()1938 public String toString() { 1939 if (locationRequest != null) { 1940 return locationRequest.toString(); 1941 } else { 1942 return "Request[interval=" 1943 + intervalMillis 1944 + ", minUpdateDistance=" 1945 + minUpdateDistanceMeters 1946 + ", singleShot=" 1947 + singleShot 1948 + "]"; 1949 } 1950 } 1951 } 1952 1953 private abstract static class LocationTransport<KeyT> { 1954 1955 private final KeyT key; 1956 private final RoboLocationRequest request; 1957 1958 private Location lastDeliveredLocation; 1959 private int numDeliveries; 1960 LocationTransport(KeyT key, RoboLocationRequest request)1961 LocationTransport(KeyT key, RoboLocationRequest request) { 1962 if (key == null) { 1963 throw new IllegalArgumentException(); 1964 } 1965 1966 this.key = key; 1967 this.request = request; 1968 } 1969 getKey()1970 public KeyT getKey() { 1971 return key; 1972 } 1973 getRequest()1974 public RoboLocationRequest getRequest() { 1975 return request; 1976 } 1977 1978 // return false if this listener should be removed by this invocation invokeOnLocations(Location... locations)1979 public boolean invokeOnLocations(Location... locations) { 1980 ArrayList<Location> deliverableLocations = new ArrayList<>(locations.length); 1981 for (Location location : locations) { 1982 if (lastDeliveredLocation != null) { 1983 if (location.getTime() - lastDeliveredLocation.getTime() 1984 < request.getMinUpdateIntervalMillis()) { 1985 Log.w(TAG, "location rejected for simulated delivery - too fast"); 1986 continue; 1987 } 1988 if (distanceBetween(location, lastDeliveredLocation) 1989 < request.getMinUpdateDistanceMeters()) { 1990 Log.w(TAG, "location rejected for simulated delivery - too close"); 1991 continue; 1992 } 1993 } 1994 1995 deliverableLocations.add(new Location(location)); 1996 lastDeliveredLocation = new Location(location); 1997 } 1998 1999 if (deliverableLocations.isEmpty()) { 2000 return true; 2001 } 2002 2003 boolean needsRemoval = false; 2004 2005 numDeliveries += deliverableLocations.size(); 2006 if (numDeliveries >= request.getMaxUpdates()) { 2007 needsRemoval = true; 2008 } 2009 2010 try { 2011 if (deliverableLocations.size() == 1) { 2012 onLocation(deliverableLocations.get(0)); 2013 } else { 2014 onLocations(deliverableLocations); 2015 } 2016 } catch (CanceledException e) { 2017 needsRemoval = true; 2018 } 2019 2020 return !needsRemoval; 2021 } 2022 2023 // return false if this listener should be removed by this invocation invokeOnProviderEnabled(String provider, boolean enabled)2024 public boolean invokeOnProviderEnabled(String provider, boolean enabled) { 2025 try { 2026 onProviderEnabled(provider, enabled); 2027 return true; 2028 } catch (CanceledException e) { 2029 return false; 2030 } 2031 } 2032 2033 // return false if this listener should be removed by this invocation invokeOnFlush(int requestCode)2034 public boolean invokeOnFlush(int requestCode) { 2035 try { 2036 onFlushComplete(requestCode); 2037 return true; 2038 } catch (CanceledException e) { 2039 return false; 2040 } 2041 } 2042 onLocation(Location location)2043 abstract void onLocation(Location location) throws CanceledException; 2044 onLocations(List<Location> locations)2045 abstract void onLocations(List<Location> locations) throws CanceledException; 2046 onProviderEnabled(String provider, boolean enabled)2047 abstract void onProviderEnabled(String provider, boolean enabled) throws CanceledException; 2048 onFlushComplete(int requestCode)2049 abstract void onFlushComplete(int requestCode) throws CanceledException; 2050 } 2051 2052 private static final class LocationListenerTransport extends LocationTransport<LocationListener> { 2053 2054 private final Executor executor; 2055 LocationListenerTransport( LocationListener key, RoboLocationRequest request, Executor executor)2056 LocationListenerTransport( 2057 LocationListener key, RoboLocationRequest request, Executor executor) { 2058 super(key, request); 2059 this.executor = executor; 2060 } 2061 2062 @Override onLocation(Location location)2063 void onLocation(Location location) { 2064 executor.execute(() -> getKey().onLocationChanged(location)); 2065 } 2066 2067 @Override onLocations(List<Location> locations)2068 void onLocations(List<Location> locations) { 2069 executor.execute( 2070 () -> { 2071 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.S) { 2072 getKey().onLocationChanged(locations); 2073 } else { 2074 for (Location location : locations) { 2075 getKey().onLocationChanged(location); 2076 } 2077 } 2078 }); 2079 } 2080 2081 @Override onProviderEnabled(String provider, boolean enabled)2082 void onProviderEnabled(String provider, boolean enabled) { 2083 executor.execute( 2084 () -> { 2085 if (enabled) { 2086 getKey().onProviderEnabled(provider); 2087 } else { 2088 getKey().onProviderDisabled(provider); 2089 } 2090 }); 2091 } 2092 2093 @Override onFlushComplete(int requestCode)2094 void onFlushComplete(int requestCode) { 2095 executor.execute(() -> getKey().onFlushComplete(requestCode)); 2096 } 2097 } 2098 2099 private static final class LocationPendingIntentTransport 2100 extends LocationTransport<PendingIntent> { 2101 2102 private final Context context; 2103 LocationPendingIntentTransport( Context context, PendingIntent key, RoboLocationRequest request)2104 LocationPendingIntentTransport( 2105 Context context, PendingIntent key, RoboLocationRequest request) { 2106 super(key, request); 2107 this.context = context; 2108 } 2109 2110 @Override onLocation(Location location)2111 void onLocation(Location location) throws CanceledException { 2112 Intent intent = new Intent(); 2113 intent.putExtra(LocationManager.KEY_LOCATION_CHANGED, new Location(location)); 2114 getKey().send(context, 0, intent); 2115 } 2116 2117 @Override onLocations(List<Location> locations)2118 void onLocations(List<Location> locations) throws CanceledException { 2119 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.S) { 2120 Intent intent = new Intent(); 2121 intent.putExtra(LocationManager.KEY_LOCATION_CHANGED, locations.get(locations.size() - 1)); 2122 intent.putExtra(LocationManager.KEY_LOCATIONS, locations.toArray(new Location[0])); 2123 getKey().send(context, 0, intent); 2124 } else { 2125 for (Location location : locations) { 2126 onLocation(location); 2127 } 2128 } 2129 } 2130 2131 @Override onProviderEnabled(String provider, boolean enabled)2132 void onProviderEnabled(String provider, boolean enabled) throws CanceledException { 2133 Intent intent = new Intent(); 2134 intent.putExtra(LocationManager.KEY_PROVIDER_ENABLED, enabled); 2135 getKey().send(context, 0, intent); 2136 } 2137 2138 @Override onFlushComplete(int requestCode)2139 void onFlushComplete(int requestCode) throws CanceledException { 2140 Intent intent = new Intent(); 2141 intent.putExtra(LocationManager.KEY_FLUSH_COMPLETE, requestCode); 2142 getKey().send(context, 0, intent); 2143 } 2144 } 2145 2146 private static final class LegacyBatchedTransport { 2147 2148 private final android.location.BatchedLocationCallback callback; 2149 private final Executor executor; 2150 private final int batchSize; 2151 private final boolean flushOnFifoFull; 2152 2153 private ArrayList<Location> batch = new ArrayList<>(); 2154 LegacyBatchedTransport( Object callback, Executor executor, int batchSize, boolean flushOnFifoFull)2155 LegacyBatchedTransport( 2156 Object callback, Executor executor, int batchSize, boolean flushOnFifoFull) { 2157 this.callback = (android.location.BatchedLocationCallback) callback; 2158 this.executor = executor; 2159 this.batchSize = batchSize; 2160 this.flushOnFifoFull = flushOnFifoFull; 2161 } 2162 invokeFlush()2163 public void invokeFlush() { 2164 ArrayList<Location> delivery = batch; 2165 batch = new ArrayList<>(); 2166 executor.execute( 2167 () -> { 2168 callback.onLocationBatch(delivery); 2169 if (!delivery.isEmpty()) { 2170 callback.onLocationBatch(new ArrayList<>()); 2171 } 2172 }); 2173 } 2174 invokeOnLocations(Location... locations)2175 public void invokeOnLocations(Location... locations) { 2176 for (Location location : locations) { 2177 batch.add(new Location(location)); 2178 if (batch.size() >= batchSize) { 2179 if (!flushOnFifoFull) { 2180 batch.remove(0); 2181 } else { 2182 ArrayList<Location> delivery = batch; 2183 batch = new ArrayList<>(); 2184 executor.execute(() -> callback.onLocationBatch(delivery)); 2185 } 2186 } 2187 } 2188 } 2189 } 2190 2191 /** 2192 * Returns the distance between the two locations in meters. Adapted from: 2193 * http://stackoverflow.com/questions/837872/calculate-distance-in-meters-when-you-know-longitude-and-latitude-in-java 2194 */ distanceBetween(Location location1, Location location2)2195 static float distanceBetween(Location location1, Location location2) { 2196 double earthRadius = 3958.75; 2197 double latDifference = Math.toRadians(location2.getLatitude() - location1.getLatitude()); 2198 double lonDifference = Math.toRadians(location2.getLongitude() - location1.getLongitude()); 2199 double a = 2200 Math.sin(latDifference / 2) * Math.sin(latDifference / 2) 2201 + Math.cos(Math.toRadians(location1.getLatitude())) 2202 * Math.cos(Math.toRadians(location2.getLatitude())) 2203 * Math.sin(lonDifference / 2) 2204 * Math.sin(lonDifference / 2); 2205 double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 2206 double dist = Math.abs(earthRadius * c); 2207 2208 int meterConversion = 1609; 2209 2210 return (float) (dist * meterConversion); 2211 } 2212 2213 @Resetter reset()2214 public static synchronized void reset() { 2215 locationProviderConstructor = null; 2216 } 2217 2218 @RequiresApi(api = VERSION_CODES.N) 2219 private final class CurrentLocationTransport implements LocationListener { 2220 2221 private final Executor executor; 2222 private final Consumer<Location> consumer; 2223 private final Handler timeoutHandler; 2224 2225 @GuardedBy("this") 2226 private boolean triggered; 2227 2228 @Nullable Runnable timeoutRunnable; 2229 CurrentLocationTransport(Executor executor, Consumer<Location> consumer)2230 CurrentLocationTransport(Executor executor, Consumer<Location> consumer) { 2231 this.executor = executor; 2232 this.consumer = consumer; 2233 timeoutHandler = new Handler(Looper.getMainLooper()); 2234 } 2235 cancel()2236 public void cancel() { 2237 synchronized (this) { 2238 if (triggered) { 2239 return; 2240 } 2241 triggered = true; 2242 } 2243 2244 cleanup(); 2245 } 2246 startTimeout(long timeoutMs)2247 public void startTimeout(long timeoutMs) { 2248 synchronized (this) { 2249 if (triggered) { 2250 return; 2251 } 2252 2253 timeoutRunnable = 2254 () -> { 2255 timeoutRunnable = null; 2256 onLocationChanged((Location) null); 2257 }; 2258 timeoutHandler.postDelayed(timeoutRunnable, timeoutMs); 2259 } 2260 } 2261 2262 @Override onStatusChanged(String provider, int status, Bundle extras)2263 public void onStatusChanged(String provider, int status, Bundle extras) {} 2264 2265 @Override onProviderEnabled(String provider)2266 public void onProviderEnabled(String provider) {} 2267 2268 @Override onProviderDisabled(String provider)2269 public void onProviderDisabled(String provider) { 2270 onLocationChanged((Location) null); 2271 } 2272 2273 @Override onLocationChanged(@ullable Location location)2274 public void onLocationChanged(@Nullable Location location) { 2275 synchronized (this) { 2276 if (triggered) { 2277 return; 2278 } 2279 triggered = true; 2280 } 2281 2282 executor.execute(() -> consumer.accept(location)); 2283 2284 cleanup(); 2285 } 2286 cleanup()2287 private void cleanup() { 2288 removeUpdates(this); 2289 if (timeoutRunnable != null) { 2290 timeoutHandler.removeCallbacks(timeoutRunnable); 2291 timeoutRunnable = null; 2292 } 2293 } 2294 } 2295 2296 private static final class GnssStatusCallbackTransport { 2297 2298 private final Executor executor; 2299 private final GnssStatus.Callback listener; 2300 GnssStatusCallbackTransport(Executor executor, GnssStatus.Callback listener)2301 GnssStatusCallbackTransport(Executor executor, GnssStatus.Callback listener) { 2302 this.executor = Objects.requireNonNull(executor); 2303 this.listener = Objects.requireNonNull(listener); 2304 } 2305 getListener()2306 GnssStatus.Callback getListener() { 2307 return listener; 2308 } 2309 2310 @RequiresApi(api = VERSION_CODES.N) onStarted()2311 public void onStarted() { 2312 executor.execute(listener::onStarted); 2313 } 2314 2315 @RequiresApi(api = VERSION_CODES.N) onFirstFix(int ttff)2316 public void onFirstFix(int ttff) { 2317 executor.execute(() -> listener.onFirstFix(ttff)); 2318 } 2319 2320 @RequiresApi(api = VERSION_CODES.N) onSatelliteStatusChanged(GnssStatus status)2321 public void onSatelliteStatusChanged(GnssStatus status) { 2322 executor.execute(() -> listener.onSatelliteStatusChanged(status)); 2323 } 2324 2325 @RequiresApi(api = VERSION_CODES.N) onStopped()2326 public void onStopped() { 2327 executor.execute(listener::onStopped); 2328 } 2329 } 2330 2331 private static final class OnNmeaMessageListenerTransport { 2332 2333 private final Executor executor; 2334 private final OnNmeaMessageListener listener; 2335 OnNmeaMessageListenerTransport(Executor executor, OnNmeaMessageListener listener)2336 OnNmeaMessageListenerTransport(Executor executor, OnNmeaMessageListener listener) { 2337 this.executor = Objects.requireNonNull(executor); 2338 this.listener = Objects.requireNonNull(listener); 2339 } 2340 getListener()2341 OnNmeaMessageListener getListener() { 2342 return listener; 2343 } 2344 2345 @RequiresApi(api = VERSION_CODES.N) onNmeaMessage(String message, long timestamp)2346 public void onNmeaMessage(String message, long timestamp) { 2347 executor.execute(() -> listener.onNmeaMessage(message, timestamp)); 2348 } 2349 } 2350 2351 private static final class GnssMeasurementsEventCallbackTransport { 2352 2353 private final Executor executor; 2354 private final GnssMeasurementsEvent.Callback listener; 2355 GnssMeasurementsEventCallbackTransport( Executor executor, GnssMeasurementsEvent.Callback listener)2356 GnssMeasurementsEventCallbackTransport( 2357 Executor executor, GnssMeasurementsEvent.Callback listener) { 2358 this.executor = Objects.requireNonNull(executor); 2359 this.listener = Objects.requireNonNull(listener); 2360 } 2361 getListener()2362 GnssMeasurementsEvent.Callback getListener() { 2363 return listener; 2364 } 2365 2366 @RequiresApi(api = VERSION_CODES.N) onStatusChanged(int status)2367 public void onStatusChanged(int status) { 2368 executor.execute(() -> listener.onStatusChanged(status)); 2369 } 2370 2371 @RequiresApi(api = VERSION_CODES.N) onGnssMeasurementsReceived(GnssMeasurementsEvent event)2372 public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) { 2373 executor.execute(() -> listener.onGnssMeasurementsReceived(event)); 2374 } 2375 } 2376 2377 private static final class GnssAntennaInfoListenerTransport { 2378 2379 private final Executor executor; 2380 private final GnssAntennaInfo.Listener listener; 2381 GnssAntennaInfoListenerTransport(Executor executor, GnssAntennaInfo.Listener listener)2382 GnssAntennaInfoListenerTransport(Executor executor, GnssAntennaInfo.Listener listener) { 2383 this.executor = Objects.requireNonNull(executor); 2384 this.listener = Objects.requireNonNull(listener); 2385 } 2386 getListener()2387 GnssAntennaInfo.Listener getListener() { 2388 return listener; 2389 } 2390 2391 @RequiresApi(api = VERSION_CODES.R) onGnssAntennaInfoReceived(List<GnssAntennaInfo> antennaInfos)2392 public void onGnssAntennaInfoReceived(List<GnssAntennaInfo> antennaInfos) { 2393 executor.execute(() -> listener.onGnssAntennaInfoReceived(antennaInfos)); 2394 } 2395 } 2396 2397 private static final class HandlerExecutor implements Executor { 2398 private final Handler handler; 2399 HandlerExecutor(Handler handler)2400 HandlerExecutor(Handler handler) { 2401 this.handler = Objects.requireNonNull(handler); 2402 } 2403 2404 @Override execute(Runnable command)2405 public void execute(Runnable command) { 2406 if (!handler.post(command)) { 2407 throw new RejectedExecutionException(handler + " is shutting down"); 2408 } 2409 } 2410 } 2411 } 2412