diff options
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) { + } + } +} |