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