summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--location/java/android/location/LocationManagerInternal.java36
-rw-r--r--location/java/android/location/LocationRequest.java56
-rw-r--r--services/core/java/com/android/server/location/LocationFudger.java4
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java2246
-rw-r--r--services/core/java/com/android/server/location/LocationManagerServiceUtils.java73
-rw-r--r--services/core/java/com/android/server/location/LocationProviderManager.java1951
-rw-r--r--services/core/java/com/android/server/location/MockProvider.java3
-rw-r--r--services/core/java/com/android/server/location/MockableLocationProvider.java1
-rw-r--r--services/core/java/com/android/server/location/PassiveLocationProviderManager.java60
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java22
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java40
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerRegistration.java14
-rw-r--r--services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java2
-rw-r--r--services/tests/mockingservicestests/AndroidManifest.xml2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java971
15 files changed, 3372 insertions, 2109 deletions
diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java
index 542737b479e2..ef68814bce84 100644
--- a/location/java/android/location/LocationManagerInternal.java
+++ b/location/java/android/location/LocationManagerInternal.java
@@ -21,6 +21,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.location.util.identity.CallerIdentity;
+import java.util.List;
+
/**
* Location manager local system service interface.
*
@@ -29,6 +31,16 @@ import android.location.util.identity.CallerIdentity;
public abstract class LocationManagerInternal {
/**
+ * Listener for changes in provider enabled state.
+ */
+ public interface ProviderEnabledListener {
+ /**
+ * Called when the provider enabled state changes for a particular user.
+ */
+ void onProviderEnabledChanged(String provider, int userId, boolean enabled);
+ }
+
+ /**
* Returns true if the given provider is enabled for the given user.
*
* @param provider A location provider as listed by {@link LocationManager#getAllProviders()}
@@ -38,6 +50,24 @@ public abstract class LocationManagerInternal {
public abstract boolean isProviderEnabledForUser(@NonNull String provider, int userId);
/**
+ * Adds a provider enabled listener. The given provider must exist.
+ *
+ * @param provider The provider to listen for changes
+ * @param listener The listener
+ */
+ public abstract void addProviderEnabledListener(String provider,
+ ProviderEnabledListener listener);
+
+ /**
+ * Removes a provider enabled listener. The given provider must exist.
+ *
+ * @param provider The provider to listen for changes
+ * @param listener The listener
+ */
+ public abstract void removeProviderEnabledListener(String provider,
+ ProviderEnabledListener listener);
+
+ /**
* Returns true if the given identity is a location provider.
*
* @param provider The provider to check, or null to check every provider
@@ -52,4 +82,10 @@ public abstract class LocationManagerInternal {
*/
// TODO: there is no reason for this to exist as part of any API. move all the logic into gnss
public abstract void sendNiResponse(int notifId, int userResponse);
+
+ /**
+ * Should only be used by GNSS code.
+ */
+ // TODO: there is no reason for this to exist as part of any API. create a real batching API
+ public abstract void reportGnssBatchLocations(List<Location> locations);
}
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index bb36c2a1fc39..280bd058ef0f 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -32,6 +32,8 @@ import android.util.TimeUtils;
import com.android.internal.util.Preconditions;
+import java.util.Objects;
+
/**
* A data object that contains quality of service parameters for requests
@@ -150,8 +152,6 @@ public final class LocationRequest implements Parcelable {
@UnsupportedAppUsage
private String mProvider;
- // if true, client requests coarse location, if false, client requests fine location
- private boolean mCoarseLocation;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private int mQuality;
@UnsupportedAppUsage
@@ -257,7 +257,6 @@ public final class LocationRequest implements Parcelable {
public LocationRequest() {
this(
/* provider= */ LocationManager.FUSED_PROVIDER,
- /* coarseLocation= */ false,
/* quality= */ POWER_LOW,
/* interval= */ DEFAULT_INTERVAL_MS,
/* fastestInterval= */ (long) (DEFAULT_INTERVAL_MS / FASTEST_INTERVAL_FACTOR),
@@ -276,7 +275,6 @@ public final class LocationRequest implements Parcelable {
public LocationRequest(LocationRequest src) {
this(
src.mProvider,
- src.mCoarseLocation,
src.mQuality,
src.mInterval,
src.mFastestInterval,
@@ -293,7 +291,6 @@ public final class LocationRequest implements Parcelable {
private LocationRequest(
@NonNull String provider,
- boolean coarseLocation,
int quality,
long intervalMs,
long fastestIntervalMs,
@@ -310,7 +307,6 @@ public final class LocationRequest implements Parcelable {
checkQuality(quality);
mProvider = provider;
- mCoarseLocation = coarseLocation;
mQuality = quality;
mInterval = intervalMs;
mFastestInterval = fastestIntervalMs;
@@ -327,20 +323,6 @@ public final class LocationRequest implements Parcelable {
}
/**
- * @hide
- */
- public boolean isCoarse() {
- return mCoarseLocation;
- }
-
- /**
- * @hide
- */
- public void setCoarse(boolean coarse) {
- mCoarseLocation = coarse;
- }
-
- /**
* Set the quality of the request.
*
* <p>Use with a accuracy constant such as {@link #ACCURACY_FINE}, or a power
@@ -720,7 +702,6 @@ public final class LocationRequest implements Parcelable {
public LocationRequest createFromParcel(Parcel in) {
return new LocationRequest(
/* provider= */ in.readString(),
- /* coarseLocation= */ in.readBoolean(),
/* quality= */ in.readInt(),
/* interval= */ in.readLong(),
/* fastestInterval= */ in.readLong(),
@@ -749,7 +730,6 @@ public final class LocationRequest implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(mProvider);
- parcel.writeBoolean(mCoarseLocation);
parcel.writeInt(mQuality);
parcel.writeLong(mInterval);
parcel.writeLong(mFastestInterval);
@@ -784,6 +764,36 @@ public final class LocationRequest implements Parcelable {
}
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ LocationRequest that = (LocationRequest) o;
+ return mQuality == that.mQuality
+ && mInterval == that.mInterval
+ && mFastestInterval == that.mFastestInterval
+ && mExplicitFastestInterval == that.mExplicitFastestInterval
+ && mExpireAt == that.mExpireAt
+ && mExpireIn == that.mExpireIn
+ && mNumUpdates == that.mNumUpdates
+ && Float.compare(that.mSmallestDisplacement, mSmallestDisplacement) == 0
+ && mHideFromAppOps == that.mHideFromAppOps
+ && mLocationSettingsIgnored == that.mLocationSettingsIgnored
+ && mLowPowerMode == that.mLowPowerMode
+ && mProvider.equals(that.mProvider)
+ && Objects.equals(mWorkSource, that.mWorkSource);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mProvider, mInterval, mWorkSource);
+ }
+
@NonNull
@Override
public String toString() {
@@ -794,7 +804,7 @@ public final class LocationRequest implements Parcelable {
if (mQuality != POWER_NONE) {
s.append(" interval=");
TimeUtils.formatDuration(mInterval, s);
- if (mExplicitFastestInterval) {
+ if (mExplicitFastestInterval && mFastestInterval != mInterval) {
s.append(" fastestInterval=");
TimeUtils.formatDuration(mFastestInterval, s);
}
diff --git a/services/core/java/com/android/server/location/LocationFudger.java b/services/core/java/com/android/server/location/LocationFudger.java
index 1f458ed4e29d..6f35c8ba1e0a 100644
--- a/services/core/java/com/android/server/location/LocationFudger.java
+++ b/services/core/java/com/android/server/location/LocationFudger.java
@@ -112,7 +112,7 @@ public class LocationFudger {
public Location createCoarse(Location fine) {
synchronized (this) {
if (fine == mCachedFineLocation) {
- return new Location(mCachedCoarseLocation);
+ return mCachedCoarseLocation;
}
}
@@ -154,7 +154,7 @@ public class LocationFudger {
mCachedCoarseLocation = coarse;
}
- return new Location(mCachedCoarseLocation);
+ return mCachedCoarseLocation;
}
/**
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index d3558f1458b8..71f1833854ce 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -17,43 +17,27 @@
package com.android.server.location;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
-import static android.app.AppOpsManager.OP_MOCK_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.location.LocationManager.EXTRA_LOCATION_ENABLED;
-import static android.location.LocationManager.EXTRA_PROVIDER_ENABLED;
-import static android.location.LocationManager.EXTRA_PROVIDER_NAME;
import static android.location.LocationManager.FUSED_PROVIDER;
import static android.location.LocationManager.GPS_PROVIDER;
-import static android.location.LocationManager.KEY_LOCATION_CHANGED;
-import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
-import static android.location.LocationManager.MODE_CHANGED_ACTION;
import static android.location.LocationManager.NETWORK_PROVIDER;
-import static android.location.LocationManager.PASSIVE_PROVIDER;
-import static android.location.LocationManager.PROVIDERS_CHANGED_ACTION;
-import static android.location.LocationManager.invalidateLocalLocationEnabledCaches;
-import static android.os.PowerManager.locationPowerSaveModeToString;
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
-import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
+import static com.android.server.location.LocationProviderManager.FASTEST_COARSE_INTERVAL_MS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.location.Criteria;
import android.location.GeocoderParams;
import android.location.Geofence;
@@ -78,17 +62,9 @@ import android.location.LocationRequest;
import android.location.LocationTime;
import android.location.util.identity.CallerIdentity;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.IBinder;
import android.os.ICancellationSignal;
-import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
-import android.os.PowerManager.ServiceType;
-import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -96,24 +72,16 @@ import android.os.UserHandle;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
import android.stats.location.LocationStatsEnums;
-import android.text.TextUtils;
-import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.Log;
-import android.util.SparseArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
-import com.android.internal.location.ProviderRequest;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
-import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.PendingIntentUtils;
import com.android.server.SystemService;
-import com.android.server.location.AbstractLocationProvider.State;
import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
import com.android.server.location.LocationRequestStatistics.PackageStatistics;
@@ -137,24 +105,17 @@ import com.android.server.location.util.SystemScreenInteractiveHelper;
import com.android.server.location.util.SystemSettingsHelper;
import com.android.server.location.util.SystemUserInfoHelper;
import com.android.server.location.util.UserInfoHelper;
-import com.android.server.location.util.UserInfoHelper.UserListener;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
-import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
-import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Executor;
/**
* The service class that manages LocationProviders and issues location
@@ -241,54 +202,21 @@ public class LocationManagerService extends ILocationManager.Stub {
public static final String TAG = "LocationManagerService";
public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
- private static final String WAKELOCK_KEY = "*location*";
-
private static final String NETWORK_LOCATION_SERVICE_ACTION =
"com.android.location.service.v3.NetworkLocationProvider";
private static final String FUSED_LOCATION_SERVICE_ACTION =
"com.android.location.service.FusedLocationProvider";
- // The maximum interval a location request can have and still be considered "high power".
- private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
-
- // The fastest interval that applications can receive coarse locations
- private static final long FASTEST_COARSE_INTERVAL_MS = 10 * 60 * 1000;
-
- // maximum age of a location before it is no longer considered "current"
- private static final long MAX_CURRENT_LOCATION_AGE_MS = 10 * 1000;
-
- // Location Providers may sometimes deliver location updates
- // slightly faster that requested - provide grace period so
- // we don't unnecessarily filter events that are otherwise on
- // time
- private static final int MAX_PROVIDER_SCHEDULING_JITTER_MS = 100;
-
- private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30000;
-
private static final String ATTRIBUTION_TAG = "LocationService";
private final Object mLock = new Object();
- private final Handler mHandler;
- private final LocalService mLocalService;
-
- private final Injector mInjector;
-
private final Context mContext;
- private final AppOpsHelper mAppOpsHelper;
- private final UserInfoHelper mUserInfoHelper;
- private final SettingsHelper mSettingsHelper;
- private final AppForegroundHelper mAppForegroundHelper;
- private final LocationUsageLogger mLocationUsageLogger;
+ private final Injector mInjector;
+ private final LocalService mLocalService;
private final GeofenceManager mGeofenceManager;
-
@Nullable private volatile GnssManagerService mGnssManagerService = null;
-
- private final PassiveLocationProviderManager mPassiveManager;
-
- private PowerManager mPowerManager;
-
private GeocoderProxy mGeocodeProvider;
@GuardedBy("mLock")
@@ -296,46 +224,30 @@ public class LocationManagerService extends ILocationManager.Stub {
@GuardedBy("mLock")
private boolean mExtraLocationControllerPackageEnabled;
- // @GuardedBy("mLock")
- // hold lock for write or to prevent write, no lock for read
- final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
- new CopyOnWriteArrayList<>();
-
- @GuardedBy("mLock")
- private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
- private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
- new HashMap<>();
+ // location provider managers
- private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
+ private final PassiveLocationProviderManager mPassiveManager;
- @GuardedBy("mLock")
- @PowerManager.LocationPowerSaveMode
- private int mBatterySaverMode;
+ // @GuardedBy("mProviderManagers")
+ // hold lock for writes, no lock necessary for simple reads
+ private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
+ new CopyOnWriteArrayList<>();
LocationManagerService(Context context, Injector injector) {
- mHandler = FgThread.getHandler();
- mLocalService = new LocalService();
-
- LocalServices.addService(LocationManagerInternal.class, mLocalService);
-
+ mContext = context.createAttributionContext(ATTRIBUTION_TAG);
mInjector = injector;
- mContext = context.createAttributionContext(ATTRIBUTION_TAG);
- mUserInfoHelper = injector.getUserInfoHelper();
- mAppOpsHelper = injector.getAppOpsHelper();
- mSettingsHelper = injector.getSettingsHelper();
- mAppForegroundHelper = injector.getAppForegroundHelper();
- mLocationUsageLogger = injector.getLocationUsageLogger();
+ mLocalService = new LocalService();
+ LocalServices.addService(LocationManagerInternal.class, mLocalService);
mGeofenceManager = new GeofenceManager(mContext, injector);
- // set up passive provider - we do this early because it has no dependencies on system
- // services or external code that isn't ready yet, and because this allows the variable to
- // be final. other more complex providers are initialized later, when system services are
- // ready
- mPassiveManager = new PassiveLocationProviderManager();
- mProviderManagers.add(mPassiveManager);
- mPassiveManager.setRealProvider(new PassiveProvider(mContext));
+ // set up passive provider first since it will be required for all other location providers,
+ // which are loaded later once the system is ready.
+ mPassiveManager = new PassiveLocationProviderManager(mContext, injector);
+ addLocationProviderManager(mPassiveManager, new PassiveProvider(mContext));
+
+ // TODO: load the gps provider here as well, which will require refactoring
// Let the package manager query which are the default location
// providers as they get certain permissions granted by default.
@@ -347,253 +259,77 @@ public class LocationManagerService extends ILocationManager.Stub {
permissionManagerInternal.setLocationExtraPackagesProvider(
userId -> mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationExtraPackageNames));
-
- // most startup is deferred until systemReady()
- }
-
- void onSystemReady() {
- synchronized (mLock) {
- mPowerManager = mContext.getSystemService(PowerManager.class);
-
- // add listeners
- mContext.getPackageManager().addOnPermissionsChangeListener(
- uid -> {
- // listener invoked on ui thread, move to our thread to reduce risk of
- // blocking ui thread
- mHandler.post(() -> {
- synchronized (mLock) {
- onPermissionsChangedLocked();
- }
- });
- });
-
- LocalServices.getService(PowerManagerInternal.class).registerLowPowerModeObserver(
- ServiceType.LOCATION,
- state -> {
- // listener invoked on ui thread, move to our thread to reduce risk of
- // blocking ui thread
- mHandler.post(() -> {
- synchronized (mLock) {
- onBatterySaverModeChangedLocked(state.locationMode);
- }
- });
- });
- mBatterySaverMode = mPowerManager.getLocationPowerSaveMode();
-
- mAppOpsHelper.addListener(this::onAppOpChanged);
-
- mSettingsHelper.addOnLocationEnabledChangedListener(this::onLocationModeChanged);
- mSettingsHelper.addOnBackgroundThrottleIntervalChangedListener(
- this::onBackgroundThrottleIntervalChanged);
- mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
- this::onBackgroundThrottleWhitelistChanged);
- mSettingsHelper.addOnIgnoreSettingsPackageWhitelistChangedListener(
- this::onIgnoreSettingsWhitelistChanged);
-
- PackageMonitor packageMonitor = new PackageMonitor() {
- @Override
- public void onPackageDisappeared(String packageName, int reason) {
- synchronized (mLock) {
- LocationManagerService.this.onPackageDisappeared(packageName);
- }
- }
- };
- packageMonitor.register(mContext, null, true, mHandler);
-
- mUserInfoHelper.addListener(this::onUserChanged);
-
- mAppForegroundHelper.addListener(this::onAppForegroundChanged);
-
- IntentFilter screenIntentFilter = new IntentFilter();
- screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
- screenIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
- mContext.registerReceiverAsUser(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())
- || Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
- onScreenStateChanged();
- }
- }
- }, UserHandle.ALL, screenIntentFilter, null, mHandler);
-
- // initialize the current users. we would get the user started notifications for these
- // users eventually anyways, but this takes care of it as early as possible.
- onUserChanged(UserHandle.USER_ALL, UserListener.USER_STARTED);
- }
}
- void onSystemThirdPartyAppsCanStart() {
- synchronized (mLock) {
- // prepare providers
- initializeProvidersLocked();
- }
-
- // initialize gnss last because it has no awareness of boot phases and blindly assumes that
- // all other location providers are loaded at initialization
- initializeGnss();
- }
-
- private void onAppOpChanged(String packageName) {
- synchronized (mLock) {
- for (Receiver receiver : mReceivers.values()) {
- if (receiver.mCallerIdentity.getPackageName().equals(packageName)) {
- receiver.updateMonitoring(true);
- }
- }
-
- HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
- for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
- String provider = entry.getKey();
- for (UpdateRecord record : entry.getValue()) {
- if (record.mReceiver.mCallerIdentity.getPackageName().equals(packageName)) {
- affectedProviders.add(provider);
- }
- }
- }
- for (String provider : affectedProviders) {
- applyRequirementsLocked(provider);
- }
- }
- }
-
- @GuardedBy("mLock")
- private void onPermissionsChangedLocked() {
- for (LocationProviderManager manager : mProviderManagers) {
- applyRequirementsLocked(manager);
- }
- }
-
- @GuardedBy("mLock")
- private void onBatterySaverModeChangedLocked(int newLocationMode) {
- if (mBatterySaverMode == newLocationMode) {
- return;
- }
-
- if (D) {
- Log.d(TAG,
- "Battery Saver location mode changed from "
- + locationPowerSaveModeToString(mBatterySaverMode) + " to "
- + locationPowerSaveModeToString(newLocationMode));
+ @Nullable
+ private LocationProviderManager getLocationProviderManager(String providerName) {
+ if (providerName == null) {
+ return null;
}
- mBatterySaverMode = newLocationMode;
-
for (LocationProviderManager manager : mProviderManagers) {
- applyRequirementsLocked(manager);
- }
- }
-
- private void onScreenStateChanged() {
- synchronized (mLock) {
- if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
- for (LocationProviderManager manager : mProviderManagers) {
- applyRequirementsLocked(manager);
- }
+ if (providerName.equals(manager.getName())) {
+ return manager;
}
}
- }
-
- private void onLocationModeChanged(int userId) {
- boolean enabled = mSettingsHelper.isLocationEnabled(userId);
- LocationManager.invalidateLocalLocationEnabledCaches();
- if (D) {
- Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
- }
-
- Intent intent = new Intent(MODE_CHANGED_ACTION)
- .putExtra(EXTRA_LOCATION_ENABLED, enabled)
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
-
- synchronized (mLock) {
- for (LocationProviderManager manager : mProviderManagers) {
- manager.onEnabledChangedLocked(userId);
- }
- }
+ return null;
}
- private void onPackageDisappeared(String packageName) {
- synchronized (mLock) {
- ArrayList<Receiver> deadReceivers = null;
-
- for (Receiver receiver : mReceivers.values()) {
- if (receiver.mCallerIdentity.getPackageName().equals(packageName)) {
- if (deadReceivers == null) {
- deadReceivers = new ArrayList<>();
- }
- deadReceivers.add(receiver);
+ private LocationProviderManager getOrAddLocationProviderManager(String providerName) {
+ synchronized (mProviderManagers) {
+ for (LocationProviderManager manager : mProviderManagers) {
+ if (providerName.equals(manager.getName())) {
+ return manager;
}
}
- // perform removal outside of mReceivers loop
- if (deadReceivers != null) {
- for (Receiver receiver : deadReceivers) {
- removeUpdatesLocked(receiver);
- }
- }
+ LocationProviderManager manager = new LocationProviderManager(mContext, mInjector,
+ providerName, mPassiveManager);
+ addLocationProviderManager(manager, null);
+ return manager;
}
}
- private void onAppForegroundChanged(int uid, boolean foreground) {
- synchronized (mLock) {
- HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
- for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
- String provider = entry.getKey();
- for (UpdateRecord record : entry.getValue()) {
- if (record.mReceiver.mCallerIdentity.getUid() == uid
- && record.mIsForegroundUid != foreground) {
- record.updateForeground(foreground);
-
- if (!isThrottlingExempt(record.mReceiver.mCallerIdentity)) {
- affectedProviders.add(provider);
- }
- }
- }
- }
- for (String provider : affectedProviders) {
- applyRequirementsLocked(provider);
- }
- }
- }
+ private void addLocationProviderManager(LocationProviderManager manager,
+ @Nullable AbstractLocationProvider realProvider) {
+ synchronized (mProviderManagers) {
+ Preconditions.checkState(getLocationProviderManager(manager.getName()) == null);
- private void onBackgroundThrottleIntervalChanged() {
- synchronized (mLock) {
- for (LocationProviderManager manager : mProviderManagers) {
- applyRequirementsLocked(manager);
+ manager.startManager();
+ if (realProvider != null) {
+ manager.setRealProvider(realProvider);
}
+ mProviderManagers.add(manager);
}
}
- private void onBackgroundThrottleWhitelistChanged() {
- synchronized (mLock) {
- for (LocationProviderManager manager : mProviderManagers) {
- applyRequirementsLocked(manager);
- }
+ private void removeLocationProviderManager(LocationProviderManager manager) {
+ synchronized (mProviderManagers) {
+ Preconditions.checkState(getLocationProviderManager(manager.getName()) == manager);
+
+ mProviderManagers.remove(manager);
+ manager.setMockProvider(null);
+ manager.setRealProvider(null);
+ manager.stopManager();
}
}
- private void onIgnoreSettingsWhitelistChanged() {
- synchronized (mLock) {
- for (LocationProviderManager manager : mProviderManagers) {
- applyRequirementsLocked(manager);
- }
- }
+ void onSystemReady() {
+ mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
+ this::onLocationModeChanged);
}
- @GuardedBy("mLock")
- private void initializeProvidersLocked() {
+ void onSystemThirdPartyAppsCanStart() {
LocationProviderProxy networkProvider = LocationProviderProxy.createAndRegister(
mContext,
NETWORK_LOCATION_SERVICE_ACTION,
com.android.internal.R.bool.config_enableNetworkLocationOverlay,
com.android.internal.R.string.config_networkLocationProviderPackageName);
if (networkProvider != null) {
- LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER);
- mProviderManagers.add(networkManager);
- networkManager.setRealProvider(networkProvider);
+ LocationProviderManager networkManager = new LocationProviderManager(mContext,
+ mInjector, NETWORK_PROVIDER, mPassiveManager);
+ addLocationProviderManager(networkManager, networkProvider);
} else {
Log.w(TAG, "no network location provider found");
}
@@ -604,18 +340,28 @@ public class LocationManagerService extends ILocationManager.Stub {
MATCH_DIRECT_BOOT_AWARE | MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM).isEmpty(),
"Unable to find a direct boot aware fused location provider");
- // bind to fused provider
LocationProviderProxy fusedProvider = LocationProviderProxy.createAndRegister(
mContext,
FUSED_LOCATION_SERVICE_ACTION,
com.android.internal.R.bool.config_enableFusedLocationOverlay,
com.android.internal.R.string.config_fusedLocationProviderPackageName);
if (fusedProvider != null) {
- LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER);
- mProviderManagers.add(fusedManager);
- fusedManager.setRealProvider(fusedProvider);
+ LocationProviderManager fusedManager = new LocationProviderManager(mContext, mInjector,
+ FUSED_PROVIDER, mPassiveManager);
+ addLocationProviderManager(fusedManager, fusedProvider);
} else {
- Log.e(TAG, "no fused location provider found");
+ Log.wtf(TAG, "no fused location provider found");
+ }
+
+ // initialize gnss last because it has no awareness of boot phases and blindly assumes that
+ // all other location providers are loaded at initialization
+ if (GnssManagerService.isGnssSupported()) {
+ mGnssManagerService = new GnssManagerService(mContext, mInjector);
+ mGnssManagerService.onSystemReady();
+
+ LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
+ GPS_PROVIDER, mPassiveManager);
+ addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
}
// bind to geocoder provider
@@ -631,6 +377,18 @@ public class LocationManagerService extends ILocationManager.Stub {
Log.e(TAG, "unable to bind ActivityRecognitionProxy");
}
+ // bind to gnss geofence proxy
+ if (GnssManagerService.isGnssSupported()) {
+ IGpsGeofenceHardware gpsGeofenceHardware = mGnssManagerService.getGpsGeofenceProxy();
+ if (gpsGeofenceHardware != null) {
+ GeofenceProxy provider = GeofenceProxy.createAndBind(mContext, gpsGeofenceHardware);
+ if (provider == null) {
+ Log.e(TAG, "unable to bind to GeofenceProxy");
+ }
+ }
+ }
+
+ // create any predefined test providers
String[] testProviderStrings = mContext.getResources().getStringArray(
com.android.internal.R.array.config_testLocationProviders);
for (String testProviderString : testProviderStrings) {
@@ -646,763 +404,24 @@ public class LocationManagerService extends ILocationManager.Stub {
Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
Integer.parseInt(fragments[8]) /* powerRequirement */,
Integer.parseInt(fragments[9]) /* accuracy */);
- LocationProviderManager manager = getLocationProviderManager(name);
- if (manager == null) {
- manager = new LocationProviderManager(name);
- mProviderManagers.add(manager);
- }
- manager.setMockProvider(
+ getOrAddLocationProviderManager(name).setMockProvider(
new MockProvider(properties, CallerIdentity.fromContext(mContext)));
}
}
- private void initializeGnss() {
- // Do not hold mLock when calling GnssManagerService#isGnssSupported() which calls into HAL.
- if (GnssManagerService.isGnssSupported()) {
- mGnssManagerService = new GnssManagerService(mContext, mInjector);
- mGnssManagerService.onSystemReady();
-
- LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER);
- synchronized (mLock) {
- mProviderManagers.add(gnssManager);
- }
- gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider());
-
- // bind to geofence proxy
- IGpsGeofenceHardware gpsGeofenceHardware = mGnssManagerService.getGpsGeofenceProxy();
- if (gpsGeofenceHardware != null) {
- GeofenceProxy provider = GeofenceProxy.createAndBind(mContext, gpsGeofenceHardware);
- if (provider == null) {
- Log.e(TAG, "unable to bind to GeofenceProxy");
- }
- }
- }
- }
-
- private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) {
- switch (change) {
- case UserListener.CURRENT_USER_CHANGED:
- synchronized (mLock) {
- for (LocationProviderManager manager : mProviderManagers) {
- manager.onEnabledChangedLocked(userId);
- }
- }
- break;
- case UserListener.USER_STARTED:
- synchronized (mLock) {
- for (LocationProviderManager manager : mProviderManagers) {
- manager.onUserStarted(userId);
- }
- }
- break;
- case UserListener.USER_STOPPED:
- synchronized (mLock) {
- for (LocationProviderManager manager : mProviderManagers) {
- manager.onUserStopped(userId);
- }
- }
- break;
- }
- }
-
- /**
- * Location provider manager, manages a LocationProvider.
- */
- class LocationProviderManager implements MockableLocationProvider.Listener {
-
- private final String mName;
-
- private final LocationFudger mLocationFudger;
-
- // if the provider is enabled for a given user id - null or not present means unknown
- @GuardedBy("mLock")
- private final SparseArray<Boolean> mEnabled;
-
- // last location for a given user
- @GuardedBy("mLock")
- private final SparseArray<Location> mLastLocation;
-
- // last coarse location for a given user
- @GuardedBy("mLock")
- private final SparseArray<Location> mLastCoarseLocation;
-
- // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
- protected final MockableLocationProvider mProvider;
-
- LocationProviderManager(String name) {
- mName = name;
- mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
- mEnabled = new SparseArray<>(2);
- mLastLocation = new SparseArray<>(2);
- mLastCoarseLocation = new SparseArray<>(2);
-
- // initialize last since this lets our reference escape
- mProvider = new MockableLocationProvider(mLock, this);
- }
-
- public String getName() {
- return mName;
- }
-
- public boolean hasProvider() {
- return mProvider.getProvider() != null;
- }
-
- public void setRealProvider(AbstractLocationProvider provider) {
- mProvider.setRealProvider(provider);
- }
-
- public void setMockProvider(@Nullable MockProvider provider) {
- synchronized (mLock) {
- mProvider.setMockProvider(provider);
-
- // when removing a mock provider, also clear any mock last locations and reset the
- // location fudger. the mock provider could have been used to infer the current
- // location fudger offsets.
- if (provider == null) {
- for (int i = 0; i < mLastLocation.size(); i++) {
- Location lastLocation = mLastLocation.valueAt(i);
- if (lastLocation != null && lastLocation.isFromMockProvider()) {
- mLastLocation.setValueAt(i, null);
- }
- }
-
- for (int i = 0; i < mLastCoarseLocation.size(); i++) {
- Location lastCoarseLocation = mLastCoarseLocation.valueAt(i);
- if (lastCoarseLocation != null && lastCoarseLocation.isFromMockProvider()) {
- mLastCoarseLocation.setValueAt(i, null);
- }
- }
-
- mLocationFudger.resetOffsets();
- }
- }
- }
-
- @Nullable
- public CallerIdentity getProviderIdentity() {
- return mProvider.getState().identity;
- }
-
- @Nullable
- public ProviderProperties getProperties() {
- return mProvider.getState().properties;
- }
-
- @Nullable
- public Location getLastLocation(int userId, @PermissionLevel int permissionlevel) {
- synchronized (mLock) {
- switch (permissionlevel) {
- case PERMISSION_COARSE:
- return mLastCoarseLocation.get(userId);
- case PERMISSION_FINE:
- return mLastLocation.get(userId);
- default:
- throw new AssertionError();
- }
- }
- }
-
- public void injectLastLocation(Location location, int userId) {
- synchronized (mLock) {
- if (mLastLocation.get(userId) == null) {
- setLastLocation(location, userId);
- }
- }
- }
-
- private void setLastLocation(Location location, int userId) {
- synchronized (mLock) {
- mLastLocation.put(userId, location);
-
- // update last coarse interval only if enough time has passed
- long timeDeltaMs = Long.MAX_VALUE;
- Location coarseLocation = mLastCoarseLocation.get(userId);
- if (coarseLocation != null) {
- timeDeltaMs = NANOSECONDS.toMillis(location.getElapsedRealtimeNanos())
- - NANOSECONDS.toMillis(coarseLocation.getElapsedRealtimeNanos());
- }
- if (timeDeltaMs > FASTEST_COARSE_INTERVAL_MS) {
- mLastCoarseLocation.put(userId, mLocationFudger.createCoarse(location));
- }
- }
- }
-
- public void setMockProviderAllowed(boolean enabled) {
- synchronized (mLock) {
- if (!mProvider.isMock()) {
- throw new IllegalArgumentException(mName + " provider is not a test provider");
- }
-
- mProvider.setMockProviderAllowed(enabled);
- }
- }
-
- public void setMockProviderLocation(Location location) {
- synchronized (mLock) {
- if (!mProvider.isMock()) {
- throw new IllegalArgumentException(mName + " provider is not a test provider");
- }
-
- String locationProvider = location.getProvider();
- if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) {
- // The location has an explicit provider that is different from the mock
- // provider name. The caller may be trying to fool us via b/33091107.
- EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
- mName + "!=" + locationProvider);
- }
-
- mProvider.setMockProviderLocation(location);
- }
- }
-
- public List<LocationRequest> getMockProviderRequests() {
- synchronized (mLock) {
- if (!mProvider.isMock()) {
- throw new IllegalArgumentException(mName + " provider is not a test provider");
- }
-
- return mProvider.getCurrentRequest().locationRequests;
- }
- }
-
- public void setRequest(ProviderRequest request) {
- mProvider.setRequest(request);
- }
-
- public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
- mProvider.sendExtraCommand(uid, pid, command, extras);
- }
-
- @GuardedBy("mLock")
- @Override
- public void onReportLocation(Location location) {
- // don't validate mock locations
- if (!location.isFromMockProvider()) {
- if (location.getLatitude() == 0 && location.getLongitude() == 0) {
- Log.w(TAG, "blocking 0,0 location from " + mName + " provider");
- return;
- }
- }
-
- if (!location.isComplete()) {
- Log.w(TAG, "blocking incomplete location from " + mName + " provider");
- return;
- }
-
- // update last location if the provider is enabled or if servicing a bypass request
- boolean locationSettingsIgnored = mProvider.getCurrentRequest().locationSettingsIgnored;
- for (int userId : mUserInfoHelper.getRunningUserIds()) {
- if (locationSettingsIgnored || isEnabled(userId)) {
- setLastLocation(location, userId);
- }
- }
-
- handleLocationChangedLocked(this, location, mLocationFudger.createCoarse(location));
- }
-
- @GuardedBy("mLock")
- @Override
- public void onReportLocation(List<Location> locations) {
- if (mGnssManagerService == null || !GPS_PROVIDER.equals(mName)) {
- return;
- }
-
- mGnssManagerService.onReportLocation(locations);
- }
-
- @GuardedBy("mLock")
- @Override
- public void onStateChanged(State oldState, State newState) {
- if (oldState.allowed != newState.allowed) {
- if (D) {
- Log.d(TAG, mName + " provider allowed = " + newState.allowed);
- }
-
- onEnabledChangedLocked(UserHandle.USER_ALL);
- }
- }
-
- public void onUserStarted(int userId) {
- if (userId == UserHandle.USER_NULL) {
- return;
- } else if (userId == UserHandle.USER_ALL) {
- for (int runningUserId : mUserInfoHelper.getRunningUserIds()) {
- onUserStarted(runningUserId);
- }
- return;
- }
-
- Preconditions.checkArgument(userId >= 0);
-
- synchronized (mLock) {
- // clear the user's prior enabled state to prevent broadcast of enabled state
- // change. user starts should never result in a broadcast since the state has
- // technically not changed.
- mEnabled.put(userId, null);
- onEnabledChangedLocked(userId);
- }
- }
-
- public void onUserStopped(int userId) {
- if (userId == UserHandle.USER_NULL) {
- return;
- } else if (userId == UserHandle.USER_ALL) {
- mEnabled.clear();
- mLastLocation.clear();
- mLastCoarseLocation.clear();
- return;
- }
-
- Preconditions.checkArgument(userId >= 0);
-
- synchronized (mLock) {
- mEnabled.remove(userId);
- mLastLocation.remove(userId);
- mLastCoarseLocation.remove(userId);
- }
- }
-
- public boolean isEnabled(int userId) {
- if (userId == UserHandle.USER_NULL) {
- // used during initialization - ignore since many lower level operations (checking
- // settings for instance) do not support the null user
- return false;
- }
-
- Preconditions.checkArgument(userId >= 0);
-
- synchronized (mLock) {
- Boolean enabled = mEnabled.get(userId);
- if (enabled == null) {
- // this generally shouldn't occur, but might be possible due to race conditions
- // on when we are notified of new users
- Log.w(TAG, mName + " provider saw user " + userId + " unexpectedly");
- onEnabledChangedLocked(userId);
- enabled = Objects.requireNonNull(mEnabled.get(userId));
- }
-
- return enabled;
- }
- }
-
- @GuardedBy("mLock")
- public void onEnabledChangedLocked(int userId) {
- if (userId == UserHandle.USER_NULL) {
- // used during initialization - ignore since many lower level operations (checking
- // settings for instance) do not support the null user
- return;
- } else if (userId == UserHandle.USER_ALL) {
- for (int runningUserId : mUserInfoHelper.getRunningUserIds()) {
- onEnabledChangedLocked(runningUserId);
- }
- return;
- }
-
- Preconditions.checkArgument(userId >= 0);
-
- // if any property that contributes to "enabled" here changes state, it MUST result
- // in a direct or indrect call to onEnabledChangedLocked. this allows the provider to
- // guarantee that it will always eventually reach the correct state.
- boolean enabled = mProvider.getState().allowed
- && mUserInfoHelper.isCurrentUserId(userId)
- && mSettingsHelper.isLocationEnabled(userId);
-
- Boolean wasEnabled = mEnabled.get(userId);
- if (wasEnabled != null && wasEnabled == enabled) {
- return;
- }
-
- mEnabled.put(userId, enabled);
-
- if (D) {
- Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
- }
-
- // clear last locations if we become disabled and if not servicing a bypass request
- if (!enabled && !mProvider.getCurrentRequest().locationSettingsIgnored) {
- mLastLocation.put(userId, null);
- mLastCoarseLocation.put(userId, null);
- }
-
- // do not send change notifications if we just saw this user for the first time
- if (wasEnabled != null) {
- // fused and passive provider never get public updates for legacy reasons
- if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) {
- Intent intent = new Intent(PROVIDERS_CHANGED_ACTION)
- .putExtra(EXTRA_PROVIDER_NAME, mName)
- .putExtra(EXTRA_PROVIDER_ENABLED, enabled)
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
- }
- }
-
- updateProviderEnabledLocked(this, enabled);
- }
-
- public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
- synchronized (mLock) {
- pw.print(mName + " provider");
- if (mProvider.isMock()) {
- pw.print(" [mock]");
- }
- pw.println(":");
-
- pw.increaseIndent();
-
- int[] userIds = mUserInfoHelper.getRunningUserIds();
- for (int userId : userIds) {
- if (userIds.length != 1) {
- pw.println("user " + userId + ":");
- pw.increaseIndent();
- }
- pw.println("last location=" + mLastLocation.get(userId));
- pw.println("last coarse location=" + mLastCoarseLocation.get(userId));
- pw.println("enabled=" + isEnabled(userId));
- if (userIds.length != 1) {
- pw.decreaseIndent();
- }
- }
- }
-
- mProvider.dump(fd, pw, args);
-
- pw.decreaseIndent();
- }
- }
-
- class PassiveLocationProviderManager extends LocationProviderManager {
-
- private PassiveLocationProviderManager() {
- super(PASSIVE_PROVIDER);
- }
-
- @Override
- public void setRealProvider(AbstractLocationProvider provider) {
- Preconditions.checkArgument(provider instanceof PassiveProvider);
- super.setRealProvider(provider);
- }
-
- @Override
- public void setMockProvider(@Nullable MockProvider provider) {
- if (provider != null) {
- throw new IllegalArgumentException("Cannot mock the passive provider");
- }
- }
-
- public void updateLocation(Location location) {
- synchronized (mLock) {
- PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider();
- Preconditions.checkState(passiveProvider != null);
-
- long identity = Binder.clearCallingIdentity();
- try {
- passiveProvider.updateLocation(location);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
- }
-
- /**
- * A wrapper class holding either an ILocationListener or a PendingIntent to receive
- * location updates.
- */
- private final class Receiver extends LocationManagerServiceUtils.LinkedListenerBase implements
- PendingIntent.OnFinished {
- private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
-
- private final ILocationListener mListener;
- final PendingIntent mPendingIntent;
- final WorkSource mWorkSource; // WorkSource for battery blame, or null to assign to caller.
- private final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
- private final Object mKey;
-
- final HashMap<String, UpdateRecord> mUpdateRecords = new HashMap<>();
-
- // True if app ops has started monitoring this receiver for locations.
- private boolean mOpMonitoring;
- // True if app ops has started monitoring this receiver for high power (gps) locations.
- private boolean mOpHighPowerMonitoring;
- private int mPendingBroadcasts;
- PowerManager.WakeLock mWakeLock;
-
- private Receiver(ILocationListener listener, PendingIntent intent, CallerIdentity identity,
- WorkSource workSource, boolean hideFromAppOps) {
- super(identity);
- mListener = listener;
- mPendingIntent = intent;
- if (listener != null) {
- mKey = listener.asBinder();
- } else {
- mKey = intent;
- }
- if (workSource != null && workSource.isEmpty()) {
- workSource = null;
- }
- mWorkSource = workSource;
- mHideFromAppOps = hideFromAppOps;
-
- updateMonitoring(true);
-
- // construct/configure wakelock
- mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
- if (workSource == null) {
- workSource = mCallerIdentity.addToWorkSource(null);
- }
- mWakeLock.setWorkSource(workSource);
-
- // For a non-reference counted wakelock, each acquire will reset the timeout, and we
- // only need to release it once.
- mWakeLock.setReferenceCounted(false);
- }
-
- @Override
- public boolean equals(Object otherObj) {
- return (otherObj instanceof Receiver) && mKey.equals(((Receiver) otherObj).mKey);
- }
-
- @Override
- public int hashCode() {
- return mKey.hashCode();
- }
-
- @Override
- public String toString() {
- StringBuilder s = new StringBuilder();
- s.append("Reciever[");
- s.append(Integer.toHexString(System.identityHashCode(this)));
- if (mListener != null) {
- s.append(" listener");
- } else {
- s.append(" intent");
- }
- for (String p : mUpdateRecords.keySet()) {
- s.append(" ").append(mUpdateRecords.get(p).toString());
- }
- s.append(" monitoring location: ").append(mOpMonitoring);
- s.append("]");
- return s.toString();
- }
-
- /**
- * Update AppOp monitoring for this receiver.
- *
- * @param allow If true receiver is currently active, if false it's been removed.
- */
- public void updateMonitoring(boolean allow) {
- if (mHideFromAppOps) {
- return;
- }
-
- boolean requestingLocation = false;
- boolean requestingHighPowerLocation = false;
- if (allow) {
- // See if receiver has any enabled update records. Also note if any update records
- // are high power (has a high power provider with an interval under a threshold).
- for (UpdateRecord updateRecord : mUpdateRecords.values()) {
- LocationProviderManager manager = getLocationProviderManager(
- updateRecord.mProvider);
- if (manager == null) {
- continue;
- }
- if (!manager.isEnabled(UserHandle.getUserId(mCallerIdentity.getUid()))
- && !isSettingsExempt(updateRecord)) {
- continue;
- }
-
- requestingLocation = true;
- ProviderProperties properties = manager.getProperties();
- if (properties != null
- && properties.mPowerRequirement == Criteria.POWER_HIGH
- && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
- requestingHighPowerLocation = true;
- break;
- }
- }
- }
-
- // First update monitoring of any location request (including high power).
- mOpMonitoring = updateMonitoring(
- requestingLocation,
- mOpMonitoring,
- false);
-
- // Now update monitoring of high power requests only.
- mOpHighPowerMonitoring = updateMonitoring(
- requestingHighPowerLocation,
- mOpHighPowerMonitoring,
- true);
- }
-
- private boolean updateMonitoring(boolean allowMonitoring, boolean currentlyMonitoring,
- boolean highPower) {
- if (!currentlyMonitoring) {
- if (allowMonitoring) {
- if (!highPower) {
- return mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, mCallerIdentity);
- } else {
- return mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
- mCallerIdentity);
- }
- }
- } else {
- int permissionLevel = LocationPermissions.getPermissionLevel(mContext,
- mCallerIdentity.getUid(), mCallerIdentity.getPid());
- if (!allowMonitoring || permissionLevel == PERMISSION_NONE
- || !mAppOpsHelper.checkOpNoThrow(
- LocationPermissions.asAppOp(permissionLevel), mCallerIdentity)) {
- if (!highPower) {
- mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, mCallerIdentity);
- } else {
- mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, mCallerIdentity);
- }
- return false;
- }
- }
-
- return currentlyMonitoring;
- }
-
- public boolean isListener() {
- return mListener != null;
- }
-
- public boolean isPendingIntent() {
- return mPendingIntent != null;
- }
-
- public ILocationListener getListener() {
- if (mListener != null) {
- return mListener;
- }
- throw new IllegalStateException("Request for non-existent listener");
- }
-
- public boolean callLocationChangedLocked(Location location,
- LocationRequest locationRequest) {
- if (mListener != null) {
- try {
- mListener.onLocationChanged(new Location(location), new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) {
- synchronized (mLock) {
- decrementPendingBroadcastsLocked();
- }
- }
- });
- // call this after broadcasting so we do not increment
- // if we throw an exception.
- incrementPendingBroadcastsLocked();
- } catch (RemoteException e) {
- return false;
- }
- } else {
- Intent locationChanged = new Intent();
- locationChanged.putExtra(KEY_LOCATION_CHANGED, new Location(location));
- try {
- mPendingIntent.send(mContext, 0, locationChanged, this, mHandler,
- LocationPermissions.asPermission(
- locationRequest.isCoarse() ? PERMISSION_COARSE
- : PERMISSION_FINE),
- PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
- // call this after broadcasting so we do not increment
- // if we throw an exception.
- incrementPendingBroadcastsLocked();
- } catch (PendingIntent.CanceledException e) {
- return false;
- }
- }
- return true;
- }
-
- private boolean callProviderEnabledLocked(String provider, boolean enabled,
- LocationRequest locationRequest) {
- // First update AppOp monitoring.
- // An app may get/lose location access as providers are enabled/disabled.
- updateMonitoring(true);
-
- if (mListener != null) {
- try {
- mListener.onProviderEnabledChanged(provider, enabled);
- } catch (RemoteException e) {
- return false;
- }
- } else {
- Intent providerIntent = new Intent();
- providerIntent.putExtra(KEY_PROVIDER_ENABLED, enabled);
- try {
- mPendingIntent.send(mContext, 0, providerIntent, null, mHandler,
- LocationPermissions.asPermission(
- locationRequest.isCoarse() ? PERMISSION_COARSE
- : PERMISSION_FINE),
- PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
- } catch (PendingIntent.CanceledException e) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public void binderDied() {
- synchronized (mLock) {
- removeUpdatesLocked(this);
- clearPendingBroadcastsLocked();
- }
- }
-
- @Override
- public void onSendFinished(PendingIntent pendingIntent, Intent intent,
- int resultCode, String resultData, Bundle resultExtras) {
- synchronized (mLock) {
- decrementPendingBroadcastsLocked();
- }
- }
-
- // this must be called while synchronized by caller in a synchronized block
- // containing the sending of the broadcaset
- private void incrementPendingBroadcastsLocked() {
- mPendingBroadcasts++;
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- try {
- mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
+ private void onLocationModeChanged(int userId) {
+ boolean enabled = mInjector.getSettingsHelper().isLocationEnabled(userId);
+ LocationManager.invalidateLocalLocationEnabledCaches();
- private void decrementPendingBroadcastsLocked() {
- if (--mPendingBroadcasts == 0) {
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- try {
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
+ if (D) {
+ Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
}
- public void clearPendingBroadcastsLocked() {
- if (mPendingBroadcasts > 0) {
- mPendingBroadcasts = 0;
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- try {
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
+ Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
+ .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
}
@Override
@@ -1459,17 +478,6 @@ public class LocationManagerService extends ILocationManager.Stub {
}
}
- @Nullable
- LocationProviderManager getLocationProviderManager(String providerName) {
- for (LocationProviderManager manager : mProviderManagers) {
- if (providerName.equals(manager.getName())) {
- return manager;
- }
- }
-
- return null;
- }
-
@Override
public List<String> getAllProviders() {
ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
@@ -1532,397 +540,16 @@ public class LocationManagerService extends ILocationManager.Stub {
return null;
}
- @GuardedBy("mLock")
- private void updateProviderEnabledLocked(LocationProviderManager manager, boolean enabled) {
- ArrayList<Receiver> deadReceivers = null;
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
- if (records != null) {
- for (UpdateRecord record : records) {
- if (!mUserInfoHelper.isCurrentUserId(
- UserHandle.getUserId(record.mReceiver.mCallerIdentity.getUid()))) {
- continue;
- }
-
- // requests that ignore location settings will never provide notifications
- if (isSettingsExempt(record)) {
- continue;
- }
-
- // Sends a notification message to the receiver
- if (!record.mReceiver.callProviderEnabledLocked(manager.getName(), enabled,
- record.mRequest)) {
- if (deadReceivers == null) {
- deadReceivers = new ArrayList<>();
- }
- deadReceivers.add(record.mReceiver);
- }
- }
- }
-
- if (deadReceivers != null) {
- for (int i = deadReceivers.size() - 1; i >= 0; i--) {
- removeUpdatesLocked(deadReceivers.get(i));
- }
- }
-
- applyRequirementsLocked(manager);
- }
-
- @GuardedBy("mLock")
- private void applyRequirementsLocked(String providerName) {
- LocationProviderManager manager = getLocationProviderManager(providerName);
- if (manager != null) {
- applyRequirementsLocked(manager);
- }
- }
-
- @GuardedBy("mLock")
- private void applyRequirementsLocked(LocationProviderManager manager) {
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
- ProviderRequest.Builder providerRequest = new ProviderRequest.Builder();
-
- // if provider is not active, it should not respond to requests
-
- if (mProviderManagers.contains(manager) && records != null && !records.isEmpty()) {
- long backgroundThrottleInterval = mSettingsHelper.getBackgroundThrottleIntervalMs();
-
- ArrayList<LocationRequest> requests = new ArrayList<>(records.size());
-
- final boolean isForegroundOnlyMode =
- mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
- final boolean shouldThrottleRequests =
- mBatterySaverMode
- == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF
- && !mPowerManager.isInteractive();
- // initialize the low power mode to true and set to false if any of the records requires
- providerRequest.setLowPowerMode(true);
- for (UpdateRecord record : records) {
- CallerIdentity identity = record.mReceiver.mCallerIdentity;
- if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
- continue;
- }
-
- if (!mAppOpsHelper.checkOpNoThrow(LocationPermissions.asAppOp(
- record.mRequest.isCoarse() ? PERMISSION_COARSE : PERMISSION_FINE),
- identity)) {
- continue;
- }
- final boolean isBatterySaverDisablingLocation = shouldThrottleRequests
- || (isForegroundOnlyMode && !record.mIsForegroundUid);
- if (!manager.isEnabled(identity.getUserId()) || isBatterySaverDisablingLocation) {
- if (isSettingsExempt(record)) {
- providerRequest.setLocationSettingsIgnored(true);
- providerRequest.setLowPowerMode(false);
- } else {
- continue;
- }
- }
-
- LocationRequest locationRequest = record.mRealRequest;
- long interval = locationRequest.getInterval();
-
-
- // if we're forcing location, don't apply any throttling
- if (!providerRequest.isLocationSettingsIgnored() && !isThrottlingExempt(
- record.mReceiver.mCallerIdentity)) {
- if (!record.mIsForegroundUid) {
- interval = Math.max(interval, backgroundThrottleInterval);
- }
- if (interval != locationRequest.getInterval()) {
- locationRequest = new LocationRequest(locationRequest);
- locationRequest.setInterval(interval);
- }
- }
-
- record.mRequest = locationRequest;
- requests.add(locationRequest);
- if (!locationRequest.isLowPowerMode()) {
- providerRequest.setLowPowerMode(false);
- }
- if (interval < providerRequest.getInterval()) {
- providerRequest.setInterval(interval);
- }
- }
-
- providerRequest.setLocationRequests(requests);
-
- if (providerRequest.getInterval() < Long.MAX_VALUE) {
- // calculate who to blame for power
- // This is somewhat arbitrary. We pick a threshold interval
- // that is slightly higher that the minimum interval, and
- // spread the blame across all applications with a request
- // under that threshold.
- // TODO: overflow
- long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2;
- for (UpdateRecord record : records) {
- if (mUserInfoHelper.isCurrentUserId(
- UserHandle.getUserId(record.mReceiver.mCallerIdentity.getUid()))) {
- LocationRequest locationRequest = record.mRequest;
-
- // Don't assign battery blame for update records whose
- // client has no permission to receive location data.
- if (!providerRequest.getLocationRequests().contains(locationRequest)) {
- continue;
- }
-
- if (locationRequest.getInterval() <= thresholdInterval) {
- if (record.mReceiver.mWorkSource != null
- && isValidWorkSource(record.mReceiver.mWorkSource)) {
- providerRequest.getWorkSource().add(record.mReceiver.mWorkSource);
- } else {
- // Assign blame to caller if there's no WorkSource associated with
- // the request or if it's invalid.
- providerRequest.getWorkSource().add(
- record.mReceiver.mCallerIdentity.getUid(),
- record.mReceiver.mCallerIdentity.getPackageName());
- }
- }
- }
- }
- }
- }
-
- manager.setRequest(providerRequest.build());
- }
-
- /**
- * Whether a given {@code WorkSource} associated with a Location request is valid.
- */
- private static boolean isValidWorkSource(WorkSource workSource) {
- if (workSource.size() > 0) {
- // If the WorkSource has one or more non-chained UIDs, make sure they're accompanied
- // by tags.
- return workSource.getPackageName(0) != null;
- } else {
- // For now, make sure callers have supplied an attribution tag for use with
- // AppOpsManager. This might be relaxed in the future.
- final List<WorkChain> workChains = workSource.getWorkChains();
- return workChains != null && !workChains.isEmpty()
- && workChains.get(0).getAttributionTag() != null;
- }
- }
-
@Override
public String[] getBackgroundThrottlingWhitelist() {
- return mSettingsHelper.getBackgroundThrottlePackageWhitelist().toArray(new String[0]);
+ return mInjector.getSettingsHelper().getBackgroundThrottlePackageWhitelist().toArray(
+ new String[0]);
}
@Override
public String[] getIgnoreSettingsWhitelist() {
- return mSettingsHelper.getIgnoreSettingsPackageWhitelist().toArray(new String[0]);
- }
-
- private boolean isThrottlingExempt(CallerIdentity callerIdentity) {
- if (callerIdentity.getUid() == Process.SYSTEM_UID) {
- return true;
- }
-
- if (mSettingsHelper.getBackgroundThrottlePackageWhitelist().contains(
- callerIdentity.getPackageName())) {
- return true;
- }
-
- return mLocalService.isProvider(null, callerIdentity);
-
- }
-
- private boolean isSettingsExempt(UpdateRecord record) {
- if (!record.mRealRequest.isLocationSettingsIgnored()) {
- return false;
- }
-
- if (mSettingsHelper.getIgnoreSettingsPackageWhitelist().contains(
- record.mReceiver.mCallerIdentity.getPackageName())) {
- return true;
- }
-
- return mLocalService.isProvider(null, record.mReceiver.mCallerIdentity);
- }
-
- private class UpdateRecord {
- final String mProvider;
- private final LocationRequest mRealRequest; // original request from client
- LocationRequest mRequest; // possibly throttled version of the request
- private final Receiver mReceiver;
- private boolean mIsForegroundUid;
- private Location mLastFixBroadcast;
- private Throwable mStackTrace; // for debugging only
- private long mExpirationRealtimeMs;
-
- /**
- * Note: must be constructed with lock held.
- */
- private UpdateRecord(String provider, LocationRequest request, Receiver receiver) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
- mExpirationRealtimeMs = request.getExpirationRealtimeMs(SystemClock.elapsedRealtime());
- mProvider = provider;
- mRealRequest = request;
- mRequest = request;
- mReceiver = receiver;
- mIsForegroundUid = mAppForegroundHelper.isAppForeground(
- mReceiver.mCallerIdentity.getUid());
-
- if (D && receiver.mCallerIdentity.getPid() == Process.myPid()) {
- mStackTrace = new Throwable();
- }
-
- ArrayList<UpdateRecord> records = mRecordsByProvider.computeIfAbsent(provider,
- k -> new ArrayList<>());
- if (!records.contains(this)) {
- records.add(this);
- }
-
- // Update statistics for historical location requests by package/provider
- mRequestStatistics.startRequesting(
- mReceiver.mCallerIdentity.getPackageName(),
- mReceiver.mCallerIdentity.getAttributionTag(),
- provider, request.getInterval(), mIsForegroundUid);
- }
-
- /**
- * Method to be called when record changes foreground/background
- */
- private void updateForeground(boolean isForeground) {
- mIsForegroundUid = isForeground;
- mRequestStatistics.updateForeground(
- mReceiver.mCallerIdentity.getPackageName(),
- mReceiver.mCallerIdentity.getAttributionTag(),
- mProvider, isForeground);
- }
-
- /**
- * Method to be called when a record will no longer be used.
- */
- private void disposeLocked(boolean removeReceiver) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- CallerIdentity identity = mReceiver.mCallerIdentity;
- mRequestStatistics.stopRequesting(identity.getPackageName(),
- identity.getAttributionTag(),
- mProvider);
-
- mLocationUsageLogger.logLocationApiUsage(
- LocationStatsEnums.USAGE_ENDED,
- LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
- identity.getPackageName(),
- mRealRequest,
- mReceiver.isListener(),
- mReceiver.isPendingIntent(),
- /* geofence= */ null,
- mAppForegroundHelper.isAppForeground(mReceiver.mCallerIdentity.getUid()));
-
- // remove from mRecordsByProvider
- ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider);
- if (globalRecords != null) {
- globalRecords.remove(this);
- }
-
- if (!removeReceiver) return; // the caller will handle the rest
-
- // remove from Receiver#mUpdateRecords
- HashMap<String, UpdateRecord> receiverRecords = mReceiver.mUpdateRecords;
- receiverRecords.remove(this.mProvider);
-
- // and also remove the Receiver if it has no more update records
- if (receiverRecords.size() == 0) {
- removeUpdatesLocked(mReceiver);
- }
- }
-
- @Override
- public String toString() {
- StringBuilder b = new StringBuilder("UpdateRecord[");
- b.append(mProvider).append(" ");
- b.append(mReceiver.mCallerIdentity).append(" ");
- if (!mIsForegroundUid) {
- b.append("(background) ");
- }
- b.append(mRealRequest).append(" ").append(mReceiver.mWorkSource);
-
- if (mStackTrace != null) {
- ByteArrayOutputStream tmp = new ByteArrayOutputStream();
- mStackTrace.printStackTrace(new PrintStream(tmp));
- b.append("\n\n").append(tmp.toString()).append("\n");
- }
-
- b.append("]");
- return b.toString();
- }
- }
-
- @GuardedBy("mLock")
- private Receiver getReceiverLocked(ILocationListener listener, CallerIdentity identity,
- WorkSource workSource, boolean hideFromAppOps) {
- IBinder binder = listener.asBinder();
- Receiver receiver = mReceivers.get(binder);
- if (receiver == null && identity != null) {
- receiver = new Receiver(listener, null, identity, workSource,
- hideFromAppOps);
- if (!receiver.linkToListenerDeathNotificationLocked(
- receiver.getListener().asBinder())) {
- return null;
- }
- mReceivers.put(binder, receiver);
- }
- return receiver;
- }
-
- @GuardedBy("mLock")
- private Receiver getReceiverLocked(PendingIntent intent, CallerIdentity identity,
- WorkSource workSource, boolean hideFromAppOps) {
- Receiver receiver = mReceivers.get(intent);
- if (receiver == null && identity != null) {
- receiver = new Receiver(null, intent, identity, workSource,
- hideFromAppOps);
- mReceivers.put(intent, receiver);
- }
- return receiver;
- }
-
- /**
- * Creates a LocationRequest based upon the supplied LocationRequest that to meets resolution
- * and consistency requirements.
- *
- * @param request the LocationRequest from which to create a sanitized version
- * @return a version of request that meets the given resolution and consistency requirements
- * @hide
- */
- private LocationRequest createSanitizedRequest(LocationRequest request,
- boolean callerHasLocationHardwarePermission, int permissionLevel) {
- LocationRequest sanitizedRequest = new LocationRequest(request);
- if (!callerHasLocationHardwarePermission) {
- // allow setting low power mode only for callers with location hardware permission
- sanitizedRequest.setLowPowerMode(false);
- }
- if (permissionLevel < PERMISSION_FINE) {
- sanitizedRequest.setCoarse(true);
- switch (sanitizedRequest.getQuality()) {
- case LocationRequest.ACCURACY_FINE:
- sanitizedRequest.setQuality(LocationRequest.ACCURACY_BLOCK);
- break;
- case LocationRequest.POWER_HIGH:
- sanitizedRequest.setQuality(LocationRequest.POWER_LOW);
- break;
- }
- // throttle
- if (sanitizedRequest.getInterval() < FASTEST_COARSE_INTERVAL_MS) {
- sanitizedRequest.setInterval(FASTEST_COARSE_INTERVAL_MS);
- }
- if (sanitizedRequest.getFastestInterval() < FASTEST_COARSE_INTERVAL_MS) {
- sanitizedRequest.setFastestInterval(FASTEST_COARSE_INTERVAL_MS);
- }
- } else {
- sanitizedRequest.setCoarse(false);
- }
- // make getFastestInterval() the minimum of interval and fastest interval
- if (sanitizedRequest.getFastestInterval() > sanitizedRequest.getInterval()) {
- sanitizedRequest.setFastestInterval(request.getInterval());
- }
- return sanitizedRequest;
+ return mInjector.getSettingsHelper().getIgnoreSettingsPackageWhitelist().toArray(
+ new String[0]);
}
@Override
@@ -1930,45 +557,24 @@ public class LocationManagerService extends ILocationManager.Stub {
String packageName, String attributionTag, String listenerId) {
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
listenerId);
- int permissionLevel = LocationPermissions.getCallingOrSelfPermissionLevel(mContext);
- LocationPermissions.enforceLocationPermission(Binder.getCallingUid(), permissionLevel,
+ int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
+ identity.getPid());
+ LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
PERMISSION_COARSE);
- WorkSource workSource = request.getWorkSource();
- if (workSource != null && !workSource.isEmpty()) {
- mContext.enforceCallingOrSelfPermission(
- permission.UPDATE_DEVICE_STATS, null);
+ // clients in the system process should have an attribution tag set
+ if (identity.getPid() == Process.myPid() && attributionTag == null) {
+ Log.w(TAG, "system location request with no attribution tag",
+ new IllegalArgumentException());
}
- boolean hideFromAppOps = request.getHideFromAppOps();
- if (hideFromAppOps) {
- mContext.enforceCallingOrSelfPermission(
- permission.UPDATE_APP_OPS_STATS, null);
- }
- if (request.isLocationSettingsIgnored()) {
- mContext.enforceCallingOrSelfPermission(
- permission.WRITE_SECURE_SETTINGS, null);
- }
- boolean callerHasLocationHardwarePermission =
- mContext.checkCallingPermission(permission.LOCATION_HARDWARE)
- == PERMISSION_GRANTED;
- LocationRequest sanitizedRequest = createSanitizedRequest(request,
- callerHasLocationHardwarePermission,
- permissionLevel);
- mLocationUsageLogger.logLocationApiUsage(
- LocationStatsEnums.USAGE_STARTED,
- LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
- packageName, request, true, false,
- /* geofence= */ null,
- mAppForegroundHelper.isAppForeground(identity.getUid()));
+ request = validateAndSanitizeLocationRequest(request, permissionLevel);
- synchronized (mLock) {
- Receiver receiver = getReceiverLocked(Objects.requireNonNull(listener), identity,
- workSource, hideFromAppOps);
- if (receiver != null) {
- requestLocationUpdatesLocked(sanitizedRequest, receiver);
- }
- }
+ LocationProviderManager manager = getLocationProviderManager(request.getProvider());
+ Preconditions.checkArgument(manager != null,
+ "provider \"" + request.getProvider() + "\" does not exist");
+
+ manager.registerLocationRequest(request, identity, permissionLevel, listener);
}
@Override
@@ -1976,248 +582,154 @@ public class LocationManagerService extends ILocationManager.Stub {
String packageName, String attributionTag) {
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
AppOpsManager.toReceiverId(pendingIntent));
- int permissionLevel = LocationPermissions.getCallingOrSelfPermissionLevel(mContext);
- LocationPermissions.enforceLocationPermission(Binder.getCallingUid(), permissionLevel,
+ int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
+ identity.getPid());
+ LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
PERMISSION_COARSE);
+ // clients in the system process must have an attribution tag set
+ Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null);
+
+ request = validateAndSanitizeLocationRequest(request, permissionLevel);
+
+ LocationProviderManager manager = getLocationProviderManager(request.getProvider());
+ Preconditions.checkArgument(manager != null,
+ "provider \"" + request.getProvider() + "\" does not exist");
+
+ manager.registerLocationRequest(request, identity, permissionLevel, pendingIntent);
+ }
+
+ private LocationRequest validateAndSanitizeLocationRequest(LocationRequest request,
+ @PermissionLevel int permissionLevel) {
+ Objects.requireNonNull(request.getProvider());
+
WorkSource workSource = request.getWorkSource();
if (workSource != null && !workSource.isEmpty()) {
mContext.enforceCallingOrSelfPermission(
- permission.UPDATE_DEVICE_STATS, null);
+ permission.UPDATE_DEVICE_STATS,
+ "setting a work source requires " + permission.UPDATE_DEVICE_STATS);
}
- boolean hideFromAppOps = request.getHideFromAppOps();
- if (hideFromAppOps) {
+ if (request.getHideFromAppOps()) {
mContext.enforceCallingOrSelfPermission(
- permission.UPDATE_APP_OPS_STATS, null);
+ permission.UPDATE_APP_OPS_STATS,
+ "hiding from app ops requires " + permission.UPDATE_APP_OPS_STATS);
}
if (request.isLocationSettingsIgnored()) {
mContext.enforceCallingOrSelfPermission(
- permission.WRITE_SECURE_SETTINGS, null);
+ permission.WRITE_SECURE_SETTINGS,
+ "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
}
- boolean callerHasLocationHardwarePermission =
- mContext.checkCallingPermission(permission.LOCATION_HARDWARE)
- == PERMISSION_GRANTED;
- LocationRequest sanitizedRequest = createSanitizedRequest(request,
- callerHasLocationHardwarePermission,
- permissionLevel);
-
- mLocationUsageLogger.logLocationApiUsage(
- LocationStatsEnums.USAGE_STARTED,
- LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
- packageName, request, true, false,
- /* geofence= */ null,
- mAppForegroundHelper.isAppForeground(identity.getUid()));
- synchronized (mLock) {
- Receiver receiver = getReceiverLocked(Objects.requireNonNull(pendingIntent), identity,
- workSource, hideFromAppOps);
- if (receiver != null) {
- requestLocationUpdatesLocked(sanitizedRequest, receiver);
- }
+ LocationRequest sanitized = new LocationRequest(request);
+ if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE) != PERMISSION_GRANTED) {
+ sanitized.setLowPowerMode(false);
}
- }
-
- @GuardedBy("mLock")
- private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver) {
- String provider = request.getProvider();
+ if (permissionLevel < PERMISSION_FINE) {
+ switch (sanitized.getQuality()) {
+ case LocationRequest.ACCURACY_FINE:
+ sanitized.setQuality(LocationRequest.ACCURACY_BLOCK);
+ break;
+ case LocationRequest.POWER_HIGH:
+ sanitized.setQuality(LocationRequest.POWER_LOW);
+ break;
+ }
- LocationProviderManager manager = getLocationProviderManager(provider);
- if (manager == null) {
- throw new IllegalArgumentException("provider doesn't exist: " + provider);
+ if (sanitized.getInterval() < FASTEST_COARSE_INTERVAL_MS) {
+ sanitized.setInterval(FASTEST_COARSE_INTERVAL_MS);
+ }
+ if (sanitized.getFastestInterval() < FASTEST_COARSE_INTERVAL_MS) {
+ sanitized.setFastestInterval(FASTEST_COARSE_INTERVAL_MS);
+ }
}
-
- UpdateRecord record = new UpdateRecord(provider, request, receiver);
-
- UpdateRecord oldRecord = receiver.mUpdateRecords.put(provider, record);
- if (oldRecord != null) {
- oldRecord.disposeLocked(false);
+ if (sanitized.getFastestInterval() > sanitized.getInterval()) {
+ sanitized.setFastestInterval(request.getInterval());
}
-
- long identity = Binder.clearCallingIdentity();
- try {
- int userId = UserHandle.getUserId(receiver.mCallerIdentity.getUid());
- if (!manager.isEnabled(userId) && !isSettingsExempt(record)) {
- // Notify the listener that updates are currently disabled - but only if the request
- // does not ignore location settings
- receiver.callProviderEnabledLocked(provider, false, request);
+ if (sanitized.getWorkSource() != null) {
+ if (sanitized.getWorkSource().isEmpty()) {
+ sanitized.setWorkSource(null);
+ } else if (sanitized.getWorkSource().getPackageName(0) == null) {
+ Log.w(TAG, "received (and ignoring) illegal worksource with no package name");
+ sanitized.setWorkSource(null);
+ } else {
+ List<WorkChain> workChains = sanitized.getWorkSource().getWorkChains();
+ if (workChains != null && !workChains.isEmpty() && workChains.get(
+ 0).getAttributionTag() == null) {
+ Log.w(TAG,
+ "received (and ignoring) illegal worksource with no attribution tag");
+ sanitized.setWorkSource(null);
+ }
}
-
- applyRequirementsLocked(provider);
-
- // Update the monitoring here just in case multiple location requests were added to the
- // same receiver (this request may be high power and the initial might not have been).
- receiver.updateMonitoring(true);
- } finally {
- Binder.restoreCallingIdentity(identity);
}
+
+ return sanitized;
}
@Override
public void unregisterLocationListener(ILocationListener listener) {
- synchronized (mLock) {
- Receiver receiver = getReceiverLocked(Objects.requireNonNull(listener), null, null,
- false);
- if (receiver != null) {
- removeUpdatesLocked(receiver);
- }
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.unregisterLocationRequest(listener);
}
}
@Override
public void unregisterLocationPendingIntent(PendingIntent pendingIntent) {
- synchronized (mLock) {
- Receiver receiver = getReceiverLocked(Objects.requireNonNull(pendingIntent), null, null,
- false);
- if (receiver != null) {
- removeUpdatesLocked(receiver);
- }
- }
- }
-
- @GuardedBy("mLock")
- private void removeUpdatesLocked(Receiver receiver) {
- if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver)));
-
- if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) {
- receiver.unlinkFromListenerDeathNotificationLocked(
- receiver.getListener().asBinder());
- receiver.clearPendingBroadcastsLocked();
- }
-
- receiver.updateMonitoring(false);
-
- // Record which providers were associated with this listener
- HashSet<String> providers = new HashSet<>();
- HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords;
- if (oldRecords != null) {
- // Call dispose() on the obsolete update records.
- for (UpdateRecord record : oldRecords.values()) {
- // Update statistics for historical location requests by package/provider
- record.disposeLocked(false);
- }
- // Accumulate providers
- providers.addAll(oldRecords.keySet());
- }
-
- // update provider
- for (String provider : providers) {
- applyRequirementsLocked(provider);
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.unregisterLocationRequest(pendingIntent);
}
}
@Override
public Location getLastLocation(LocationRequest request, String packageName,
String attributionTag) {
- // unsafe is ok because app ops will verify the package name
- CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
- int permissionLevel = LocationPermissions.getCallingOrSelfPermissionLevel(mContext);
- LocationPermissions.enforceLocationPermission(Binder.getCallingUid(), permissionLevel,
+ CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
+ int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
+ identity.getPid());
+ LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
PERMISSION_COARSE);
- if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
- identity.getPackageName())) {
- return null;
- }
- if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
- return null;
- }
+ // clients in the system process must have an attribution tag set
+ Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null);
- synchronized (mLock) {
- LocationProviderManager manager = getLocationProviderManager(request.getProvider());
- if (manager == null) {
- return null;
- }
- if (!manager.isEnabled(identity.getUserId()) && !request.isLocationSettingsIgnored()) {
- return null;
- }
+ request = validateAndSanitizeLocationRequest(request, permissionLevel);
- // appops check should always be right before delivery
- if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
- identity)) {
- return null;
- }
+ LocationProviderManager manager = getLocationProviderManager(request.getProvider());
+ if (manager == null) {
+ return null;
+ }
- Location location = manager.getLastLocation(identity.getUserId(), permissionLevel);
+ Location location = manager.getLastLocation(request, identity, permissionLevel);
- // make a defensive copy - the client could be in the same process as us
- return location != null ? new Location(location) : null;
+ // lastly - note app ops
+ if (!mInjector.getAppOpsHelper().noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
+ identity)) {
+ return null;
}
+
+ return location;
}
@Override
public void getCurrentLocation(LocationRequest request,
- ICancellationSignal remoteCancellationSignal, ILocationCallback callback,
+ ICancellationSignal cancellationTransport, ILocationCallback consumer,
String packageName, String attributionTag, String listenerId) {
- // unsafe is ok because app ops will verify the package name
- CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag,
+ CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
listenerId);
- int permissionLevel = LocationPermissions.getCallingOrSelfPermissionLevel(mContext);
- LocationPermissions.enforceLocationPermission(Binder.getCallingUid(), permissionLevel,
+ int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
+ identity.getPid());
+ LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
PERMISSION_COARSE);
- request = createSanitizedRequest(request, false, permissionLevel);
- request.setNumUpdates(1);
- if (request.getExpireIn() > GET_CURRENT_LOCATION_MAX_TIMEOUT_MS) {
- request.setExpireIn(GET_CURRENT_LOCATION_MAX_TIMEOUT_MS);
- }
-
- GetCurrentLocationTransport transport = new GetCurrentLocationTransport(callback);
-
- if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
- identity.getPackageName())) {
- transport.deliverResult(null);
- return;
- }
- if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
- transport.deliverResult(null);
- return;
- }
-
- Location lastLocation;
- synchronized (mLock) {
- LocationProviderManager manager = getLocationProviderManager(request.getProvider());
- if (manager == null) {
- transport.deliverResult(null);
- return;
- }
- if (!manager.isEnabled(identity.getUserId()) && !request.isLocationSettingsIgnored()) {
- transport.deliverResult(null);
- return;
- }
-
- lastLocation = manager.getLastLocation(identity.getUserId(), permissionLevel);
- }
-
- if (lastLocation != null) {
- long locationAgeMs = NANOSECONDS.toMillis(
- SystemClock.elapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos());
+ // clients in the system process must have an attribution tag set
+ Preconditions.checkState(identity.getPid() != Process.myPid() || attributionTag != null);
- if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
- // appops check should always be right before delivery
- if (mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
- identity)) {
- transport.deliverResult(lastLocation);
- } else {
- transport.deliverResult(null);
- }
- return;
- }
+ request = validateAndSanitizeLocationRequest(request, permissionLevel);
- if (!mAppForegroundHelper.isAppForeground(Binder.getCallingUid())) {
- if (locationAgeMs < mSettingsHelper.getBackgroundThrottleIntervalMs()) {
- // not allowed to request new locations, so we can't return anything
- transport.deliverResult(null);
- return;
- }
- }
- }
+ LocationProviderManager manager = getLocationProviderManager(request.getProvider());
+ Preconditions.checkArgument(manager != null,
+ "provider \"" + request.getProvider() + "\" does not exist");
- registerLocationListener(request, transport, packageName, attributionTag, listenerId);
- CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
- remoteCancellationSignal);
- if (cancellationSignal != null) {
- cancellationSignal.setOnCancelListener(() -> unregisterLocationListener(transport));
- }
+ manager.getCurrentLocation(request, identity, permissionLevel, cancellationTransport,
+ consumer);
}
@Override
@@ -2228,8 +740,13 @@ public class LocationManagerService extends ILocationManager.Stub {
return null;
}
- Location location = gpsManager.getLastLocation(UserHandle.getCallingUserId(),
- PERMISSION_FINE);
+ // create a location request that works in almost all circumstances
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(GPS_PROVIDER, 0,
+ 0, true);
+
+ // use our own identity rather than the caller
+ CallerIdentity identity = CallerIdentity.fromContext(mContext);
+ Location location = gpsManager.getLastLocation(request, identity, PERMISSION_FINE);
if (location == null) {
return null;
}
@@ -2248,11 +765,9 @@ public class LocationManagerService extends ILocationManager.Stub {
Preconditions.checkArgument(location.isComplete());
int userId = UserHandle.getCallingUserId();
- synchronized (mLock) {
- LocationProviderManager manager = getLocationProviderManager(location.getProvider());
- if (manager != null && manager.isEnabled(userId)) {
- manager.injectLastLocation(Objects.requireNonNull(location), userId);
- }
+ LocationProviderManager manager = getLocationProviderManager(location.getProvider());
+ if (manager != null && manager.isEnabled(userId)) {
+ manager.injectLastLocation(Objects.requireNonNull(location), userId);
}
}
@@ -2357,12 +872,11 @@ public class LocationManagerService extends ILocationManager.Stub {
Objects.requireNonNull(command), extras);
}
- mLocationUsageLogger.logLocationApiUsage(
+ mInjector.getLocationUsageLogger().logLocationApiUsage(
LocationStatsEnums.USAGE_STARTED,
LocationStatsEnums.API_SEND_EXTRA_COMMAND,
provider);
-
- mLocationUsageLogger.logLocationApiUsage(
+ mInjector.getLocationUsageLogger().logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_SEND_EXTRA_COMMAND,
provider);
@@ -2385,7 +899,7 @@ public class LocationManagerService extends ILocationManager.Stub {
if (provider != null && !provider.equals(manager.getName())) {
continue;
}
- CallerIdentity identity = manager.getProviderIdentity();
+ CallerIdentity identity = manager.getIdentity();
if (identity == null) {
continue;
}
@@ -2406,7 +920,7 @@ public class LocationManagerService extends ILocationManager.Stub {
return Collections.emptyList();
}
- CallerIdentity identity = manager.getProviderIdentity();
+ CallerIdentity identity = manager.getIdentity();
if (identity == null) {
return Collections.emptyList();
}
@@ -2454,164 +968,26 @@ public class LocationManagerService extends ILocationManager.Stub {
mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null);
- invalidateLocalLocationEnabledCaches();
- mSettingsHelper.setLocationEnabled(enabled, userId);
+ LocationManager.invalidateLocalLocationEnabledCaches();
+ mInjector.getSettingsHelper().setLocationEnabled(enabled, userId);
}
@Override
public boolean isLocationEnabledForUser(int userId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false, "isLocationEnabledForUser", null);
- return mSettingsHelper.isLocationEnabled(userId);
+ return mInjector.getSettingsHelper().isLocationEnabled(userId);
}
@Override
public boolean isProviderEnabledForUser(String provider, int userId) {
- // Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
+ // fused provider is accessed indirectly via criteria rather than the provider-based APIs,
// so we discourage its use
if (FUSED_PROVIDER.equals(provider)) return false;
return mLocalService.isProviderEnabledForUser(provider, userId);
}
- @GuardedBy("mLock")
- private static boolean shouldBroadcastSafeLocked(
- Location loc, Location lastLoc, UpdateRecord record, long now) {
- // Always broadcast the first update
- if (lastLoc == null) {
- return true;
- }
-
- // Check whether sufficient time has passed
- long minTime = record.mRealRequest.getFastestInterval();
- long deltaMs = NANOSECONDS.toMillis(
- loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos());
- if (deltaMs < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) {
- return false;
- }
-
- // Check whether sufficient distance has been traveled
- double minDistance = record.mRealRequest.getSmallestDisplacement();
- if (minDistance > 0.0) {
- if (loc.distanceTo(lastLoc) <= minDistance) {
- return false;
- }
- }
-
- // Check whether sufficient number of udpates is left
- if (record.mRealRequest.getNumUpdates() <= 0) {
- return false;
- }
-
- // Check whether the expiry date has passed
- return record.mExpirationRealtimeMs >= now;
- }
-
- @GuardedBy("mLock")
- private void handleLocationChangedLocked(LocationProviderManager manager, Location fineLocation,
- Location coarseLocation) {
- if (!mProviderManagers.contains(manager)) {
- Log.w(TAG, "received location from unknown provider: " + manager.getName());
- return;
- }
-
- // notify passive provider
- if (manager != mPassiveManager) {
- mPassiveManager.updateLocation(fineLocation);
- }
-
- long now = SystemClock.elapsedRealtime();
-
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
- if (records == null || records.size() == 0) return;
-
- ArrayList<Receiver> deadReceivers = null;
- ArrayList<UpdateRecord> deadUpdateRecords = null;
-
- // Broadcast location to all listeners
- for (UpdateRecord r : records) {
- Receiver receiver = r.mReceiver;
- CallerIdentity identity = receiver.mCallerIdentity;
- boolean receiverDead = false;
-
-
- if (!manager.isEnabled(identity.getUserId()) && !isSettingsExempt(r)) {
- continue;
- }
-
- if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())
- && !mLocalService.isProvider(null, identity)) {
- continue;
- }
-
- if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
- identity.getPackageName())) {
- continue;
- }
-
- int permissionLevel = r.mRequest.isCoarse() ? PERMISSION_COARSE : PERMISSION_FINE;
-
- Location location;
- switch (permissionLevel) {
- case PERMISSION_COARSE:
- location = coarseLocation;
- break;
- case PERMISSION_FINE:
- location = fineLocation;
- break;
- default:
- throw new AssertionError();
- }
-
- if (shouldBroadcastSafeLocked(location, r.mLastFixBroadcast, r, now)) {
- r.mLastFixBroadcast = location;
-
- // appops check should always be right before delivery
- if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
- receiver.mCallerIdentity)) {
- continue;
- }
-
- if (!receiver.callLocationChangedLocked(location, r.mRequest)) {
- receiverDead = true;
- }
- r.mRealRequest.decrementNumUpdates();
- }
-
- // track expired records
- if (r.mRealRequest.getNumUpdates() <= 0 || r.mExpirationRealtimeMs < now) {
- if (deadUpdateRecords == null) {
- deadUpdateRecords = new ArrayList<>();
- }
- deadUpdateRecords.add(r);
- }
- // track dead receivers
- if (receiverDead) {
- if (deadReceivers == null) {
- deadReceivers = new ArrayList<>();
- }
- if (!deadReceivers.contains(receiver)) {
- deadReceivers.add(receiver);
- }
- }
- }
-
- // remove dead records and receivers outside the loop
- if (deadReceivers != null) {
- for (Receiver receiver : deadReceivers) {
- removeUpdatesLocked(receiver);
- }
- }
- if (deadUpdateRecords != null) {
- for (UpdateRecord r : deadUpdateRecords) {
- r.disposeLocked(true);
- }
- applyRequirementsLocked(manager);
- }
- }
-
- // Geocoder
-
@Override
public boolean geocoderIsPresent() {
return mGeocodeProvider != null;
@@ -2637,7 +1013,6 @@ public class LocationManagerService extends ILocationManager.Stub {
double lowerLeftLatitude, double lowerLeftLongitude,
double upperRightLatitude, double upperRightLongitude, int maxResults,
GeocoderParams params, IGeocodeListener listener) {
-
if (mGeocodeProvider != null) {
mGeocodeProvider.getFromLocationName(locationName, lowerLeftLatitude,
lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
@@ -2651,35 +1026,24 @@ public class LocationManagerService extends ILocationManager.Stub {
}
}
- // Mock Providers
-
@Override
public void addTestProvider(String provider, ProviderProperties properties,
String packageName, String attributionTag) {
// unsafe is ok because app ops will verify the package name
- CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
- attributionTag);
- if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
+ CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
+ if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) {
return;
}
- synchronized (mLock) {
- LocationProviderManager manager = getLocationProviderManager(provider);
- if (manager == null) {
- manager = new LocationProviderManager(provider);
- mProviderManagers.add(manager);
- }
-
- manager.setMockProvider(new MockProvider(properties, identity));
- }
+ getOrAddLocationProviderManager(provider).setMockProvider(
+ new MockProvider(properties, identity));
}
@Override
public void removeTestProvider(String provider, String packageName, String attributionTag) {
// unsafe is ok because app ops will verify the package name
- CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
- attributionTag);
- if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
+ CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
+ if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) {
return;
}
@@ -2691,7 +1055,7 @@ public class LocationManagerService extends ILocationManager.Stub {
manager.setMockProvider(null);
if (!manager.hasProvider()) {
- mProviderManagers.remove(manager);
+ removeLocationProviderManager(manager);
}
}
}
@@ -2702,7 +1066,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// unsafe is ok because app ops will verify the package name
CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
attributionTag);
- if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
+ if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) {
return;
}
@@ -2723,7 +1087,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// unsafe is ok because app ops will verify the package name
CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
attributionTag);
- if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
+ if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) {
return;
}
@@ -2777,57 +1141,32 @@ public class LocationManagerService extends ILocationManager.Stub {
ipw.println("User Info:");
ipw.increaseIndent();
- mUserInfoHelper.dump(fd, ipw, args);
+ mInjector.getUserInfoHelper().dump(fd, ipw, args);
ipw.decreaseIndent();
ipw.println("Location Settings:");
ipw.increaseIndent();
- mSettingsHelper.dump(fd, ipw, args);
+ mInjector.getSettingsHelper().dump(fd, ipw, args);
ipw.decreaseIndent();
- synchronized (mLock) {
- ipw.println("Battery Saver Location Mode: "
- + locationPowerSaveModeToString(mBatterySaverMode));
-
- if (dumpFilter == null) {
- ipw.println("Location Listeners:");
- ipw.increaseIndent();
- for (Receiver receiver : mReceivers.values()) {
- ipw.println(receiver);
- }
- ipw.decreaseIndent();
-
- ipw.println("Active Records by Provider:");
- ipw.increaseIndent();
- for (Map.Entry<String, ArrayList<UpdateRecord>> entry :
- mRecordsByProvider.entrySet()) {
- ipw.println(entry.getKey() + ":");
- ipw.increaseIndent();
- for (UpdateRecord record : entry.getValue()) {
- ipw.println(record);
- }
- ipw.decreaseIndent();
- }
- ipw.decreaseIndent();
-
- ipw.println("Historical Records by Provider:");
- ipw.increaseIndent();
- TreeMap<PackageProviderKey, PackageStatistics> sorted = new TreeMap<>(
- mRequestStatistics.statistics);
- for (Map.Entry<PackageProviderKey, PackageStatistics> entry
- : sorted.entrySet()) {
- ipw.println(entry.getKey() + ": " + entry.getValue());
- }
- ipw.decreaseIndent();
+ ipw.println("Historical Records by Provider:");
+ ipw.increaseIndent();
+ TreeMap<PackageProviderKey, PackageStatistics> sorted = new TreeMap<>(
+ mInjector.getLocationRequestStatistics().statistics);
+ for (Map.Entry<PackageProviderKey, PackageStatistics> entry
+ : sorted.entrySet()) {
+ ipw.println(entry.getKey() + ": " + entry.getValue());
+ }
+ ipw.decreaseIndent();
- mRequestStatistics.history.dump(ipw);
+ mInjector.getLocationRequestStatistics().history.dump(ipw);
- if (mExtraLocationControllerPackage != null) {
- ipw.println(
- "Location Controller Extra Package: " + mExtraLocationControllerPackage
- + (mExtraLocationControllerPackageEnabled ? " [enabled]"
- : "[disabled]"));
- }
+ synchronized (mLock) {
+ if (mExtraLocationControllerPackage != null) {
+ ipw.println(
+ "Location Controller Extra Package: " + mExtraLocationControllerPackage
+ + (mExtraLocationControllerPackageEnabled ? " [enabled]"
+ : "[disabled]"));
}
}
@@ -2857,47 +1196,6 @@ public class LocationManagerService extends ILocationManager.Stub {
}
}
- private class GetCurrentLocationTransport extends ILocationListener.Stub {
-
- private final Executor mExecutor;
- private final ILocationCallback mCallback;
-
- GetCurrentLocationTransport(ILocationCallback callback) {
- mExecutor = FgThread.getExecutor();
- mCallback = callback;
- }
-
- @Override
- public void onLocationChanged(Location location, IRemoteCallback onCompleteCallback) {
- mExecutor.execute(() -> {
- deliverResult(location);
- try {
- onCompleteCallback.sendResult(null);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- });
- unregisterLocationListener(this);
- }
-
- @Override
- public void onProviderEnabledChanged(String provider, boolean enabled)
- throws RemoteException {
- if (!enabled) {
- deliverResult(null);
- unregisterLocationListener(this);
- }
- }
-
- public void deliverResult(@Nullable Location location) {
- try {
- mCallback.onLocation(location);
- } catch (RemoteException e) {
- // do nothing
- }
- }
- }
-
private class LocalService extends LocationManagerInternal {
LocalService() {}
@@ -2916,17 +1214,28 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
+ public void addProviderEnabledListener(String provider, ProviderEnabledListener listener) {
+ LocationProviderManager manager = Objects.requireNonNull(
+ getLocationProviderManager(provider));
+ manager.addEnabledListener(listener);
+ }
+
+ @Override
+ public void removeProviderEnabledListener(String provider,
+ ProviderEnabledListener listener) {
+ LocationProviderManager manager = Objects.requireNonNull(
+ getLocationProviderManager(provider));
+ manager.removeEnabledListener(listener);
+ }
+
+ @Override
public boolean isProvider(String provider, CallerIdentity identity) {
- for (LocationProviderManager manager : mProviderManagers) {
- if (provider != null && !provider.equals(manager.getName())) {
- continue;
- }
- if (identity.equals(manager.getProviderIdentity())) {
- return true;
- }
+ LocationProviderManager manager = getLocationProviderManager(provider);
+ if (manager == null) {
+ return false;
+ } else {
+ return identity.equals(manager.getIdentity());
}
-
- return false;
}
@Override
@@ -2935,6 +1244,13 @@ public class LocationManagerService extends ILocationManager.Stub {
mGnssManagerService.sendNiResponse(notifId, userResponse);
}
}
+
+ @Override
+ public void reportGnssBatchLocations(List<Location> locations) {
+ if (mGnssManagerService != null) {
+ mGnssManagerService.onReportLocation(locations);
+ }
+ }
}
private static class SystemInjector implements Injector {
diff --git a/services/core/java/com/android/server/location/LocationManagerServiceUtils.java b/services/core/java/com/android/server/location/LocationManagerServiceUtils.java
deleted file mode 100644
index b9d86c84508f..000000000000
--- a/services/core/java/com/android/server/location/LocationManagerServiceUtils.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location;
-
-import android.annotation.NonNull;
-import android.location.util.identity.CallerIdentity;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import java.util.NoSuchElementException;
-
-/**
- * Shared utilities for LocationManagerService and GnssManager.
- */
-public class LocationManagerServiceUtils {
-
- /**
- * Skeleton class of listener that can be linked to a binder.
- */
- public abstract static class LinkedListenerBase implements IBinder.DeathRecipient {
- protected final CallerIdentity mCallerIdentity;
-
- LinkedListenerBase(@NonNull CallerIdentity callerIdentity) {
- mCallerIdentity = callerIdentity;
- }
-
- @Override
- public String toString() {
- return mCallerIdentity.toString();
- }
-
- public CallerIdentity getCallerIdentity() {
- return mCallerIdentity;
- }
-
- /**
- * Link listener (i.e. callback) to a binder, so that it will be called upon binder's death.
- */
- public boolean linkToListenerDeathNotificationLocked(IBinder binder) {
- try {
- binder.linkToDeath(this, 0 /* flags */);
- return true;
- } catch (RemoteException e) {
- return false;
- }
- }
-
- /**
- * Unlink death listener (i.e. callback) from binder.
- */
- public void unlinkFromListenerDeathNotificationLocked(IBinder binder) {
- try {
- binder.unlinkToDeath(this, 0 /* flags */);
- } catch (NoSuchElementException e) {
- // ignore
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
new file mode 100644
index 000000000000..d4f8c7e855b9
--- /dev/null
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -0,0 +1,1951 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.WINDOW_EXACT;
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.GPS_PROVIDER;
+import static android.location.LocationManager.KEY_LOCATION_CHANGED;
+import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
+import static android.os.IPowerManager.LOCATION_MODE_NO_CHANGE;
+import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
+import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+
+import static com.android.server.location.LocationManagerService.D;
+import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
+import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
+import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Criteria;
+import android.location.ILocationCallback;
+import android.location.ILocationListener;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.LocationManagerInternal.ProviderEnabledListener;
+import android.location.LocationRequest;
+import android.location.util.identity.CallerIdentity;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.IRemoteCallback;
+import android.os.PowerManager;
+import android.os.PowerManager.LocationPowerSaveMode;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.stats.location.LocationStatsEnums;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.PendingIntentUtils;
+import com.android.server.location.LocationPermissions.PermissionLevel;
+import com.android.server.location.listeners.ListenerMultiplexer;
+import com.android.server.location.listeners.RemovableListenerRegistration;
+import com.android.server.location.util.AppForegroundHelper;
+import com.android.server.location.util.AppForegroundHelper.AppForegroundListener;
+import com.android.server.location.util.AppOpsHelper;
+import com.android.server.location.util.Injector;
+import com.android.server.location.util.LocationAttributionHelper;
+import com.android.server.location.util.LocationPermissionsHelper;
+import com.android.server.location.util.LocationPermissionsHelper.LocationPermissionsListener;
+import com.android.server.location.util.LocationPowerSaveModeHelper;
+import com.android.server.location.util.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
+import com.android.server.location.util.LocationUsageLogger;
+import com.android.server.location.util.ScreenInteractiveHelper;
+import com.android.server.location.util.ScreenInteractiveHelper.ScreenInteractiveChangedListener;
+import com.android.server.location.util.SettingsHelper;
+import com.android.server.location.util.SettingsHelper.GlobalSettingChangedListener;
+import com.android.server.location.util.SettingsHelper.UserSettingChangedListener;
+import com.android.server.location.util.UserInfoHelper;
+import com.android.server.location.util.UserInfoHelper.UserListener;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+class LocationProviderManager extends
+ ListenerMultiplexer<Object, LocationRequest, LocationProviderManager.LocationTransport,
+ LocationProviderManager.Registration, ProviderRequest> implements
+ AbstractLocationProvider.Listener {
+
+ // fastest interval at which clients may receive coarse locations
+ public static final long FASTEST_COARSE_INTERVAL_MS = 10 * 60 * 1000;
+
+ private static final String WAKELOCK_TAG = "*location*";
+ private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000;
+
+ // maximum interval to be considered "high power" request
+ private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
+
+ // maximum age of a location before it is no longer considered "current"
+ private static final long MAX_CURRENT_LOCATION_AGE_MS = 10 * 1000;
+
+ // max timeout allowed for getting the current location
+ private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30 * 1000;
+
+ // maximum jitter allowed for fastest interval evaluation
+ private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 100;
+
+ protected interface LocationTransport {
+
+ void deliverOnLocationChanged(Location location, @Nullable Runnable onCompleteCallback)
+ throws Exception;
+ }
+
+ protected interface ProviderTransport {
+
+ void deliverOnProviderEnabledChanged(String provider, boolean enabled) throws Exception;
+ }
+
+ protected static final class LocationListenerTransport implements LocationTransport,
+ ProviderTransport {
+
+ private final ILocationListener mListener;
+
+ protected LocationListenerTransport(ILocationListener listener) {
+ mListener = Objects.requireNonNull(listener);
+ }
+
+ @Override
+ public void deliverOnLocationChanged(Location location,
+ @Nullable Runnable onCompleteCallback)
+ throws RemoteException {
+ mListener.onLocationChanged(location,
+ onCompleteCallback == null ? null : new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) {
+ onCompleteCallback.run();
+ }
+ });
+ }
+
+ @Override
+ public void deliverOnProviderEnabledChanged(String provider, boolean enabled)
+ throws RemoteException {
+ mListener.onProviderEnabledChanged(provider, enabled);
+ }
+ }
+
+ protected static final class LocationPendingIntentTransport implements LocationTransport,
+ ProviderTransport {
+
+ private final Context mContext;
+ private final PendingIntent mPendingIntent;
+
+ public LocationPendingIntentTransport(Context context, PendingIntent pendingIntent) {
+ mContext = context;
+ mPendingIntent = pendingIntent;
+ }
+
+ @Override
+ public void deliverOnLocationChanged(Location location,
+ @Nullable Runnable onCompleteCallback)
+ throws PendingIntent.CanceledException {
+ mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_LOCATION_CHANGED, location),
+ onCompleteCallback != null ? (pI, i, rC, rD, rE) -> onCompleteCallback.run()
+ : null, null, null,
+ PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
+ }
+
+ @Override
+ public void deliverOnProviderEnabledChanged(String provider, boolean enabled)
+ throws PendingIntent.CanceledException {
+ mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_PROVIDER_ENABLED, enabled),
+ null, null, null,
+ PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
+ }
+ }
+
+ protected static final class GetCurrentLocationTransport implements LocationTransport {
+
+ private final ILocationCallback mCallback;
+
+ protected GetCurrentLocationTransport(ILocationCallback callback) {
+ mCallback = Objects.requireNonNull(callback);
+ }
+
+ @Override
+ public void deliverOnLocationChanged(Location location,
+ @Nullable Runnable onCompleteCallback)
+ throws RemoteException {
+ // ILocationCallback doesn't currently support completion callbacks
+ Preconditions.checkState(onCompleteCallback == null);
+ mCallback.onLocation(location);
+ }
+ }
+
+ protected abstract class Registration extends
+ RemovableListenerRegistration<LocationRequest, LocationTransport> {
+
+ @PermissionLevel protected final int mPermissionLevel;
+ private final WorkSource mWorkSource;
+
+ // we cache these values because checking/calculating on the fly is more expensive
+ private boolean mPermitted;
+ private boolean mForeground;
+ @Nullable private LocationRequest mProviderLocationRequest;
+ private boolean mIsUsingHighPower;
+
+ protected Registration(LocationRequest request, CallerIdentity identity,
+ LocationTransport transport, @PermissionLevel int permissionLevel) {
+ super(TAG, Objects.requireNonNull(request), identity, transport);
+
+ Preconditions.checkArgument(permissionLevel > PERMISSION_NONE);
+ mPermissionLevel = permissionLevel;
+
+ if (request.getWorkSource() != null && !request.getWorkSource().isEmpty()) {
+ mWorkSource = request.getWorkSource();
+ } else {
+ mWorkSource = identity.addToWorkSource(null);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected final void onRemovableListenerRegister() {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ if (D) {
+ Log.d(TAG, mName + " provider added registration from " + getIdentity() + " -> "
+ + getRequest());
+ }
+
+ // initialization order is important as there are ordering dependencies
+ mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
+ getIdentity());
+ mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid());
+ mProviderLocationRequest = calculateProviderLocationRequest();
+ mIsUsingHighPower = isUsingHighPower();
+
+ onProviderListenerRegister();
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected final void onRemovableListenerUnregister() {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ onProviderListenerUnregister();
+
+ if (D) {
+ Log.d(TAG, mName + " provider removed registration from " + getIdentity());
+ }
+ }
+
+ /**
+ * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
+ */
+ @GuardedBy("mLock")
+ protected void onProviderListenerRegister() {}
+
+ /**
+ * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
+ */
+ @GuardedBy("mLock")
+ protected void onProviderListenerUnregister() {}
+
+ @Override
+ protected final ListenerOperation<LocationTransport> onActive() {
+ if (!getRequest().getHideFromAppOps()) {
+ mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
+ }
+ onHighPowerUsageChanged();
+ return null;
+ }
+
+ @Override
+ protected final void onInactive() {
+ onHighPowerUsageChanged();
+ if (!getRequest().getHideFromAppOps()) {
+ mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
+ }
+ }
+
+ @Override
+ public final LocationRequest getRequest() {
+ return Objects.requireNonNull(mProviderLocationRequest);
+ }
+
+ public final boolean isForeground() {
+ return mForeground;
+ }
+
+ public final boolean isPermitted() {
+ return mPermitted;
+ }
+
+ @Override
+ protected final LocationProviderManager getOwner() {
+ return LocationProviderManager.this;
+ }
+
+ protected final WorkSource getWorkSource() {
+ return mWorkSource;
+ }
+
+ @GuardedBy("mLock")
+ private void onHighPowerUsageChanged() {
+ boolean isUsingHighPower = isUsingHighPower();
+ if (isUsingHighPower != mIsUsingHighPower) {
+ mIsUsingHighPower = isUsingHighPower;
+
+ if (!getRequest().getHideFromAppOps()) {
+ if (mIsUsingHighPower) {
+ mLocationAttributionHelper.reportHighPowerLocationStart(
+ getIdentity(), getName(), getKey());
+ } else {
+ mLocationAttributionHelper.reportHighPowerLocationStop(
+ getIdentity(), getName(), getKey());
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUsingHighPower() {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ return isActive()
+ && getRequest().getInterval() < MAX_HIGH_POWER_INTERVAL_MS
+ && getProperties().mPowerRequirement == Criteria.POWER_HIGH;
+ }
+
+ @GuardedBy("mLock")
+ final boolean onLocationPermissionsChanged(String packageName) {
+ if (getIdentity().getPackageName().equals(packageName)) {
+ return onLocationPermissionsChanged();
+ }
+
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ final boolean onLocationPermissionsChanged(int uid) {
+ if (getIdentity().getUid() == uid) {
+ return onLocationPermissionsChanged();
+ }
+
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ private boolean onLocationPermissionsChanged() {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
+ getIdentity());
+ if (permitted != mPermitted) {
+ if (D) {
+ Log.v(TAG, mName + " provider package " + getIdentity().getPackageName()
+ + " permitted = " + permitted);
+ }
+
+ mPermitted = permitted;
+ return true;
+ }
+
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ final boolean onForegroundChanged(int uid, boolean foreground) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ if (getIdentity().getUid() == uid && foreground != mForeground) {
+ if (D) {
+ Log.v(TAG, mName + " provider uid " + uid + " foreground = " + foreground);
+ }
+
+ mForeground = foreground;
+
+ // note that onProviderLocationRequestChanged() is always called
+ return onProviderLocationRequestChanged()
+ || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
+ == LOCATION_MODE_FOREGROUND_ONLY;
+ }
+
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ final boolean onProviderLocationRequestChanged() {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ LocationRequest newRequest = calculateProviderLocationRequest();
+ if (!mProviderLocationRequest.equals(newRequest)) {
+ LocationRequest oldRequest = mProviderLocationRequest;
+ mProviderLocationRequest = newRequest;
+ onHighPowerUsageChanged();
+ updateService();
+
+ // if location settings ignored has changed then the active state may have changed
+ return oldRequest.isLocationSettingsIgnored()
+ != newRequest.isLocationSettingsIgnored();
+ }
+
+ return false;
+ }
+
+ private LocationRequest calculateProviderLocationRequest() {
+ LocationRequest newRequest = new LocationRequest(super.getRequest());
+
+ if (newRequest.isLocationSettingsIgnored()) {
+ // if we are not currently allowed use location settings ignored, disable it
+ if (!mSettingsHelper.getIgnoreSettingsPackageWhitelist().contains(
+ getIdentity().getPackageName()) && !mLocationManagerInternal.isProvider(
+ null, getIdentity())) {
+ newRequest.setLocationSettingsIgnored(false);
+ }
+ }
+
+ if (!newRequest.isLocationSettingsIgnored() && !isThrottlingExempt()) {
+ // throttle in the background
+ if (!mForeground) {
+ newRequest.setInterval(Math.max(newRequest.getInterval(),
+ mSettingsHelper.getBackgroundThrottleIntervalMs()));
+ }
+ }
+
+ return newRequest;
+ }
+
+ private boolean isThrottlingExempt() {
+ if (mSettingsHelper.getBackgroundThrottlePackageWhitelist().contains(
+ getIdentity().getPackageName())) {
+ return true;
+ }
+
+ return mLocationManagerInternal.isProvider(null, getIdentity());
+ }
+
+ @Nullable
+ abstract ListenerOperation<LocationTransport> acceptLocationChange(Location fineLocation);
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(getIdentity());
+
+ ArraySet<String> flags = new ArraySet<>(2);
+ if (!isForeground()) {
+ flags.add("bg");
+ }
+ if (!isPermitted()) {
+ flags.add("na");
+ }
+ if (!flags.isEmpty()) {
+ builder.append(" ").append(flags);
+ }
+
+ if (mPermissionLevel == PERMISSION_COARSE) {
+ builder.append(" (COARSE)");
+ }
+
+ builder.append(" ").append(getRequest());
+ return builder.toString();
+ }
+ }
+
+ protected abstract class LocationRegistration extends Registration implements
+ AlarmManager.OnAlarmListener, ProviderEnabledListener {
+
+ private final PowerManager.WakeLock mWakeLock;
+
+ private volatile ProviderTransport mProviderTransport;
+ @Nullable private Location mLastLocation = null;
+ private int mNumLocationsDelivered = 0;
+ private long mExpirationRealtimeMs = Long.MAX_VALUE;
+
+ protected <TTransport extends LocationTransport & ProviderTransport> LocationRegistration(
+ LocationRequest request, CallerIdentity identity, TTransport transport,
+ @PermissionLevel int permissionLevel) {
+ super(request, identity, transport, permissionLevel);
+ mProviderTransport = transport;
+ mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
+ .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+ mWakeLock.setReferenceCounted(true);
+ mWakeLock.setWorkSource(getWorkSource());
+ }
+
+ @Override
+ protected void onListenerUnregister() {
+ mProviderTransport = null;
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected final void onProviderListenerRegister() {
+ mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
+ SystemClock.elapsedRealtime());
+
+ // add alarm for expiration
+ if (mExpirationRealtimeMs < SystemClock.elapsedRealtime()) {
+ remove();
+ } else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
+ AlarmManager alarmManager = Objects.requireNonNull(
+ mContext.getSystemService(AlarmManager.class));
+ alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
+ 0, this, FgThread.getHandler(), getWorkSource());
+ }
+
+ // start listening for provider enabled/disabled events
+ addEnabledListener(this);
+
+ onLocationListenerRegister();
+
+ // if the provider is currently disabled, let the client know immediately
+ int userId = getIdentity().getUserId();
+ if (!isEnabled(userId)) {
+ onProviderEnabledChanged(mName, userId, false);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected final void onProviderListenerUnregister() {
+ // stop listening for provider enabled/disabled events
+ removeEnabledListener(this);
+
+ // remove alarm for expiration
+ if (mExpirationRealtimeMs < Long.MAX_VALUE) {
+ AlarmManager alarmManager = Objects.requireNonNull(
+ mContext.getSystemService(AlarmManager.class));
+ alarmManager.cancel(this);
+ }
+
+ onLocationListenerUnregister();
+ }
+
+ /**
+ * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
+ */
+ @GuardedBy("mLock")
+ protected void onLocationListenerRegister() {}
+
+ /**
+ * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
+ */
+ @GuardedBy("mLock")
+ protected void onLocationListenerUnregister() {}
+
+ @Override
+ public void onAlarm() {
+ if (D) {
+ Log.d(TAG, "removing " + getIdentity() + " from " + mName
+ + " provider due to expiration at " + TimeUtils.formatRealtime(
+ mExpirationRealtimeMs));
+ }
+
+ synchronized (mLock) {
+ remove();
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ @Override
+ ListenerOperation<LocationTransport> acceptLocationChange(Location fineLocation) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ // check expiration time - alarm is not guaranteed to go off at the right time,
+ // especially for short intervals
+ if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
+ remove();
+ return null;
+ }
+
+ Location location;
+ switch (mPermissionLevel) {
+ case PERMISSION_FINE:
+ location = fineLocation;
+ break;
+ case PERMISSION_COARSE:
+ location = mLocationFudger.createCoarse(fineLocation);
+ break;
+ default:
+ // shouldn't be possible to have a client added without location permissions
+ throw new AssertionError();
+ }
+
+ if (mLastLocation != null) {
+ // check fastest interval
+ long deltaMs = NANOSECONDS.toMillis(
+ location.getElapsedRealtimeNanos()
+ - mLastLocation.getElapsedRealtimeNanos());
+ if (deltaMs < getRequest().getFastestInterval() - MAX_FASTEST_INTERVAL_JITTER_MS) {
+ return null;
+ }
+
+ // check smallest displacement
+ double smallestDisplacement = getRequest().getSmallestDisplacement();
+ if (smallestDisplacement > 0.0 && location.distanceTo(mLastLocation)
+ <= smallestDisplacement) {
+ return null;
+ }
+ }
+
+ // note app ops
+ if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(mPermissionLevel),
+ getIdentity())) {
+ if (D) {
+ Log.w(TAG, "noteOp denied for " + getIdentity());
+ }
+ return null;
+ }
+
+ // update last location
+ mLastLocation = location;
+
+ return new ListenerOperation<LocationTransport>() {
+ @Override
+ public void onPreExecute() {
+ // don't acquire a wakelock for mock locations to prevent abuse
+ if (!location.isFromMockProvider()) {
+ mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
+ }
+ }
+
+ @Override
+ public void operate(LocationTransport listener) throws Exception {
+ // if delivering to the same process, make a copy of the location first (since
+ // location is mutable)
+ Location deliveryLocation;
+ if (getIdentity().getPid() == Process.myPid()) {
+ deliveryLocation = new Location(location);
+ } else {
+ deliveryLocation = location;
+ }
+
+ listener.deliverOnLocationChanged(deliveryLocation,
+ location.isFromMockProvider() ? null : mWakeLock::release);
+ }
+
+ @Override
+ public void onPostExecute(boolean success) {
+ if (!success && !location.isFromMockProvider()) {
+ mWakeLock.release();
+ }
+
+ if (success) {
+ // check num updates - if successful then this function will always be run
+ // from the same thread, and no additional synchronization is necessary
+ boolean remove = ++mNumLocationsDelivered >= getRequest().getNumUpdates();
+ if (remove) {
+ if (D) {
+ Log.d(TAG, "removing " + getIdentity() + " from " + mName
+ + " provider due to number of updates");
+ }
+
+ synchronized (mLock) {
+ remove();
+ }
+ }
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onProviderEnabledChanged(String provider, int userId, boolean enabled) {
+ Preconditions.checkState(mName.equals(provider));
+
+ if (userId != getIdentity().getUserId()) {
+ return;
+ }
+
+ // we choose not to hold a wakelock for provider enabled changed events
+ executeSafely(getExecutor(), () -> mProviderTransport,
+ listener -> listener.deliverOnProviderEnabledChanged(mName, enabled));
+ }
+ }
+
+ protected final class LocationListenerRegistration extends LocationRegistration implements
+ IBinder.DeathRecipient {
+
+ protected LocationListenerRegistration(LocationRequest request, CallerIdentity identity,
+ LocationListenerTransport transport, @PermissionLevel int permissionLevel) {
+ super(request, identity, transport, permissionLevel);
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected void onLocationListenerRegister() {
+ try {
+ ((IBinder) getKey()).linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ remove();
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected void onLocationListenerUnregister() {
+ ((IBinder) getKey()).unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ try {
+ if (D) {
+ Log.d(TAG, mName + " provider client died: " + getIdentity());
+ }
+
+ synchronized (mLock) {
+ remove();
+ }
+ } catch (RuntimeException e) {
+ // the caller may swallow runtime exceptions, so we rethrow as assertion errors to
+ // ensure the crash is seen
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ protected final class LocationPendingIntentRegistration extends LocationRegistration implements
+ PendingIntent.CancelListener {
+
+ protected LocationPendingIntentRegistration(LocationRequest request,
+ CallerIdentity identity, LocationPendingIntentTransport transport,
+ @PermissionLevel int permissionLevel) {
+ super(request, identity, transport, permissionLevel);
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected void onLocationListenerRegister() {
+ ((PendingIntent) getKey()).registerCancelListener(this);
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected void onLocationListenerUnregister() {
+ ((PendingIntent) getKey()).unregisterCancelListener(this);
+ }
+
+ @Override
+ public void onCancelled(PendingIntent intent) {
+ synchronized (mLock) {
+ remove();
+ }
+ }
+ }
+
+ protected final class GetCurrentLocationListenerRegistration extends Registration implements
+ IBinder.DeathRecipient, ProviderEnabledListener, AlarmManager.OnAlarmListener {
+
+ private volatile LocationTransport mTransport;
+
+ private long mExpirationRealtimeMs = Long.MAX_VALUE;
+
+ protected GetCurrentLocationListenerRegistration(LocationRequest request,
+ CallerIdentity identity, LocationTransport transport, int permissionLevel) {
+ super(request, identity, transport, permissionLevel);
+ mTransport = transport;
+ }
+
+ @GuardedBy("mLock")
+ void deliverLocation(@Nullable Location location) {
+ executeSafely(getExecutor(), () -> mTransport, acceptLocationChange(location));
+ }
+
+ @Override
+ protected void onListenerUnregister() {
+ mTransport = null;
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected void onProviderListenerRegister() {
+ mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
+ SystemClock.elapsedRealtime());
+
+ // add alarm for expiration
+ if (mExpirationRealtimeMs < Long.MAX_VALUE) {
+ AlarmManager alarmManager = Objects.requireNonNull(
+ mContext.getSystemService(AlarmManager.class));
+ alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
+ 0, this, FgThread.getHandler(), getWorkSource());
+ }
+
+ try {
+ ((IBinder) getKey()).linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ remove();
+ }
+
+ // start listening for provider enabled/disabled events
+ addEnabledListener(this);
+
+ // if the provider is currently disabled fail immediately
+ int userId = getIdentity().getUserId();
+ if (!getRequest().isLocationSettingsIgnored() && !isEnabled(userId)) {
+ deliverLocation(null);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected void onProviderListenerUnregister() {
+ // stop listening for provider enabled/disabled events
+ removeEnabledListener(this);
+
+ // remove alarm for expiration
+ if (mExpirationRealtimeMs < Long.MAX_VALUE) {
+ AlarmManager alarmManager = Objects.requireNonNull(
+ mContext.getSystemService(AlarmManager.class));
+ alarmManager.cancel(this);
+ }
+
+ ((IBinder) getKey()).unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void onAlarm() {
+ if (D) {
+ Log.d(TAG, "removing " + getIdentity() + " from " + mName
+ + " provider due to expiration at " + TimeUtils.formatRealtime(
+ mExpirationRealtimeMs));
+ }
+
+ synchronized (mLock) {
+ deliverLocation(null);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ @Override
+ ListenerOperation<LocationTransport> acceptLocationChange(@Nullable Location fineLocation) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ // lastly - note app ops
+ Location location;
+ if (fineLocation == null) {
+ location = null;
+ } else if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(mPermissionLevel),
+ getIdentity())) {
+ if (D) {
+ Log.w(TAG, "noteOp denied for " + getIdentity());
+ }
+ location = null;
+ } else {
+ switch (mPermissionLevel) {
+ case PERMISSION_FINE:
+ location = fineLocation;
+ break;
+ case PERMISSION_COARSE:
+ location = mLocationFudger.createCoarse(fineLocation);
+ break;
+ default:
+ // shouldn't be possible to have a client added without location permissions
+ throw new AssertionError();
+ }
+ }
+
+ return listener -> {
+ // if delivering to the same process, make a copy of the location first (since
+ // location is mutable)
+ Location deliveryLocation = location;
+ if (getIdentity().getPid() == Process.myPid() && location != null) {
+ deliveryLocation = new Location(location);
+ }
+
+ // we currently don't hold a wakelock for getCurrentLocation deliveries
+ listener.deliverOnLocationChanged(deliveryLocation, null);
+
+ synchronized (mLock) {
+ remove();
+ }
+ };
+ }
+
+ @Override
+ public void onProviderEnabledChanged(String provider, int userId, boolean enabled) {
+ Preconditions.checkState(mName.equals(provider));
+
+ if (userId != getIdentity().getUserId()) {
+ return;
+ }
+
+ // if the provider is disabled we give up on current location immediately
+ if (!getRequest().isLocationSettingsIgnored() && !enabled) {
+ synchronized (mLock) {
+ deliverLocation(null);
+ }
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ try {
+ if (D) {
+ Log.d(TAG, mName + " provider client died: " + getIdentity());
+ }
+
+ synchronized (mLock) {
+ remove();
+ }
+ } catch (RuntimeException e) {
+ // the caller may swallow runtime exceptions, so we rethrow as assertion errors to
+ // ensure the crash is seen
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ protected final Object mLock = new Object();
+
+ protected final String mName;
+ @Nullable private final PassiveLocationProviderManager mPassiveManager;
+
+ protected final Context mContext;
+
+ @GuardedBy("mLock")
+ private boolean mStarted;
+
+ // maps of user id to value
+ @GuardedBy("mLock")
+ private final SparseBooleanArray mEnabled; // null or not present means unknown
+ @GuardedBy("mLock")
+ private final SparseArray<LastLocation> mLastLocations;
+
+ @GuardedBy("mLock")
+ private final ArrayList<ProviderEnabledListener> mEnabledListeners;
+
+ protected final LocationManagerInternal mLocationManagerInternal;
+ protected final SettingsHelper mSettingsHelper;
+ protected final UserInfoHelper mUserInfoHelper;
+ protected final AppOpsHelper mAppOpsHelper;
+ protected final LocationPermissionsHelper mLocationPermissionsHelper;
+ protected final AppForegroundHelper mAppForegroundHelper;
+ protected final LocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
+ protected final ScreenInteractiveHelper mScreenInteractiveHelper;
+ protected final LocationAttributionHelper mLocationAttributionHelper;
+ protected final LocationUsageLogger mLocationUsageLogger;
+ protected final LocationFudger mLocationFudger;
+ protected final LocationRequestStatistics mLocationRequestStatistics;
+
+ private final UserListener mUserChangedListener = this::onUserChanged;
+ private final UserSettingChangedListener mLocationEnabledChangedListener =
+ this::onLocationEnabledChanged;
+ private final GlobalSettingChangedListener mBackgroundThrottlePackageWhitelistChangedListener =
+ this::onBackgroundThrottlePackageWhitelistChanged;
+ private final UserSettingChangedListener mLocationPackageBlacklistChangedListener =
+ this::onLocationPackageBlacklistChanged;
+ private final LocationPermissionsListener mLocationPermissionsListener =
+ new LocationPermissionsListener() {
+ @Override
+ public void onLocationPermissionsChanged(String packageName) {
+ LocationProviderManager.this.onLocationPermissionsChanged(packageName);
+ }
+
+ @Override
+ public void onLocationPermissionsChanged(int uid) {
+ LocationProviderManager.this.onLocationPermissionsChanged(uid);
+ }
+ };
+ private final AppForegroundListener mAppForegroundChangedListener =
+ this::onAppForegroundChanged;
+ private final GlobalSettingChangedListener mBackgroundThrottleIntervalChangedListener =
+ this::onBackgroundThrottleIntervalChanged;
+ private final GlobalSettingChangedListener mIgnoreSettingsPackageWhitelistChangedListener =
+ this::onIgnoreSettingsWhitelistChanged;
+ private final LocationPowerSaveModeChangedListener mLocationPowerSaveModeChangedListener =
+ this::onLocationPowerSaveModeChanged;
+ private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener =
+ this::onScreenInteractiveChanged;
+
+ // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
+ protected final MockableLocationProvider mProvider;
+
+ LocationProviderManager(Context context, Injector injector, String name,
+ @Nullable PassiveLocationProviderManager passiveManager) {
+ mContext = context;
+ mName = Objects.requireNonNull(name);
+ mPassiveManager = passiveManager;
+ mStarted = false;
+ mEnabled = new SparseBooleanArray(2);
+ mLastLocations = new SparseArray<>(2);
+
+ mEnabledListeners = new ArrayList<>();
+
+ mLocationManagerInternal = Objects.requireNonNull(
+ LocalServices.getService(LocationManagerInternal.class));
+ mSettingsHelper = injector.getSettingsHelper();
+ mUserInfoHelper = injector.getUserInfoHelper();
+ mAppOpsHelper = injector.getAppOpsHelper();
+ mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
+ mAppForegroundHelper = injector.getAppForegroundHelper();
+ mLocationPowerSaveModeHelper = injector.getLocationPowerSaveModeHelper();
+ mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
+ mLocationAttributionHelper = injector.getLocationAttributionHelper();
+ mLocationUsageLogger = injector.getLocationUsageLogger();
+ mLocationRequestStatistics = injector.getLocationRequestStatistics();
+ mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
+
+ // initialize last since this lets our reference escape
+ mProvider = new MockableLocationProvider(mLock, this);
+ }
+
+ public void startManager() {
+ synchronized (mLock) {
+ mStarted = true;
+
+ mUserInfoHelper.addListener(mUserChangedListener);
+ mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
+
+ // initialize enabled state
+ onUserStarted(UserHandle.USER_ALL);
+ }
+ }
+
+ public void stopManager() {
+ synchronized (mLock) {
+ mUserInfoHelper.removeListener(mUserChangedListener);
+ mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
+
+ // notify and remove all listeners
+ onUserStopped(UserHandle.USER_ALL);
+ removeRegistrationIf(key -> true);
+ mEnabledListeners.clear();
+
+ mStarted = false;
+ }
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ @Nullable
+ public CallerIdentity getIdentity() {
+ return mProvider.getState().identity;
+ }
+
+ @Nullable
+ public ProviderProperties getProperties() {
+ return mProvider.getState().properties;
+ }
+
+ public boolean hasProvider() {
+ return mProvider.getProvider() != null;
+ }
+
+ public boolean isEnabled(int userId) {
+ if (userId == UserHandle.USER_NULL) {
+ return false;
+ }
+
+ Preconditions.checkArgument(userId >= 0);
+
+ synchronized (mLock) {
+ int index = mEnabled.indexOfKey(userId);
+ if (index < 0) {
+ // this generally shouldn't occur, but might be possible due to race conditions
+ // on when we are notified of new users
+ Log.w(TAG, mName + " provider saw user " + userId + " unexpectedly");
+ onEnabledChanged(userId);
+ index = mEnabled.indexOfKey(userId);
+ }
+
+ return mEnabled.valueAt(index);
+ }
+ }
+
+ public void addEnabledListener(ProviderEnabledListener listener) {
+ synchronized (mLock) {
+ Preconditions.checkState(mStarted);
+ mEnabledListeners.add(listener);
+ }
+ }
+
+ public void removeEnabledListener(ProviderEnabledListener listener) {
+ synchronized (mLock) {
+ Preconditions.checkState(mStarted);
+ mEnabledListeners.remove(listener);
+ }
+ }
+
+ public void setRealProvider(AbstractLocationProvider provider) {
+ synchronized (mLock) {
+ Preconditions.checkState(mStarted);
+ mProvider.setRealProvider(provider);
+ }
+ }
+
+ public void setMockProvider(@Nullable MockProvider provider) {
+ synchronized (mLock) {
+ Preconditions.checkState(mStarted);
+ mProvider.setMockProvider(provider);
+
+ // when removing a mock provider, also clear any mock last locations and reset the
+ // location fudger. the mock provider could have been used to infer the current
+ // location fudger offsets.
+ if (provider == null) {
+ final int lastLocationSize = mLastLocations.size();
+ for (int i = 0; i < lastLocationSize; i++) {
+ mLastLocations.valueAt(i).clearMock();
+ }
+
+ mLocationFudger.resetOffsets();
+ }
+ }
+ }
+
+ public void setMockProviderAllowed(boolean enabled) {
+ synchronized (mLock) {
+ if (!mProvider.isMock()) {
+ throw new IllegalArgumentException(mName + " provider is not a test provider");
+ }
+
+ mProvider.setMockProviderAllowed(enabled);
+ }
+ }
+
+ public void setMockProviderLocation(Location location) {
+ synchronized (mLock) {
+ if (!mProvider.isMock()) {
+ throw new IllegalArgumentException(mName + " provider is not a test provider");
+ }
+
+ String locationProvider = location.getProvider();
+ if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) {
+ // The location has an explicit provider that is different from the mock
+ // provider name. The caller may be trying to fool us via b/33091107.
+ EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
+ mName + "!=" + locationProvider);
+ }
+
+ mProvider.setMockProviderLocation(location);
+ }
+ }
+
+ public List<LocationRequest> getMockProviderRequests() {
+ synchronized (mLock) {
+ if (!mProvider.isMock()) {
+ throw new IllegalArgumentException(mName + " provider is not a test provider");
+ }
+
+ return mProvider.getCurrentRequest().locationRequests;
+ }
+ }
+
+ @Nullable
+ public Location getLastLocation(LocationRequest request, CallerIdentity identity,
+ @PermissionLevel int permissionLevel) {
+ Preconditions.checkArgument(mName.equals(request.getProvider()));
+
+ if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
+ identity.getPackageName())) {
+ return null;
+ }
+ if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+ return null;
+ }
+ if (!request.isLocationSettingsIgnored() && !isEnabled(identity.getUserId())) {
+ return null;
+ }
+
+ Location location = getLastLocation(identity.getUserId(), permissionLevel,
+ request.isLocationSettingsIgnored());
+
+ // we don't note op here because we don't know what the client intends to do with the
+ // location, the client is responsible for noting if necessary
+
+ if (identity.getPid() == Process.myPid() && location != null) {
+ // if delivering to the same process, make a copy of the location first (since
+ // location is mutable)
+ return new Location(location);
+ } else {
+ return location;
+ }
+ }
+
+ @Nullable
+ private Location getLastLocation(int userId, @PermissionLevel int permissionLevel,
+ boolean ignoreLocationSettings) {
+ synchronized (mLock) {
+ LastLocation lastLocation = mLastLocations.get(userId);
+ if (lastLocation == null) {
+ return null;
+ }
+ return lastLocation.get(permissionLevel, ignoreLocationSettings);
+ }
+ }
+
+ public void injectLastLocation(Location location, int userId) {
+ synchronized (mLock) {
+ if (getLastLocation(userId, PERMISSION_FINE, false) == null) {
+ setLastLocation(location, userId);
+ }
+ }
+ }
+
+ private void setLastLocation(Location location, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ final int[] runningUserIds = mUserInfoHelper.getRunningUserIds();
+ for (int i = 0; i < runningUserIds.length; i++) {
+ setLastLocation(location, runningUserIds[i]);
+ }
+ return;
+ }
+
+ Preconditions.checkArgument(userId >= 0);
+
+ synchronized (mLock) {
+ LastLocation lastLocation = mLastLocations.get(userId);
+ if (lastLocation == null) {
+ lastLocation = new LastLocation();
+ mLastLocations.put(userId, lastLocation);
+ }
+
+ Location coarseLocation = mLocationFudger.createCoarse(location);
+ if (isEnabled(userId)) {
+ lastLocation.set(location, coarseLocation);
+ }
+ lastLocation.setBypass(location, coarseLocation);
+ }
+ }
+
+ public void getCurrentLocation(LocationRequest request, CallerIdentity identity,
+ int permissionLevel, ICancellationSignal cancellationTransport,
+ ILocationCallback callback) {
+ Preconditions.checkArgument(mName.equals(request.getProvider()));
+
+ if (request.getExpireIn() > GET_CURRENT_LOCATION_MAX_TIMEOUT_MS) {
+ request.setExpireIn(GET_CURRENT_LOCATION_MAX_TIMEOUT_MS);
+ }
+
+ GetCurrentLocationListenerRegistration registration =
+ new GetCurrentLocationListenerRegistration(
+ request,
+ identity,
+ new GetCurrentLocationTransport(callback),
+ permissionLevel);
+
+ synchronized (mLock) {
+ Location lastLocation = getLastLocation(request, identity, permissionLevel);
+ if (lastLocation != null) {
+ long locationAgeMs = NANOSECONDS.toMillis(
+ SystemClock.elapsedRealtimeNanos()
+ - lastLocation.getElapsedRealtimeNanos());
+ if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
+ registration.deliverLocation(lastLocation);
+ return;
+ }
+
+ if (!mAppForegroundHelper.isAppForeground(Binder.getCallingUid())
+ && locationAgeMs < mSettingsHelper.getBackgroundThrottleIntervalMs()) {
+ registration.deliverLocation(null);
+ return;
+ }
+ }
+
+ // if last location isn't good enough then we add a location request
+ addRegistration(callback.asBinder(), registration);
+ CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
+ cancellationTransport);
+ if (cancellationSignal != null) {
+ cancellationSignal.setOnCancelListener(
+ () -> {
+ synchronized (mLock) {
+ removeRegistration(callback.asBinder(), registration);
+ }
+ });
+ }
+ }
+ }
+
+ public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
+ mProvider.sendExtraCommand(uid, pid, command, extras);
+ }
+
+ public void registerLocationRequest(LocationRequest request, CallerIdentity identity,
+ @PermissionLevel int permissionLevel, ILocationListener listener) {
+ Preconditions.checkArgument(mName.equals(request.getProvider()));
+
+ synchronized (mLock) {
+ addRegistration(
+ listener.asBinder(),
+ new LocationListenerRegistration(
+ request,
+ identity,
+ new LocationListenerTransport(listener),
+ permissionLevel));
+ }
+ }
+
+ public void registerLocationRequest(LocationRequest request, CallerIdentity identity,
+ @PermissionLevel int permissionLevel, PendingIntent pendingIntent) {
+ Preconditions.checkArgument(mName.equals(request.getProvider()));
+
+ synchronized (mLock) {
+ addRegistration(
+ pendingIntent,
+ new LocationPendingIntentRegistration(
+ request,
+ identity,
+ new LocationPendingIntentTransport(mContext, pendingIntent),
+ permissionLevel));
+ }
+ }
+
+ public void unregisterLocationRequest(ILocationListener listener) {
+ synchronized (mLock) {
+ removeRegistration(listener.asBinder());
+ }
+ }
+
+ public void unregisterLocationRequest(PendingIntent pendingIntent) {
+ synchronized (mLock) {
+ removeRegistration(pendingIntent);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected void onRegister() {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ mSettingsHelper.addOnBackgroundThrottleIntervalChangedListener(
+ mBackgroundThrottleIntervalChangedListener);
+ mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
+ mBackgroundThrottlePackageWhitelistChangedListener);
+ mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
+ mLocationPackageBlacklistChangedListener);
+ mSettingsHelper.addOnIgnoreSettingsPackageWhitelistChangedListener(
+ mIgnoreSettingsPackageWhitelistChangedListener);
+ mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
+ mAppForegroundHelper.addListener(mAppForegroundChangedListener);
+ mLocationPowerSaveModeHelper.addListener(mLocationPowerSaveModeChangedListener);
+ mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected void onUnregister() {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ mSettingsHelper.removeOnBackgroundThrottleIntervalChangedListener(
+ mBackgroundThrottleIntervalChangedListener);
+ mSettingsHelper.removeOnBackgroundThrottlePackageWhitelistChangedListener(
+ mBackgroundThrottlePackageWhitelistChangedListener);
+ mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
+ mLocationPackageBlacklistChangedListener);
+ mSettingsHelper.removeOnIgnoreSettingsPackageWhitelistChangedListener(
+ mIgnoreSettingsPackageWhitelistChangedListener);
+ mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
+ mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
+ mLocationPowerSaveModeHelper.removeListener(mLocationPowerSaveModeChangedListener);
+ mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected void onRegistrationAdded(Object key, Registration registration) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ mLocationUsageLogger.logLocationApiUsage(
+ LocationStatsEnums.USAGE_STARTED,
+ LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
+ registration.getIdentity().getPackageName(),
+ registration.getRequest(),
+ key instanceof PendingIntent,
+ key instanceof IBinder,
+ /* geofence= */ null,
+ registration.isForeground());
+
+ mLocationRequestStatistics.startRequesting(
+ registration.getIdentity().getPackageName(),
+ registration.getIdentity().getAttributionTag(),
+ mName,
+ registration.getRequest().getInterval(),
+ registration.isForeground());
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected void onRegistrationRemoved(Object key, Registration registration) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ mLocationRequestStatistics.stopRequesting(
+ registration.getIdentity().getPackageName(),
+ registration.getIdentity().getAttributionTag(),
+ mName);
+
+ mLocationUsageLogger.logLocationApiUsage(
+ LocationStatsEnums.USAGE_ENDED,
+ LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
+ registration.getIdentity().getPackageName(),
+ registration.getRequest(),
+ key instanceof PendingIntent,
+ key instanceof IBinder,
+ /* geofence= */ null,
+ registration.isForeground());
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected boolean registerWithService(ProviderRequest mergedRequest) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ mProvider.setRequest(mergedRequest);
+ return true;
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected boolean reregisterWithService(ProviderRequest oldRequest,
+ ProviderRequest newRequest) {
+ return registerWithService(newRequest);
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected void unregisterWithService() {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+ mProvider.setRequest(ProviderRequest.EMPTY_REQUEST);
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected boolean isActive(Registration registration) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ CallerIdentity identity = registration.getIdentity();
+
+ if (!registration.isPermitted()) {
+ return false;
+ }
+
+ if (!registration.getRequest().isLocationSettingsIgnored()) {
+ if (!isEnabled(identity.getUserId())) {
+ return false;
+ }
+
+ switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
+ case LOCATION_MODE_FOREGROUND_ONLY:
+ if (!registration.isForeground()) {
+ return false;
+ }
+ break;
+ case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+ if (!GPS_PROVIDER.equals(mName)) {
+ break;
+ }
+ // fall through
+ case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+ // fall through
+ case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+ if (!mScreenInteractiveHelper.isInteractive()) {
+ return false;
+ }
+ break;
+ case LOCATION_MODE_NO_CHANGE:
+ // fall through
+ default:
+ break;
+ }
+ }
+
+ return !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
+ identity.getPackageName());
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected ProviderRequest mergeRequests(Collection<Registration> registrations) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ ProviderRequest.Builder providerRequest = new ProviderRequest.Builder();
+ // initialize the low power mode to true and set to false if any of the records requires
+ providerRequest.setLowPowerMode(true);
+
+ ArrayList<Registration> providerRegistrations = new ArrayList<>(registrations.size());
+ for (Registration registration : registrations) {
+ LocationRequest locationRequest = registration.getRequest();
+
+ if (locationRequest.isLocationSettingsIgnored()) {
+ providerRequest.setLocationSettingsIgnored(true);
+ }
+ if (!locationRequest.isLowPowerMode()) {
+ providerRequest.setLowPowerMode(false);
+ }
+
+ providerRequest.setInterval(
+ Math.min(locationRequest.getInterval(), providerRequest.getInterval()));
+ providerRegistrations.add(registration);
+ }
+
+ // collect contributing location requests
+ ArrayList<LocationRequest> providerRequests = new ArrayList<>(providerRegistrations.size());
+ final int registrationsSize = providerRegistrations.size();
+ for (int i = 0; i < registrationsSize; i++) {
+ providerRequests.add(providerRegistrations.get(i).getRequest());
+ }
+
+ providerRequest.setLocationRequests(providerRequests);
+
+ // calculate who to blame for power in a somewhat arbitrary fashion. we pick a threshold
+ // interval slightly higher that the minimum interval, and spread the blame across all
+ // contributing registrations under that threshold.
+ long thresholdIntervalMs = (providerRequest.getInterval() + 1000) * 3 / 2;
+ if (thresholdIntervalMs < 0) {
+ // handle overflow
+ thresholdIntervalMs = Long.MAX_VALUE;
+ }
+ for (int i = 0; i < registrationsSize; i++) {
+ LocationRequest request = providerRegistrations.get(i).getRequest();
+ if (request.getInterval() <= thresholdIntervalMs) {
+ providerRequest.getWorkSource().add(providerRegistrations.get(i).getWorkSource());
+ }
+ }
+
+ return providerRequest.build();
+ }
+
+ private void onUserChanged(int userId, int change) {
+ synchronized (mLock) {
+ switch (change) {
+ case UserListener.CURRENT_USER_CHANGED:
+ onEnabledChanged(userId);
+ break;
+ case UserListener.USER_STARTED:
+ onUserStarted(userId);
+ break;
+ case UserListener.USER_STOPPED:
+ onUserStopped(userId);
+ break;
+ }
+ }
+ }
+
+ private void onLocationEnabledChanged(int userId) {
+ synchronized (mLock) {
+ onEnabledChanged(userId);
+ }
+ }
+
+ private void onScreenInteractiveChanged(boolean screenInteractive) {
+ synchronized (mLock) {
+ switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
+ case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+ if (!GPS_PROVIDER.equals(mName)) {
+ break;
+ }
+ // fall through
+ case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+ // fall through
+ case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+ updateService();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private void onBackgroundThrottlePackageWhitelistChanged() {
+ synchronized (mLock) {
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
+ }
+ }
+
+ private void onBackgroundThrottleIntervalChanged() {
+ synchronized (mLock) {
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
+ }
+ }
+
+ private void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode) {
+ synchronized (mLock) {
+ // this is rare, just assume everything has changed to keep it simple
+ updateRegistrations(registration -> true);
+ }
+ }
+
+ private void onAppForegroundChanged(int uid, boolean foreground) {
+ synchronized (mLock) {
+ updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
+ }
+ }
+
+ private void onIgnoreSettingsWhitelistChanged() {
+ synchronized (mLock) {
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
+ }
+ }
+
+ private void onLocationPackageBlacklistChanged(int userId) {
+ synchronized (mLock) {
+ updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
+ }
+ }
+
+ private void onLocationPermissionsChanged(String packageName) {
+ synchronized (mLock) {
+ updateRegistrations(
+ registration -> registration.onLocationPermissionsChanged(packageName));
+ }
+ }
+
+ private void onLocationPermissionsChanged(int uid) {
+ synchronized (mLock) {
+ updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ public void onStateChanged(
+ AbstractLocationProvider.State oldState, AbstractLocationProvider.State newState) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ if (oldState.allowed != newState.allowed) {
+ onEnabledChanged(UserHandle.USER_ALL);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ public void onReportLocation(Location location) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ // don't validate mock locations
+ if (!location.isFromMockProvider()) {
+ if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+ Log.w(TAG, "blocking 0,0 location from " + mName + " provider");
+ return;
+ }
+ }
+
+ if (!location.isComplete()) {
+ Log.w(TAG, "blocking incomplete location from " + mName + " provider");
+ return;
+ }
+
+ // update last location
+ setLastLocation(location, UserHandle.USER_ALL);
+
+ // notify passive provider
+ if (mPassiveManager != null) {
+ mPassiveManager.updateLocation(location);
+ }
+
+ // attempt listener delivery
+ deliverToListeners(registration -> {
+ return registration.acceptLocationChange(location);
+ });
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ public void onReportLocation(List<Location> locations) {
+ if (!GPS_PROVIDER.equals(mName)) {
+ return;
+ }
+
+ mLocationManagerInternal.reportGnssBatchLocations(locations);
+ }
+
+ @GuardedBy("mLock")
+ private void onUserStarted(int userId) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ if (userId == UserHandle.USER_NULL) {
+ return;
+ }
+
+ if (userId == UserHandle.USER_ALL) {
+ // clear the user's prior enabled state to prevent broadcast of enabled state change
+ mEnabled.clear();
+ onEnabledChanged(UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkArgument(userId >= 0);
+
+ // clear the user's prior enabled state to prevent broadcast of enabled state change
+ mEnabled.delete(userId);
+ onEnabledChanged(userId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onUserStopped(int userId) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ if (userId == UserHandle.USER_NULL) {
+ return;
+ }
+
+ if (userId == UserHandle.USER_ALL) {
+ onEnabledChanged(UserHandle.USER_ALL);
+ mEnabled.clear();
+ mLastLocations.clear();
+ } else {
+ Preconditions.checkArgument(userId >= 0);
+
+ onEnabledChanged(userId);
+ mEnabled.delete(userId);
+ mLastLocations.remove(userId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onEnabledChanged(int userId) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ if (userId == UserHandle.USER_NULL) {
+ // used during initialization - ignore since many lower level operations (checking
+ // settings for instance) do not support the null user
+ return;
+ } else if (userId == UserHandle.USER_ALL) {
+ final int[] runningUserIds = mUserInfoHelper.getRunningUserIds();
+ for (int i = 0; i < runningUserIds.length; i++) {
+ onEnabledChanged(runningUserIds[i]);
+ }
+ return;
+ }
+
+ Preconditions.checkArgument(userId >= 0);
+
+ boolean enabled = mStarted
+ && mProvider.getState().allowed
+ && mUserInfoHelper.isCurrentUserId(userId)
+ && mSettingsHelper.isLocationEnabled(userId);
+
+ int index = mEnabled.indexOfKey(userId);
+ Boolean wasEnabled = index < 0 ? null : mEnabled.valueAt(index);
+ if (wasEnabled != null && wasEnabled == enabled) {
+ return;
+ }
+
+ mEnabled.put(userId, enabled);
+
+ if (D) {
+ Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
+ }
+
+ // clear last locations if we become disabled
+ if (!enabled) {
+ LastLocation lastLocation = mLastLocations.get(userId);
+ if (lastLocation != null) {
+ lastLocation.clearLocations();
+ }
+ }
+
+ // do not send change notifications if we just saw this user for the first time
+ if (wasEnabled != null) {
+ // fused and passive provider never get public updates for legacy reasons
+ if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) {
+ Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION)
+ .putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName)
+ .putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, enabled)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+ }
+
+ // send updates to internal listeners - since we expect listener changes to be more
+ // frequent than enabled changes, we use copy-on-read instead of copy-on-write
+ if (!mEnabledListeners.isEmpty()) {
+ ProviderEnabledListener[] listeners = mEnabledListeners.toArray(
+ new ProviderEnabledListener[0]);
+ FgThread.getHandler().post(() -> {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onProviderEnabledChanged(mName, userId, enabled);
+ }
+ });
+ }
+ }
+
+ // update active state of affected registrations
+ updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
+ }
+
+ public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
+ synchronized (mLock) {
+ ipw.print(mName + " provider");
+ if (mProvider.isMock()) {
+ ipw.print(" [mock]");
+ }
+ ipw.println(":");
+ ipw.increaseIndent();
+
+ super.dump(fd, ipw, args);
+
+ int[] userIds = mUserInfoHelper.getRunningUserIds();
+ for (int userId : userIds) {
+ if (userIds.length != 1) {
+ ipw.println("user " + userId + ":");
+ ipw.increaseIndent();
+ }
+ ipw.println("last location=" + getLastLocation(userId, PERMISSION_FINE, false));
+ ipw.println("enabled=" + isEnabled(userId));
+ if (userIds.length != 1) {
+ ipw.decreaseIndent();
+ }
+ }
+ }
+
+ mProvider.dump(fd, ipw, args);
+
+ ipw.decreaseIndent();
+ }
+
+ private static class LastLocation {
+
+ @Nullable private Location mFineLocation;
+ @Nullable private Location mCoarseLocation;
+ @Nullable private Location mFineBypassLocation;
+ @Nullable private Location mCoarseBypassLocation;
+
+ public void clearMock() {
+ if (mFineLocation != null && mFineLocation.isFromMockProvider()) {
+ mFineLocation = null;
+ mCoarseLocation = null;
+ }
+ if (mFineBypassLocation != null && mFineBypassLocation.isFromMockProvider()) {
+ mFineBypassLocation = null;
+ mCoarseBypassLocation = null;
+ }
+ }
+
+ public void clearLocations() {
+ mFineLocation = null;
+ mCoarseLocation = null;
+ }
+
+ @Nullable
+ public Location get(@PermissionLevel int permissionLevel, boolean ignoreLocationSettings) {
+ switch (permissionLevel) {
+ case PERMISSION_FINE:
+ if (ignoreLocationSettings) {
+ return mFineBypassLocation;
+ } else {
+ return mFineLocation;
+ }
+ case PERMISSION_COARSE:
+ if (ignoreLocationSettings) {
+ return mCoarseBypassLocation;
+ } else {
+ return mCoarseLocation;
+ }
+ default:
+ // shouldn't be possible to have a client added without location permissions
+ throw new AssertionError();
+ }
+ }
+
+ public void set(Location location, Location coarseLocation) {
+ mFineLocation = location;
+ mCoarseLocation = calculateNextCoarse(mCoarseLocation, coarseLocation);
+ }
+
+ public void setBypass(Location location, Location coarseLocation) {
+ mFineBypassLocation = location;
+ mCoarseBypassLocation = calculateNextCoarse(mCoarseBypassLocation, coarseLocation);
+ }
+
+ private Location calculateNextCoarse(@Nullable Location oldCoarse, Location newCoarse) {
+ if (oldCoarse == null) {
+ return newCoarse;
+ }
+ // update last coarse interval only if enough time has passed
+ long timeDeltaMs = NANOSECONDS.toMillis(newCoarse.getElapsedRealtimeNanos())
+ - NANOSECONDS.toMillis(oldCoarse.getElapsedRealtimeNanos());
+ if (timeDeltaMs > FASTEST_COARSE_INTERVAL_MS) {
+ return newCoarse;
+ } else {
+ return oldCoarse;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index c5bd5e6fed8c..fc88f147ea9d 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -40,9 +40,8 @@ public class MockProvider extends AbstractLocationProvider {
public MockProvider(ProviderProperties properties, CallerIdentity identity) {
// using a direct executor is ok because this class has no locks that could deadlock
- super(DIRECT_EXECUTOR);
+ super(DIRECT_EXECUTOR, identity);
setProperties(properties);
- setIdentity(identity);
}
/** Sets the allowed state of this mock provider. */
diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java
index 54af1c84d36b..d8d435aa4ac0 100644
--- a/services/core/java/com/android/server/location/MockableLocationProvider.java
+++ b/services/core/java/com/android/server/location/MockableLocationProvider.java
@@ -241,7 +241,6 @@ public class MockableLocationProvider extends AbstractLocationProvider {
if (getState().properties != null) {
pw.println("properties=" + getState().properties);
}
- pw.println("request=" + mRequest);
}
if (provider != null) {
diff --git a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
new file mode 100644
index 000000000000..d4999ab8be0a
--- /dev/null
+++ b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationManager;
+import android.os.Binder;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.location.util.Injector;
+
+class PassiveLocationProviderManager extends LocationProviderManager {
+
+ PassiveLocationProviderManager(Context context, Injector injector) {
+ super(context, injector, LocationManager.PASSIVE_PROVIDER, null);
+ }
+
+ @Override
+ public void setRealProvider(AbstractLocationProvider provider) {
+ Preconditions.checkArgument(provider instanceof PassiveProvider);
+ super.setRealProvider(provider);
+ }
+
+ @Override
+ public void setMockProvider(@Nullable MockProvider provider) {
+ if (provider != null) {
+ throw new IllegalArgumentException("Cannot mock the passive provider");
+ }
+ }
+
+ public void updateLocation(Location location) {
+ synchronized (mLock) {
+ PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider();
+ Preconditions.checkState(passiveProvider != null);
+
+ long identity = Binder.clearCallingIdentity();
+ try {
+ passiveProvider.updateLocation(location);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index 0b7968be484b..1b599b026c38 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -16,17 +16,21 @@
package com.android.server.location.gnss;
+import static android.location.LocationManager.GPS_PROVIDER;
+
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.gnss.GnssManagerService.TAG;
import android.annotation.Nullable;
import android.location.LocationManagerInternal;
+import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.util.identity.CallerIdentity;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Process;
import android.util.ArraySet;
+import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.location.listeners.BinderListenerRegistration;
import com.android.server.location.listeners.ListenerMultiplexer;
@@ -161,8 +165,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
protected final LocationManagerInternal mLocationManagerInternal;
private final UserListener mUserChangedListener = this::onUserChanged;
- private final SettingsHelper.UserSettingChangedListener mLocationEnabledChangedListener =
- this::onLocationEnabledChanged;
+ private final ProviderEnabledListener mProviderEnabledChangedListener =
+ this::onProviderEnabledChanged;
private final SettingsHelper.GlobalSettingChangedListener
mBackgroundThrottlePackageWhitelistChangedListener =
this::onBackgroundThrottlePackageWhitelistChanged;
@@ -233,12 +237,11 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
CallerIdentity identity = registration.getIdentity();
- // TODO: this should be checking if the gps provider is enabled, not if location is enabled,
- // but this is the same for now.
return registration.isPermitted()
&& (registration.isForeground() || isBackgroundRestrictionExempt(identity))
&& mUserInfoHelper.isCurrentUserId(identity.getUserId())
- && mSettingsHelper.isLocationEnabled(identity.getUserId())
+ && mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER,
+ identity.getUserId())
&& !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
identity.getPackageName());
}
@@ -263,7 +266,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
mUserInfoHelper.addListener(mUserChangedListener);
- mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
+ mLocationManagerInternal.addProviderEnabledListener(GPS_PROVIDER,
+ mProviderEnabledChangedListener);
mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
@@ -279,7 +283,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
mUserInfoHelper.removeListener(mUserChangedListener);
- mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
+ mLocationManagerInternal.removeProviderEnabledListener(GPS_PROVIDER,
+ mProviderEnabledChangedListener);
mSettingsHelper.removeOnBackgroundThrottlePackageWhitelistChangedListener(
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
@@ -294,7 +299,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
}
- private void onLocationEnabledChanged(int userId) {
+ private void onProviderEnabledChanged(String provider, int userId, boolean enabled) {
+ Preconditions.checkState(GPS_PROVIDER.equals(provider));
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index 528cf8acd5b3..f94de9be0cfe 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -104,18 +104,18 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
* should return true if a matching call to {@link #unregisterWithService()} is required to
* unregister (ie, if registration succeeds).
*
- * @see #reregisterWithService(Object)
+ * @see #reregisterWithService(Object, Object)
*/
- protected abstract boolean registerWithService(TMergedRequest mergedRequest);
+ protected abstract boolean registerWithService(TMergedRequest newRequest);
/**
* Invoked when the service already has a request, and it is being replaced with a new request.
* The default implementation unregisters first, then registers with the new merged request, but
* this may be overridden by subclasses in order to reregister more efficiently.
*/
- protected boolean reregisterWithService(TMergedRequest mergedRequest) {
+ protected boolean reregisterWithService(TMergedRequest oldRequest, TMergedRequest newRequest) {
unregisterWithService();
- return registerWithService(mergedRequest);
+ return registerWithService(newRequest);
}
/**
@@ -368,6 +368,7 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
mCurrentRequest = null;
if (mServiceRegistered) {
mServiceRegistered = false;
+ mCurrentRequest = null;
unregisterWithService();
}
return;
@@ -376,11 +377,15 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
TMergedRequest merged = mergeRequests(actives);
if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) {
if (mServiceRegistered) {
- mServiceRegistered = reregisterWithService(merged);
+ mServiceRegistered = reregisterWithService(mCurrentRequest, merged);
} else {
mServiceRegistered = registerWithService(merged);
}
- mCurrentRequest = merged;
+ if (mServiceRegistered) {
+ mCurrentRequest = merged;
+ } else {
+ mCurrentRequest = null;
+ }
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -389,29 +394,6 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
}
/**
- * Evaluates the given predicate for all registrations, and forces an {@link #updateService()}
- * if any predicate returns true for an active registration. The predicate will always be
- * evaluated for all registrations, even inactive registrations, or if it has already returned
- * true for a prior registration.
- */
- protected final void updateService(Predicate<TRegistration> predicate) {
- synchronized (mRegistrations) {
- boolean updateService = false;
- final int size = mRegistrations.size();
- for (int i = 0; i < size; i++) {
- TRegistration registration = mRegistrations.valueAt(i);
- if (predicate.test(registration) && registration.isActive()) {
- updateService = true;
- }
- }
-
- if (updateService) {
- updateService();
- }
- }
- }
-
- /**
* Begins buffering calls to {@link #updateService()} until {@link UpdateServiceLock#close()}
* is called. This is useful to prevent extra work when combining multiple calls (for example,
* buffering {@code updateService()} until after multiple adds/removes/updates occur.
diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
index 0bdd1316d265..ac56c51568be 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
@@ -24,6 +24,7 @@ import android.annotation.Nullable;
import android.location.util.identity.CallerIdentity;
import android.os.Process;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.listeners.ListenerExecutor;
import com.android.server.FgThread;
@@ -39,6 +40,9 @@ import java.util.concurrent.Executor;
*/
public class ListenerRegistration<TRequest, TListener> implements ListenerExecutor {
+ @VisibleForTesting
+ public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
+
private final Executor mExecutor;
private final @Nullable TRequest mRequest;
private final CallerIdentity mIdentity;
@@ -55,9 +59,9 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
// there's a slight loophole here for pending intents - pending intent callbacks can
// always be run on the direct executor since they're always asynchronous, but honestly
// you shouldn't be using pending intent callbacks within the same process anyways
- mExecutor = FgThread.getExecutor();
+ mExecutor = IN_PROCESS_EXECUTOR;
} else {
- mExecutor = DIRECT_EXECUTOR;
+ mExecutor = DIRECT_EXECUTOR;
}
mRequest = request;
@@ -73,7 +77,7 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
/**
* Returns the request associated with this listener, or null if one wasn't supplied.
*/
- public final @Nullable TRequest getRequest() {
+ public @Nullable TRequest getRequest() {
return mRequest;
}
@@ -107,7 +111,7 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
*/
protected void onInactive() {}
- final boolean isActive() {
+ public final boolean isActive() {
return mActive;
}
@@ -120,7 +124,7 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
return false;
}
- final boolean isRegistered() {
+ public final boolean isRegistered() {
return mListener != null;
}
diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
index 6a815ead9f9f..0698cca903f0 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -39,7 +39,7 @@ public abstract class RemovableListenerRegistration<TRequest, TListener> extends
protected RemovableListenerRegistration(String tag, @Nullable TRequest request,
CallerIdentity callerIdentity, TListener listener) {
super(request, callerIdentity, listener);
- mTag = tag;
+ mTag = Objects.requireNonNull(tag);
}
/**
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 44eb8285c7db..a398961db4c3 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.mockingservicestests">
+ <uses-sdk android:targetSdkVersion="30" />
+
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
new file mode 100644
index 000000000000..1cb004a6dc1e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
@@ -0,0 +1,971 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.WINDOW_EXACT;
+import static android.app.AppOpsManager.OP_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
+import static android.location.Criteria.ACCURACY_COARSE;
+import static android.location.Criteria.ACCURACY_FINE;
+import static android.location.Criteria.POWER_HIGH;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
+
+import static androidx.test.ext.truth.location.LocationSubject.assertThat;
+
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
+import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
+import static com.android.server.location.LocationUtils.createLocation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
+import android.content.Context;
+import android.location.ILocationCallback;
+import android.location.ILocationListener;
+import android.location.Location;
+import android.location.LocationManagerInternal;
+import android.location.LocationManagerInternal.ProviderEnabledListener;
+import android.location.LocationRequest;
+import android.location.util.identity.CallerIdentity;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.ICancellationSignal;
+import android.os.IRemoteCallback;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.WorkSource;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.location.listeners.ListenerRegistration;
+import com.android.server.location.util.FakeUserInfoHelper;
+import com.android.server.location.util.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LocationProviderManagerTest {
+
+ private static final String TAG = "LocationProviderManagerTest";
+
+ private static final long TIMEOUT_MS = 1000;
+
+ private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+ private static final int OTHER_USER = CURRENT_USER + 10;
+
+ private static final String NAME = "test";
+ private static final ProviderProperties PROPERTIES = new ProviderProperties(false, false, false,
+ false, true, true, true, POWER_HIGH, ACCURACY_FINE);
+ private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1,
+ "mypackage",
+ "attribution");
+
+ private Random mRandom;
+
+ @Mock
+ private LocationManagerInternal mInternal;
+ @Mock
+ private Context mContext;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private PowerManager mPowerManager;
+ @Mock
+ private PowerManager.WakeLock mWakeLock;
+
+ private TestInjector mInjector;
+ private PassiveLocationProviderManager mPassive;
+ private TestProvider mProvider;
+
+ private LocationProviderManager mManager;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+
+ long seed = System.currentTimeMillis();
+ Log.i(TAG, "location random seed: " + seed);
+
+ mRandom = new Random(seed);
+
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+
+ doReturn("android").when(mContext).getPackageName();
+ doReturn(mAlarmManager).when(mContext).getSystemService(AlarmManager.class);
+ doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
+ doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
+
+ mInjector = new TestInjector();
+ mInjector.getUserInfoHelper().startUser(OTHER_USER);
+
+ mPassive = new PassiveLocationProviderManager(mContext, mInjector);
+ mPassive.startManager();
+ mPassive.setRealProvider(new PassiveProvider(mContext));
+
+ mProvider = new TestProvider(PROPERTIES, IDENTITY);
+ mProvider.setProviderAllowed(true);
+
+ mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive);
+ mManager.startManager();
+ mManager.setRealProvider(mProvider);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+
+ // some test failures may leave the fg thread stuck, interrupt until we get out of it
+ CountDownLatch latch = new CountDownLatch(1);
+ FgThread.getExecutor().execute(latch::countDown);
+ int count = 0;
+ while (++count < 10 && !latch.await(10, TimeUnit.MILLISECONDS)) {
+ FgThread.get().getLooper().getThread().interrupt();
+ }
+ }
+
+ @Test
+ public void testProperties() {
+ assertThat(mManager.getName()).isEqualTo(NAME);
+ assertThat(mManager.getProperties()).isEqualTo(PROPERTIES);
+ assertThat(mManager.getIdentity()).isEqualTo(IDENTITY);
+ assertThat(mManager.hasProvider()).isTrue();
+
+ ProviderProperties newProperties = new ProviderProperties(true, true, true,
+ true, false, false, false, POWER_HIGH, ACCURACY_COARSE);
+ mProvider.setProperties(newProperties);
+ assertThat(mManager.getProperties()).isEqualTo(newProperties);
+
+ CallerIdentity newIdentity = CallerIdentity.forTest(OTHER_USER, 1, "otherpackage",
+ "otherattribution");
+ mProvider.setIdentity(newIdentity);
+ assertThat(mManager.getIdentity()).isEqualTo(newIdentity);
+
+ mManager.setRealProvider(null);
+ assertThat(mManager.hasProvider()).isFalse();
+ }
+
+ @Test
+ public void testRemoveProvider() {
+ mManager.setRealProvider(null);
+ assertThat(mManager.hasProvider()).isFalse();
+ }
+
+ @Test
+ public void testIsEnabled() {
+ assertThat(mManager.isEnabled(CURRENT_USER)).isTrue();
+
+ mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
+ assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
+
+ mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
+ mProvider.setAllowed(false);
+ assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
+
+ mProvider.setAllowed(true);
+ mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
+ assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
+ assertThat(mManager.isEnabled(OTHER_USER)).isTrue();
+
+ mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
+ assertThat(mManager.isEnabled(CURRENT_USER)).isTrue();
+ assertThat(mManager.isEnabled(OTHER_USER)).isFalse();
+ }
+
+ @Test
+ public void testIsEnabledListener() {
+ ProviderEnabledListener listener = mock(ProviderEnabledListener.class);
+ mManager.addEnabledListener(listener);
+ verify(listener, never()).onProviderEnabledChanged(anyString(), anyInt(), anyBoolean());
+
+ mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
+ verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, CURRENT_USER,
+ false);
+
+ mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
+ verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, CURRENT_USER,
+ true);
+
+ mProvider.setAllowed(false);
+ verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, CURRENT_USER,
+ false);
+
+ mProvider.setAllowed(true);
+ verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, CURRENT_USER,
+ true);
+
+ mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
+ verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, CURRENT_USER,
+ false);
+ verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, OTHER_USER,
+ true);
+
+ mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
+ verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, CURRENT_USER,
+ true);
+ verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, OTHER_USER,
+ false);
+
+ mManager.removeEnabledListener(listener);
+ mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
+ verifyNoMoreInteractions(listener);
+ }
+
+ @Test
+ public void testGetLastLocation_Fine() {
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+
+ Location loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
+ }
+
+ @Test
+ public void testGetLastLocation_Coarse() {
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+
+ Location loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ Location coarse = mManager.getLastLocation(request, IDENTITY, PERMISSION_COARSE);
+ assertThat(coarse).isNotEqualTo(loc);
+ assertThat(coarse).isNearby(loc, 5000);
+ }
+
+ @Test
+ public void testGetLastLocation_Bypass() {
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+ LocationRequest bypassRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+ false).setLocationSettingsIgnored(true);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+ assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isNull();
+
+ Location loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
+ assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
+ loc);
+
+ mProvider.setProviderAllowed(false);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+ assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
+ loc);
+
+ loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+ assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
+ loc);
+
+ mProvider.setProviderAllowed(true);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+ assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
+ loc);
+
+ loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
+ assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
+ loc);
+ }
+
+ @Test
+ public void testGetLastLocation_ClearOnMockRemoval() {
+ MockProvider mockProvider = new MockProvider(PROPERTIES, IDENTITY);
+ mockProvider.setAllowed(true);
+ mManager.setMockProvider(mockProvider);
+
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+ Location loc = createLocation(NAME, mRandom);
+ mockProvider.setProviderLocation(loc);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
+
+ mManager.setMockProvider(null);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+ }
+
+ @Test
+ public void testInjectLastLocation() {
+ Location loc1 = createLocation(NAME, mRandom);
+ mManager.injectLastLocation(loc1, CURRENT_USER);
+
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc1);
+
+ Location loc2 = createLocation(NAME, mRandom);
+ mManager.injectLastLocation(loc2, CURRENT_USER);
+
+ assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc1);
+ }
+
+ @Test
+ public void testPassive_Listener() throws Exception {
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(PASSIVE_PROVIDER, 0,
+ 0, false);
+ mPassive.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ Location loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+
+ ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
+ verify(listener).onLocationChanged(locationCaptor.capture(),
+ nullable(IRemoteCallback.class));
+ assertThat(locationCaptor.getValue()).isEqualTo(loc);
+ }
+
+ @Test
+ public void testPassive_LastLocation() {
+ Location loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(PASSIVE_PROVIDER, 0,
+ 0, false);
+ assertThat(mPassive.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
+ }
+
+ @Test
+ public void testRegisterListener() throws Exception {
+ ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
+
+ ILocationListener listener = createMockLocationListener();
+ mManager.registerLocationRequest(
+ LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), IDENTITY,
+ PERMISSION_FINE, listener);
+
+ Location loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ verify(listener, times(1)).onLocationChanged(locationCaptor.capture(),
+ nullable(IRemoteCallback.class));
+ assertThat(locationCaptor.getValue()).isEqualTo(loc);
+
+ mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
+ verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, false);
+ loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ verify(listener, times(1)).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+
+ mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
+ verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, true);
+
+ mProvider.setAllowed(false);
+ verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, false);
+ loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ verify(listener, times(1)).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+
+ mProvider.setAllowed(true);
+ verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, true);
+
+ mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
+ verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, false);
+ loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ verify(listener, times(1)).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+
+ mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
+ verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, true);
+
+ loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ verify(listener, times(2)).onLocationChanged(locationCaptor.capture(),
+ nullable(IRemoteCallback.class));
+ assertThat(locationCaptor.getValue()).isEqualTo(loc);
+ }
+
+ @Test
+ public void testRegisterListener_SameProcess() throws Exception {
+ ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
+
+ CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage",
+ "attribution");
+
+ ILocationListener listener = createMockLocationListener();
+ mManager.registerLocationRequest(
+ LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), identity,
+ PERMISSION_FINE, listener);
+
+ Location loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ verify(listener, timeout(TIMEOUT_MS).times(1)).onLocationChanged(locationCaptor.capture(),
+ nullable(IRemoteCallback.class));
+ assertThat(locationCaptor.getValue()).isEqualTo(loc);
+ }
+
+ @Test
+ public void testRegisterListener_Unregister() throws Exception {
+ ILocationListener listener = createMockLocationListener();
+ mManager.registerLocationRequest(
+ LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), IDENTITY,
+ PERMISSION_FINE, listener);
+ mManager.unregisterLocationRequest(listener);
+
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ verify(listener, never()).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+
+ mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
+ verify(listener, after(TIMEOUT_MS).never()).onProviderEnabledChanged(NAME, false);
+ }
+
+ @Test
+ public void testRegisterListener_Unregister_SameProcess() throws Exception {
+ CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage",
+ "attribution");
+
+ ILocationListener listener = createMockLocationListener();
+ mManager.registerLocationRequest(
+ LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), identity,
+ PERMISSION_FINE, listener);
+
+ CountDownLatch blocker = new CountDownLatch(1);
+ ListenerRegistration.IN_PROCESS_EXECUTOR.execute(() -> {
+ try {
+ blocker.await();
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ });
+
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ mManager.unregisterLocationRequest(listener);
+ blocker.countDown();
+ verify(listener, after(TIMEOUT_MS).never()).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+ }
+
+ @Test
+ public void testRegisterListener_NumUpdates() throws Exception {
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+ false).setNumUpdates(5);
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ verify(listener, times(5)).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+ }
+
+ @Test
+ public void testRegisterListener_ExpiringAlarm() throws Exception {
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+ false).setExpireIn(5000);
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+ long baseTimeMs = SystemClock.elapsedRealtime();
+
+ ArgumentCaptor<Long> timeoutCapture = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
+ OnAlarmListener.class);
+ verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), timeoutCapture.capture(),
+ eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
+ any(WorkSource.class));
+
+ assertThat(timeoutCapture.getValue()).isAtLeast(baseTimeMs + 4000);
+ assertThat(timeoutCapture.getValue()).isAtMost(baseTimeMs + 5000);
+ listenerCapture.getValue().onAlarm();
+
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ verify(listener, never()).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+ }
+
+ @Test
+ public void testRegisterListener_ExpiringNoAlarm() throws Exception {
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+ false).setExpireIn(25);
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ Thread.sleep(25);
+
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ verify(listener, never()).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+ }
+
+ @Test
+ public void testRegisterListener_AlreadyExpired() throws Exception {
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+ false).setExpireIn(-1);
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ verify(listener, never()).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+ }
+
+ @Test
+ public void testRegisterListener_FastestInterval() throws Exception {
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 5000, 0,
+ false).setFastestInterval(5000);
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ verify(listener, times(1)).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+ }
+
+ @Test
+ public void testRegisterListener_SmallestDisplacement() throws Exception {
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 5000, 0,
+ false).setSmallestDisplacement(1f);
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ Location loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ mProvider.setProviderLocation(loc);
+
+ verify(listener, times(1)).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+ }
+
+ @Test
+ public void testRegisterListener_NoteOpFailure() throws Exception {
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ mInjector.getAppOpsHelper().setAppOpAllowed(OP_FINE_LOCATION, IDENTITY.getPackageName(),
+ false);
+
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ verify(listener, never()).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+ }
+
+ @Test
+ public void testRegisterListener_Wakelock() throws Exception {
+ CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage",
+ "attribution");
+
+ ILocationListener listener = createMockLocationListener();
+ mManager.registerLocationRequest(
+ LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), identity,
+ PERMISSION_FINE, listener);
+
+ CountDownLatch blocker = new CountDownLatch(1);
+ ListenerRegistration.IN_PROCESS_EXECUTOR.execute(() -> {
+ try {
+ blocker.await();
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ });
+
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ verify(mWakeLock).acquire(anyLong());
+ verify(mWakeLock, never()).release();
+
+ blocker.countDown();
+ verify(listener, timeout(TIMEOUT_MS)).onLocationChanged(any(Location.class),
+ nullable(IRemoteCallback.class));
+ verify(mWakeLock).acquire(anyLong());
+ verify(mWakeLock, timeout(TIMEOUT_MS)).release();
+ }
+
+ @Test
+ public void testGetCurrentLocation() throws Exception {
+ ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
+
+ ILocationCallback listener = createMockGetCurrentLocationListener();
+ LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+ false);
+ ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+ mManager.getCurrentLocation(locationRequest, IDENTITY,
+ PERMISSION_FINE, cancellationSignal, listener);
+
+ Location loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ verify(listener, times(1)).onLocation(locationCaptor.capture());
+ assertThat(locationCaptor.getValue()).isEqualTo(loc);
+ }
+
+ @Test
+ public void testGetCurrentLocation_Cancel() throws Exception {
+ ILocationCallback listener = createMockGetCurrentLocationListener();
+ LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+ false);
+ ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+ mManager.getCurrentLocation(locationRequest, IDENTITY,
+ PERMISSION_FINE, cancellationSignal, listener);
+
+ cancellationSignal.cancel();
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ verify(listener, never()).onLocation(nullable(Location.class));
+ }
+
+ @Test
+ public void testGetCurrentLocation_ProviderDisabled() throws Exception {
+ ILocationCallback listener = createMockGetCurrentLocationListener();
+ LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+ false);
+ ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+ mManager.getCurrentLocation(locationRequest, IDENTITY,
+ PERMISSION_FINE, cancellationSignal, listener);
+
+ mProvider.setProviderAllowed(false);
+ mProvider.setProviderAllowed(true);
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ verify(listener, times(1)).onLocation(isNull());
+ }
+
+ @Test
+ public void testGetCurrentLocation_ProviderAlreadyDisabled() throws Exception {
+ mProvider.setProviderAllowed(false);
+
+ ILocationCallback listener = createMockGetCurrentLocationListener();
+ LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+ false);
+ ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+ mManager.getCurrentLocation(locationRequest, IDENTITY,
+ PERMISSION_FINE, cancellationSignal, listener);
+
+ mProvider.setProviderAllowed(true);
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+ verify(listener, times(1)).onLocation(isNull());
+ }
+
+ @Test
+ public void testGetCurrentLocation_LastLocation() throws Exception {
+ ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
+
+ Location loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+
+ ILocationCallback listener = createMockGetCurrentLocationListener();
+ LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+ false);
+ ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+ mManager.getCurrentLocation(locationRequest, IDENTITY,
+ PERMISSION_FINE, cancellationSignal, listener);
+
+ verify(listener, times(1)).onLocation(locationCaptor.capture());
+ assertThat(locationCaptor.getValue()).isEqualTo(loc);
+ }
+
+ @Test
+ public void testGetCurrentLocation_Timeout() throws Exception {
+ ILocationCallback listener = createMockGetCurrentLocationListener();
+ LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+ false);
+ ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+ mManager.getCurrentLocation(locationRequest, IDENTITY,
+ PERMISSION_FINE, cancellationSignal, listener);
+
+ ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
+ OnAlarmListener.class);
+ verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), anyLong(),
+ eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
+ any(WorkSource.class));
+ listenerCapture.getValue().onAlarm();
+
+ verify(listener, times(1)).onLocation(isNull());
+ }
+
+ @Test
+ public void testLocationMonitoring() {
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+ IDENTITY.getPackageName())).isFalse();
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+ IDENTITY.getPackageName())).isFalse();
+
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+ IDENTITY.getPackageName())).isTrue();
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+ IDENTITY.getPackageName())).isTrue();
+
+ mInjector.getAppForegroundHelper().setAppForeground(IDENTITY.getUid(), false);
+
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+ IDENTITY.getPackageName())).isTrue();
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+ IDENTITY.getPackageName())).isFalse();
+
+ mManager.unregisterLocationRequest(listener);
+
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+ IDENTITY.getPackageName())).isFalse();
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+ IDENTITY.getPackageName())).isFalse();
+ }
+
+ @Test
+ public void testProviderRequest() {
+ assertThat(mProvider.getRequest().reportLocation).isFalse();
+ assertThat(mProvider.getRequest().locationRequests).isEmpty();
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0, false);
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+ assertThat(mProvider.getRequest().reportLocation).isTrue();
+ assertThat(mProvider.getRequest().locationRequests).containsExactly(request1);
+ assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
+ assertThat(mProvider.getRequest().interval).isEqualTo(5);
+ assertThat(mProvider.getRequest().lowPowerMode).isFalse();
+ assertThat(mProvider.getRequest().workSource).isNotNull();
+
+ ILocationListener listener2 = createMockLocationListener();
+ LocationRequest request2 = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0,
+ false).setLowPowerMode(true);
+ mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
+
+ assertThat(mProvider.getRequest().reportLocation).isTrue();
+ assertThat(mProvider.getRequest().locationRequests).containsExactly(request1, request2);
+ assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
+ assertThat(mProvider.getRequest().interval).isEqualTo(1);
+ assertThat(mProvider.getRequest().lowPowerMode).isFalse();
+ assertThat(mProvider.getRequest().workSource).isNotNull();
+
+ mManager.unregisterLocationRequest(listener1);
+
+ assertThat(mProvider.getRequest().reportLocation).isTrue();
+ assertThat(mProvider.getRequest().locationRequests).containsExactly(request2);
+ assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
+ assertThat(mProvider.getRequest().interval).isEqualTo(1);
+ assertThat(mProvider.getRequest().lowPowerMode).isTrue();
+ assertThat(mProvider.getRequest().workSource).isNotNull();
+
+ mManager.unregisterLocationRequest(listener2);
+
+ assertThat(mProvider.getRequest().reportLocation).isFalse();
+ assertThat(mProvider.getRequest().locationRequests).isEmpty();
+ }
+
+ @Test
+ public void testProviderRequest_BackgroundThrottle() {
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0, false);
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+ assertThat(mProvider.getRequest().interval).isEqualTo(5);
+
+ mInjector.getAppForegroundHelper().setAppForeground(IDENTITY.getUid(), false);
+ assertThat(mProvider.getRequest().interval).isEqualTo(
+ mInjector.getSettingsHelper().getBackgroundThrottleIntervalMs());
+ }
+
+ @Test
+ public void testProviderRequest_IgnoreLocationSettings() {
+ mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
+ Collections.singleton(IDENTITY.getPackageName()));
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0, false);
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+ assertThat(mProvider.getRequest().reportLocation).isTrue();
+ assertThat(mProvider.getRequest().interval).isEqualTo(5);
+ assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
+
+ ILocationListener listener2 = createMockLocationListener();
+ LocationRequest request2 = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0,
+ false).setLocationSettingsIgnored(true);
+ mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
+
+ assertThat(mProvider.getRequest().reportLocation).isTrue();
+ assertThat(mProvider.getRequest().interval).isEqualTo(1);
+ assertThat(mProvider.getRequest().locationSettingsIgnored).isTrue();
+ }
+
+ @Test
+ public void testProviderRequest_IgnoreLocationSettings_ProviderDisabled() {
+ mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
+ Collections.singleton(IDENTITY.getPackageName()));
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0, false);
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+ ILocationListener listener2 = createMockLocationListener();
+ LocationRequest request2 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0,
+ false).setLocationSettingsIgnored(true);
+ mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
+
+ mInjector.getSettingsHelper().setLocationEnabled(false, IDENTITY.getUserId());
+
+ assertThat(mProvider.getRequest().reportLocation).isTrue();
+ assertThat(mProvider.getRequest().locationRequests).containsExactly(request2);
+ assertThat(mProvider.getRequest().interval).isEqualTo(5);
+ assertThat(mProvider.getRequest().locationSettingsIgnored).isTrue();
+ }
+
+ @Test
+ public void testProviderRequest_IgnoreLocationSettings_NoAllowlist() {
+ mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
+ Collections.singleton(IDENTITY.getPackageName()));
+
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0,
+ false).setLocationSettingsIgnored(true);
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(Collections.emptySet());
+
+ assertThat(mProvider.getRequest().reportLocation).isTrue();
+ assertThat(mProvider.getRequest().interval).isEqualTo(1);
+ assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
+ }
+
+ @Test
+ public void testProviderRequest_BackgroundThrottle_IgnoreLocationSettings() {
+ mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
+ Collections.singleton(IDENTITY.getPackageName()));
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0,
+ false).setLocationSettingsIgnored(true);
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+ assertThat(mProvider.getRequest().interval).isEqualTo(5);
+
+ mInjector.getAppForegroundHelper().setAppForeground(IDENTITY.getUid(), false);
+ assertThat(mProvider.getRequest().interval).isEqualTo(5);
+ }
+
+ private ILocationListener createMockLocationListener() {
+ return spy(new ILocationListener.Stub() {
+ @Override
+ public void onLocationChanged(Location location, IRemoteCallback onCompleteCallback) {
+ if (onCompleteCallback != null) {
+ try {
+ onCompleteCallback.sendResult(null);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ public void onProviderEnabledChanged(String provider, boolean enabled) {
+ }
+ });
+ }
+
+ private ILocationCallback createMockGetCurrentLocationListener() {
+ return spy(new ILocationCallback.Stub() {
+ @Override
+ public void onLocation(Location location) {
+ }
+ });
+ }
+
+ private static class TestProvider extends AbstractLocationProvider {
+
+ private ProviderRequest mProviderRequest = ProviderRequest.EMPTY_REQUEST;
+
+ TestProvider(ProviderProperties properties, CallerIdentity identity) {
+ super(DIRECT_EXECUTOR, identity);
+ setProperties(properties);
+ }
+
+ public void setProviderAllowed(boolean allowed) {
+ setAllowed(allowed);
+ }
+
+ public void setProviderLocation(Location l) {
+ reportLocation(new Location(l));
+ }
+
+ public ProviderRequest getRequest() {
+ return mProviderRequest;
+ }
+
+ @Override
+ public void onSetRequest(ProviderRequest request) {
+ mProviderRequest = request;
+ }
+
+ @Override
+ protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ }
+ }
+}