• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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