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