summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xapi/system-current.txt2
-rw-r--r--api/test-current.txt2
-rw-r--r--core/java/android/provider/Settings.java11
-rw-r--r--location/java/android/location/ILocationManager.aidl2
-rw-r--r--location/java/android/location/Location.java13
-rw-r--r--location/java/android/location/LocationManager.java5
-rw-r--r--location/java/android/location/LocationManagerInternal.java9
-rw-r--r--location/lib/java/com/android/location/provider/LocationProviderBase.java13
-rw-r--r--services/core/java/com/android/server/LocationManagerService.java757
-rw-r--r--services/core/java/com/android/server/location/LocationFudger.java416
-rw-r--r--services/core/java/com/android/server/location/MockableLocationProvider.java4
-rw-r--r--services/core/java/com/android/server/location/SettingsHelper.java42
-rw-r--r--services/core/java/com/android/server/location/UserInfoHelper.java118
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssManagerService.java14
-rw-r--r--services/tests/mockingservicestests/Android.bp1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java165
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationUtils.java51
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java86
18 files changed, 933 insertions, 778 deletions
diff --git a/api/system-current.txt b/api/system-current.txt
index 8adc66394a52..d0da42c49226 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4136,7 +4136,7 @@ package android.location {
method public boolean isComplete();
method public void makeComplete();
method public void setIsFromMockProvider(boolean);
- field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
+ field @Deprecated public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
}
public class LocationManager {
diff --git a/api/test-current.txt b/api/test-current.txt
index 8598c167cab0..225f3dc3ee5c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1338,7 +1338,7 @@ package android.location {
public class Location implements android.os.Parcelable {
method public void makeComplete();
- field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
+ field @Deprecated public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
}
public class LocationManager {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 713cfc64afe7..44c2fbbd9ce7 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6257,16 +6257,19 @@ public final class Settings {
* @hide
*/
public static final String LOCATION_CHANGER = "location_changer";
+
/**
* The location changer is unknown or unable to detect.
* @hide
*/
public static final int LOCATION_CHANGER_UNKNOWN = 0;
+
/**
* Location settings in system settings.
* @hide
*/
public static final int LOCATION_CHANGER_SYSTEM_SETTINGS = 1;
+
/**
* The location icon in drop down notification drawer.
* @hide
@@ -6314,6 +6317,14 @@ public final class Settings {
public static final int LOCATION_MODE_ON = LOCATION_MODE_HIGH_ACCURACY;
/**
+ * The accuracy in meters used for coarsening location for clients with only the coarse
+ * location permission.
+ *
+ * @hide
+ */
+ public static final String LOCATION_COARSE_ACCURACY_M = "locationCoarseAccuracy";
+
+ /**
* A flag containing settings used for biometric weak
* @hide
*/
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index a99e68fbb7b6..7de4d89dfa4f 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -93,7 +93,7 @@ interface ILocationManager
boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName);
void flushGnssBatch(String packageName);
boolean stopGnssBatch();
- boolean injectLocation(in Location location);
+ void injectLocation(in Location location);
@UnsupportedAppUsage
List<String> getAllProviders();
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index eb76c29301c6..6724324bfcb9 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -64,23 +64,18 @@ public class Location implements Parcelable {
public static final int FORMAT_SECONDS = 2;
/**
- * Bundle key for a version of the location that has been fed through
- * LocationFudger. Allows location providers to flag locations as being
- * safe for use with ACCESS_COARSE_LOCATION permission.
- *
- * @hide
- */
- public static final String EXTRA_COARSE_LOCATION = "coarseLocation";
-
- /**
* Bundle key for a version of the location containing no GPS data.
* Allows location providers to flag locations as being safe to
* feed to LocationFudger.
*
* @hide
+ * @deprecated As of Android R, this extra is longer in use, since it is not necessary to keep
+ * gps locations separate from other locations for coarsening. Providers that do not need to
+ * support platforms below Android R should not use this constant.
*/
@TestApi
@SystemApi
+ @Deprecated
public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
/**
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 8ae967fe79c2..0fec611bc522 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -1215,7 +1215,7 @@ public class LocationManager {
* the first fix.
*
* @param location newly available {@link Location} object
- * @return true if the location was successfully injected, false otherwise
+ * @return true if the location was injected, false otherwise
*
* @throws IllegalArgumentException if location is null
* @throws SecurityException if permissions are not present
@@ -1229,7 +1229,8 @@ public class LocationManager {
"incomplete location object, missing timestamp or accuracy?");
try {
- return mService.injectLocation(location);
+ mService.injectLocation(location);
+ return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java
index 69162bab3167..085602cbcd4f 100644
--- a/location/java/android/location/LocationManagerInternal.java
+++ b/location/java/android/location/LocationManagerInternal.java
@@ -43,6 +43,15 @@ public abstract class LocationManagerInternal {
public abstract void requestSetProviderAllowed(@NonNull String provider, boolean allowed);
/**
+ * Returns true if the given provider is enabled for the given user.
+ *
+ * @param provider A location provider as listed by {@link LocationManager#getAllProviders()}
+ * @param userId The user id to check
+ * @return True if the provider is enabled, false otherwise
+ */
+ public abstract boolean isProviderEnabledForUser(@NonNull String provider, int userId);
+
+ /**
* Returns true if the given package belongs to a location provider, and so should be afforded
* some special privileges.
*
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index f67d08e045e2..bd29d8ac2a85 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -224,6 +224,19 @@ public abstract class LocationProviderBase {
public void reportLocation(Location location) {
ILocationProviderManager manager = mManager;
if (manager != null) {
+ // remove deprecated extras to save on serialization
+ Bundle extras = location.getExtras();
+ if (extras != null && (extras.containsKey("noGPSLocation")
+ || extras.containsKey("coarseLocation"))) {
+ location = new Location(location);
+ extras = location.getExtras();
+ extras.remove("noGPSLocation");
+ extras.remove("coarseLocation");
+ if (extras.isEmpty()) {
+ location.setExtras(null);
+ }
+ }
+
try {
manager.onReportLocation(location);
} catch (RemoteException | RuntimeException e) {
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 2c91a11c358c..6e6c9420c5aa 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -27,9 +27,12 @@ import static android.location.LocationManager.NETWORK_PROVIDER;
import static android.location.LocationManager.PASSIVE_PROVIDER;
import static android.os.PowerManager.locationPowerSaveModeToString;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.PendingIntent;
@@ -75,7 +78,6 @@ import android.stats.location.LocationStatsEnums;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -106,6 +108,7 @@ import com.android.server.location.MockableLocationProvider;
import com.android.server.location.PassiveProvider;
import com.android.server.location.SettingsHelper;
import com.android.server.location.UserInfoHelper;
+import com.android.server.location.UserInfoHelper.UserListener;
import com.android.server.location.gnss.GnssManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -124,7 +127,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.TimeUnit;
/**
* The service class that manages LocationProviders and issues location
@@ -154,7 +156,7 @@ public class LocationManagerService extends ILocationManager.Stub {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
// the location service must be functioning after this boot phase
mService.onSystemReady();
- } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
// some providers rely on third party code, so we wait to initialize
// providers until third party code is allowed to run
mService.onSystemThirdPartyAppsCanStart();
@@ -179,6 +181,9 @@ public class LocationManagerService extends ILocationManager.Stub {
// 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;
@@ -207,8 +212,12 @@ public class LocationManagerService extends ILocationManager.Stub {
private PackageManager mPackageManager;
private PowerManager mPowerManager;
- private GeofenceManager mGeofenceManager;
+ // TODO: sharing a location fudger with mock providers can leak information as the mock provider
+ // can be used to retrieve offset information. the fudger should likely be reset whenever mock
+ // providers are added or removed
private LocationFudger mLocationFudger;
+
+ private GeofenceManager mGeofenceManager;
private GeocoderProxy mGeocodeProvider;
@GuardedBy("mLock")
@@ -228,16 +237,6 @@ public class LocationManagerService extends ILocationManager.Stub {
private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
- // mapping from provider name to last known location
- @GuardedBy("mLock")
- private final HashMap<String, Location> mLastLocation = new HashMap<>();
-
- // same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS.
- // locations stored here are not fudged for coarse permissions.
- @GuardedBy("mLock")
- private final HashMap<String, Location> mLastLocationCoarseInterval =
- new HashMap<>();
-
@GuardedBy("mLock")
@PowerManager.LocationPowerSaveMode
private int mBatterySaverMode;
@@ -254,7 +253,7 @@ public class LocationManagerService extends ILocationManager.Stub {
mAppForegroundHelper = new AppForegroundHelper(mContext);
mLocationUsageLogger = new LocationUsageLogger();
- // set up passive provider - we do this early because it has no dependencies on system
+ // 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
@@ -281,18 +280,12 @@ public class LocationManagerService extends ILocationManager.Stub {
mSettingsHelper.onSystemReady();
mAppForegroundHelper.onSystemReady();
- if (GnssManagerService.isGnssSupported()) {
- mGnssManagerService = new GnssManagerService(mContext, mSettingsHelper,
- mAppForegroundHelper, mLocationUsageLogger);
- mGnssManagerService.onSystemReady();
- }
-
synchronized (mLock) {
mPackageManager = mContext.getPackageManager();
mAppOps = mContext.getSystemService(AppOpsManager.class);
mPowerManager = mContext.getSystemService(PowerManager.class);
- mLocationFudger = new LocationFudger(mContext, mHandler);
+ mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
mGeofenceManager = new GeofenceManager(mContext, mSettingsHelper);
PowerManagerInternal localPowerManager =
@@ -376,10 +369,11 @@ public class LocationManagerService extends ILocationManager.Stub {
}
}, UserHandle.ALL, intentFilter, null, mHandler);
- // switching the user from null to current here performs the bulk of the initialization
- // work. the user being changed will cause a reload of all user specific settings, which
- // causes initialization, and propagates changes until a steady state is reached
- onUserChanged(UserHandle.USER_NULL, mUserInfoHelper.getCurrentUserId());
+ // 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.
+ for (int userId: mUserInfoHelper.getCurrentUserIds()) {
+ onUserChanged(userId, UserListener.USER_STARTED);
+ }
}
}
@@ -427,7 +421,7 @@ public class LocationManagerService extends ILocationManager.Stub {
}
if (D) {
- Slog.d(TAG,
+ Log.d(TAG,
"Battery Saver location mode changed from "
+ locationPowerSaveModeToString(mBatterySaverMode) + " to "
+ locationPowerSaveModeToString(newLocationMode));
@@ -456,15 +450,15 @@ public class LocationManagerService extends ILocationManager.Stub {
Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
}
- synchronized (mLock) {
- 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));
+ 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));
+ synchronized (mLock) {
for (LocationProviderManager manager : mProviderManagers) {
- manager.onEnabledChangedLocked(userId);
+ manager.onEnabledChangedLocked(userId);
}
}
}
@@ -538,12 +532,6 @@ public class LocationManagerService extends ILocationManager.Stub {
@GuardedBy("mLock")
private void initializeProvidersLocked() {
- if (mGnssManagerService != null) {
- LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER);
- mProviderManagers.add(gnssManager);
- gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider());
- }
-
LocationProviderProxy networkProvider = LocationProviderProxy.createAndRegister(
mContext,
NETWORK_LOCATION_SERVICE_ACTION,
@@ -554,7 +542,7 @@ public class LocationManagerService extends ILocationManager.Stub {
mProviderManagers.add(networkManager);
networkManager.setRealProvider(networkProvider);
} else {
- Slog.w(TAG, "no network location provider found");
+ Log.w(TAG, "no network location provider found");
}
// ensure that a fused provider exists which will work in direct boot
@@ -574,14 +562,13 @@ public class LocationManagerService extends ILocationManager.Stub {
mProviderManagers.add(fusedManager);
fusedManager.setRealProvider(fusedProvider);
} else {
- Slog.e(TAG, "no fused location provider found",
- new IllegalStateException("Location service needs a fused location provider"));
+ Log.e(TAG, "no fused location provider found");
}
// bind to geocoder provider
mGeocodeProvider = GeocoderProxy.createAndRegister(mContext);
if (mGeocodeProvider == null) {
- Slog.e(TAG, "no geocoder provider found");
+ Log.e(TAG, "no geocoder provider found");
}
// bind to geofence proxy
@@ -590,7 +577,7 @@ public class LocationManagerService extends ILocationManager.Stub {
if (gpsGeofenceHardware != null) {
GeofenceProxy provider = GeofenceProxy.createAndBind(mContext, gpsGeofenceHardware);
if (provider == null) {
- Slog.d(TAG, "unable to bind to GeofenceProxy");
+ Log.e(TAG, "unable to bind to GeofenceProxy");
}
}
}
@@ -619,22 +606,52 @@ public class LocationManagerService extends ILocationManager.Stub {
Integer.parseInt(fragments[9]) /* accuracy */);
addTestProvider(name, properties, mContext.getOpPackageName());
}
- }
- private void onUserChanged(int oldUserId, int newUserId) {
- if (D) {
- Log.d(TAG, "foreground user is changing to " + newUserId);
- }
+ // 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, mSettingsHelper,
+ mAppForegroundHelper, mLocationUsageLogger);
+ mGnssManagerService.onSystemReady();
- synchronized (mLock) {
- for (LocationProviderManager manager : mProviderManagers) {
- // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
- mSettingsHelper.setLocationProviderAllowed(manager.getName(),
- manager.isEnabled(newUserId), newUserId);
+ LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER);
+ mProviderManagers.add(gnssManager);
+ gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider());
+ }
+ }
- manager.onEnabledChangedLocked(oldUserId);
- manager.onEnabledChangedLocked(newUserId);
- }
+ private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) {
+ switch (change) {
+ case UserListener.USER_SWITCHED:
+ if (D) {
+ Log.d(TAG, "user " + userId + " current status changed");
+ }
+ synchronized (mLock) {
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.onEnabledChangedLocked(userId);
+ }
+ }
+ break;
+ case UserListener.USER_STARTED:
+ if (D) {
+ Log.d(TAG, "user " + userId + " started");
+ }
+ synchronized (mLock) {
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.onUserStarted(userId);
+ }
+ }
+ break;
+ case UserListener.USER_STOPPED:
+ if (D) {
+ Log.d(TAG, "user " + userId + " stopped");
+ }
+ synchronized (mLock) {
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.onUserStopped(userId);
+ }
+ }
+ break;
}
}
@@ -645,25 +662,29 @@ public class LocationManagerService extends ILocationManager.Stub {
private final String mName;
- // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
- protected final MockableLocationProvider mProvider;
-
- // enabled state for parent user ids, no entry implies false. location state is only kept
- // for parent user ids, the location state for a profile user id is assumed to be the same
- // as for the parent. if querying this structure, ensure that the user id being used is a
- // parent id or the results may be incorrect.
+ // 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;
+
private LocationProviderManager(String name) {
mName = name;
- mEnabled = new SparseArray<>(1);
+ 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);
-
- // we can assume all users start with disabled location state since the initial state
- // of all providers is disabled. no need to initialize mEnabled further.
}
public String getName() {
@@ -679,7 +700,26 @@ public class LocationManagerService extends ILocationManager.Stub {
}
public void setMockProvider(@Nullable MockProvider provider) {
- mProvider.setMockProvider(provider);
+ synchronized (mLock) {
+ mProvider.setMockProvider(provider);
+
+ // when removing a mock provider, also clear any mock last locations
+ 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);
+ }
+ }
+ }
+ }
}
public Set<String> getPackages() {
@@ -691,6 +731,45 @@ public class LocationManagerService extends ILocationManager.Stub {
return mProvider.getState().properties;
}
+ @Nullable
+ public Location getLastFineLocation(int userId) {
+ synchronized (mLock) {
+ return mLastLocation.get(userId);
+ }
+ }
+
+ @Nullable
+ public Location getLastCoarseLocation(int userId) {
+ synchronized (mLock) {
+ return mLastCoarseLocation.get(userId);
+ }
+ }
+
+ 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()) {
@@ -743,23 +822,31 @@ public class LocationManagerService extends ILocationManager.Stub {
// don't validate mock locations
if (!location.isFromMockProvider()) {
if (location.getLatitude() == 0 && location.getLongitude() == 0) {
- Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
+ Log.w(TAG, "blocking 0,0 location from " + mName + " provider");
return;
}
}
- handleLocationChangedLocked(location, this);
+ 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.getCurrentUserIds()) {
+ 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) {
- return;
- }
-
- if (!GPS_PROVIDER.equals(mName) || !isEnabled()) {
- Slog.w(TAG, "reportLocationBatch() called without user permission");
+ if (mGnssManagerService == null || !GPS_PROVIDER.equals(mName)) {
return;
}
@@ -770,10 +857,7 @@ public class LocationManagerService extends ILocationManager.Stub {
@Override
public void onStateChanged(State oldState, State newState) {
if (oldState.allowed != newState.allowed) {
- // it would be more correct to call this for all users, but we know this can
- // only affect the current user since providers are disabled for non-current
- // users
- onEnabledChangedLocked(mUserInfoHelper.getCurrentUserId());
+ onEnabledChangedLocked(UserHandle.USER_ALL);
}
}
@@ -781,37 +865,74 @@ public class LocationManagerService extends ILocationManager.Stub {
mProvider.requestSetAllowed(allowed);
}
- public boolean isEnabled() {
- return isEnabled(mUserInfoHelper.getCurrentUserId());
+ public void onUserStarted(int userId) {
+ synchronized (mLock) {
+ // clear the user's enabled state in order to force a reevalution of whether the
+ // provider is enabled or disabled for the given user. we clear the user's state
+ // first to ensure that a user starting never causes any change notifications. it's
+ // possible for us to observe a user before we observe it's been started (for
+ // example, another component gets a user started notification before us and
+ // registers a location request immediately), which would cause us to already have
+ // some state in place. when we eventually do get the user started notification
+ // ourselves we don't want to send a change notification based on the prior state
+ mEnabled.put(userId, null);
+ onEnabledChangedLocked(userId);
+ }
+ }
+
+ public void onUserStopped(int userId) {
+ 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;
+ }
+
synchronized (mLock) {
- // normalize user id to always refer to parent since profile state is always the
- // same as parent state
- userId = mUserInfoHelper.getParentUserId(userId);
- return mEnabled.get(userId, Boolean.FALSE);
+ 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) {
- // only used during initialization - we don't care about the null user
+ // 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) {
+ // we know enabled changes can only happen for current users since providers are
+ // always disabled for all non-current users
+ for (int currentUserId : mUserInfoHelper.getCurrentUserIds()) {
+ onEnabledChangedLocked(currentUserId);
+ }
return;
}
- // normalize user id to always refer to parent since profile state is always the same
- // as parent state
- userId = mUserInfoHelper.getParentUserId(userId);
-
// 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 = (userId == mUserInfoHelper.getCurrentUserId())
- && mSettingsHelper.isLocationEnabled(userId) && mProvider.getState().allowed;
+ boolean enabled = mProvider.getState().allowed
+ && mUserInfoHelper.isCurrentUserId(userId)
+ && mSettingsHelper.isLocationEnabled(userId);
- if (enabled == isEnabled(userId)) {
+ Boolean wasEnabled = mEnabled.get(userId);
+ if (wasEnabled != null && wasEnabled == enabled) {
return;
}
@@ -821,28 +942,29 @@ public class LocationManagerService extends ILocationManager.Stub {
Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
}
- // fused and passive provider never get public updates for legacy reasons
- if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) {
- // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
- mSettingsHelper.setLocationProviderAllowed(mName, enabled, userId);
-
- 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));
+ // 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);
}
- if (!enabled) {
- // If any provider has been disabled, clear all last locations for all
- // providers. This is to be on the safe side in case a provider has location
- // derived from this disabled provider.
- mLastLocation.clear();
- mLastLocationCoarseInterval.clear();
+ // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
+ mSettingsHelper.setLocationProviderAllowed(mName, enabled, userId);
+
+ // 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));
+ }
}
- updateProviderEnabledLocked(this);
+ updateProviderEnabledLocked(this, enabled);
}
public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
@@ -855,7 +977,11 @@ public class LocationManagerService extends ILocationManager.Stub {
pw.increaseIndent();
- pw.println("enabled=" + isEnabled());
+ // for now we only dump for the parent user
+ int userId = mUserInfoHelper.getCurrentUserIds()[0];
+ pw.println("last location=" + mLastLocation.get(userId));
+ pw.println("last coarse location=" + mLastCoarseLocation.get(userId));
+ pw.println("enabled=" + isEnabled(userId));
}
mProvider.dump(fd, pw, args);
@@ -1004,7 +1130,8 @@ public class LocationManagerService extends ILocationManager.Stub {
if (manager == null) {
continue;
}
- if (!manager.isEnabled() && !isSettingsExempt(updateRecord)) {
+ if (!manager.isEnabled(UserHandle.getUserId(mCallerIdentity.mUid))
+ && !isSettingsExempt(updateRecord)) {
continue;
}
@@ -1420,7 +1547,7 @@ public class LocationManagerService extends ILocationManager.Stub {
if (FUSED_PROVIDER.equals(name)) {
continue;
}
- if (enabledOnly && !manager.isEnabled()) {
+ if (enabledOnly && !manager.isEnabled(UserHandle.getCallingUserId())) {
continue;
}
if (criteria != null
@@ -1462,15 +1589,12 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@GuardedBy("mLock")
- private void updateProviderEnabledLocked(LocationProviderManager manager) {
- boolean enabled = manager.isEnabled();
-
+ 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.isCurrentUserOrProfile(
+ if (!mUserInfoHelper.isCurrentUserId(
UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
continue;
}
@@ -1528,8 +1652,8 @@ public class LocationManagerService extends ILocationManager.Stub {
// initialize the low power mode to true and set to false if any of the records requires
providerRequest.setLowPowerMode(true);
for (UpdateRecord record : records) {
- if (!mUserInfoHelper.isCurrentUserOrProfile(
- UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
+ int userId = UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid);
+ if (!mUserInfoHelper.isCurrentUserId(userId)) {
continue;
}
if (!checkLocationAccess(
@@ -1541,7 +1665,7 @@ public class LocationManagerService extends ILocationManager.Stub {
}
final boolean isBatterySaverDisablingLocation = shouldThrottleRequests
|| (isForegroundOnlyMode && !record.mIsForegroundUid);
- if (!manager.isEnabled() || isBatterySaverDisablingLocation) {
+ if (!manager.isEnabled(userId) || isBatterySaverDisablingLocation) {
if (isSettingsExempt(record)) {
providerRequest.setLocationSettingsIgnored(true);
providerRequest.setLowPowerMode(false);
@@ -1587,7 +1711,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// TODO: overflow
long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2;
for (UpdateRecord record : records) {
- if (mUserInfoHelper.isCurrentUserOrProfile(
+ if (mUserInfoHelper.isCurrentUserId(
UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
LocationRequest locationRequest = record.mRequest;
@@ -1697,11 +1821,8 @@ public class LocationManagerService extends ILocationManager.Stub {
mStackTrace = new Throwable();
}
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
- if (records == null) {
- records = new ArrayList<>();
- mRecordsByProvider.put(provider, records);
- }
+ ArrayList<UpdateRecord> records = mRecordsByProvider.computeIfAbsent(provider,
+ k -> new ArrayList<>());
if (!records.contains(this)) {
records.add(this);
}
@@ -1837,11 +1958,11 @@ public class LocationManagerService extends ILocationManager.Stub {
break;
}
// throttle
- if (sanitizedRequest.getInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
- sanitizedRequest.setInterval(LocationFudger.FASTEST_INTERVAL_MS);
+ if (sanitizedRequest.getInterval() < FASTEST_COARSE_INTERVAL_MS) {
+ sanitizedRequest.setInterval(FASTEST_COARSE_INTERVAL_MS);
}
- if (sanitizedRequest.getFastestInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
- sanitizedRequest.setFastestInterval(LocationFudger.FASTEST_INTERVAL_MS);
+ if (sanitizedRequest.getFastestInterval() < FASTEST_COARSE_INTERVAL_MS) {
+ sanitizedRequest.setFastestInterval(FASTEST_COARSE_INTERVAL_MS);
}
}
// make getFastestInterval() the minimum of interval and fastest interval
@@ -1916,7 +2037,7 @@ public class LocationManagerService extends ILocationManager.Stub {
receiver = getReceiverLocked(listener, pid, uid, packageName, featureId,
workSource, hideFromAppOps, listenerIdentifier);
}
- requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
+ requestLocationUpdatesLocked(sanitizedRequest, receiver);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1924,8 +2045,7 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@GuardedBy("mLock")
- private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver,
- int uid, String packageName) {
+ private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver) {
// Figure out the provider. Either its explicitly request (legacy use cases), or
// use the fused provider
if (request == null) request = DEFAULT_LOCATION_REQUEST;
@@ -1940,20 +2060,14 @@ public class LocationManagerService extends ILocationManager.Stub {
}
UpdateRecord record = new UpdateRecord(name, request, receiver);
- if (D) {
- Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
- + " " + name + " " + request + " from " + packageName + "(" + uid + " "
- + (record.mIsForegroundUid ? "foreground" : "background")
- + (isThrottlingExempt(receiver.mCallerIdentity)
- ? " [whitelisted]" : "") + ")");
- }
UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
if (oldRecord != null) {
oldRecord.disposeLocked(false);
}
- if (!manager.isEnabled() && !isSettingsExempt(record)) {
+ int userId = UserHandle.getUserId(receiver.mCallerIdentity.mUid);
+ 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(name, false);
@@ -2030,91 +2144,65 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
- public Location getLastLocation(LocationRequest r, String packageName, String featureId) {
+ public Location getLastLocation(LocationRequest request, String packageName, String featureId) {
+ if (request == null) {
+ request = DEFAULT_LOCATION_REQUEST;
+ }
+
enforceCallingOrSelfLocationPermission();
enforceCallingOrSelfPackageName(packageName);
- synchronized (mLock) {
- LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
- int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- // no need to sanitize this request, as only the provider name is used
+ int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+ if (!reportLocationAccessNoThrow(Binder.getCallingPid(), Binder.getCallingUid(),
+ packageName, featureId, allowedResolutionLevel, null)) {
+ if (D) {
+ Log.d(TAG, "not returning last loc for no op app: " + packageName);
+ }
+ return null;
+ }
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long identity = Binder.clearCallingIdentity();
- try {
- if (mSettingsHelper.isLocationPackageBlacklisted(UserHandle.getUserId(uid),
- packageName)) {
- if (D) {
- Log.d(TAG, "not returning last loc for blacklisted app: "
- + packageName);
- }
- return null;
- }
+ int userId = UserHandle.getCallingUserId();
- // Figure out the provider. Either its explicitly request (deprecated API's),
- // or use the fused provider
- String name = request.getProvider();
- if (name == null) name = LocationManager.FUSED_PROVIDER;
- LocationProviderManager manager = getLocationProviderManager(name);
- if (manager == null) return null;
+ if (mSettingsHelper.isLocationPackageBlacklisted(userId, packageName)) {
+ return null;
+ }
- // only the current user or location providers may get location this way
- if (!mUserInfoHelper.isCurrentUserOrProfile(UserHandle.getUserId(uid))
- && !mLocalService.isProviderPackage(packageName)) {
- return null;
- }
+ if (!mUserInfoHelper.isCurrentUserId(userId)) {
+ return null;
+ }
- if (!manager.isEnabled()) {
- return null;
- }
+ synchronized (mLock) {
+ LocationProviderManager manager = getLocationProviderManager(request.getProvider());
+ if (manager == null) {
+ return null;
+ }
- Location location;
- if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
- // Make sure that an app with coarse permissions can't get frequent location
- // updates by calling LocationManager.getLastKnownLocation repeatedly.
- location = mLastLocationCoarseInterval.get(name);
- } else {
- location = mLastLocation.get(name);
- }
- if (location == null) {
- return null;
- }
+ if (!manager.isEnabled(userId) && !request.isLocationSettingsIgnored()) {
+ return null;
+ }
- // Don't return stale location to apps with foreground-only location permission.
- String op = resolutionLevelToOpStr(allowedResolutionLevel);
- long locationAgeMs = TimeUnit.NANOSECONDS.toMillis(
- SystemClock.elapsedRealtime() - location.getElapsedRealtimeNanos());
- if (locationAgeMs > mSettingsHelper.getMaxLastLocationAgeMs()
- && (mAppOps.unsafeCheckOp(op, uid, packageName)
- == AppOpsManager.MODE_FOREGROUND)) {
- return null;
- }
+ Location location;
+ if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
+ location = manager.getLastCoarseLocation(userId);
+ } else {
+ location = manager.getLastFineLocation(userId);
+ }
+ if (location == null) {
+ return null;
+ }
- Location lastLocation = null;
- if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
- Location noGPSLocation = location.getExtraLocation(
- Location.EXTRA_NO_GPS_LOCATION);
- if (noGPSLocation != null) {
- lastLocation = new Location(mLocationFudger.getOrCreate(noGPSLocation));
- }
- } else {
- lastLocation = new Location(location);
- }
- // Don't report location access if there is no last location to deliver.
- if (lastLocation != null) {
- if (!reportLocationAccessNoThrow(pid, uid, packageName, featureId,
- allowedResolutionLevel, null)) {
- if (D) {
- Log.d(TAG, "not returning last loc for no op app: " + packageName);
- }
- lastLocation = null;
- }
- }
- return lastLocation;
- } finally {
- Binder.restoreCallingIdentity(identity);
+ // Don't return stale location to apps with foreground-only location permission.
+ String op = resolutionLevelToOpStr(allowedResolutionLevel);
+ long locationAgeMs = NANOSECONDS.toMillis(
+ SystemClock.elapsedRealtime() - location.getElapsedRealtimeNanos());
+ if (locationAgeMs > mSettingsHelper.getMaxLastLocationAgeMs()
+ && (mAppOps.unsafeCheckOp(op, Binder.getCallingUid(), packageName)
+ == AppOpsManager.MODE_FOREGROUND)) {
+ return null;
}
+
+ // make a defensive copy - the client could be in the same process as us
+ return new Location(location);
}
}
@@ -2125,7 +2213,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// side effect of validating locationRequest and packageName
Location lastLocation = getLastLocation(locationRequest, packageName, featureId);
if (lastLocation != null) {
- long locationAgeMs = TimeUnit.NANOSECONDS.toMillis(
+ long locationAgeMs = NANOSECONDS.toMillis(
SystemClock.elapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos());
if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
@@ -2160,38 +2248,36 @@ public class LocationManagerService extends ILocationManager.Stub {
@Override
public LocationTime getGnssTimeMillis() {
synchronized (mLock) {
- Location location = mLastLocation.get(LocationManager.GPS_PROVIDER);
+ LocationProviderManager gpsManager = getLocationProviderManager(GPS_PROVIDER);
+ if (gpsManager == null) {
+ return null;
+ }
+
+ Location location = gpsManager.getLastFineLocation(UserHandle.getCallingUserId());
if (location == null) {
return null;
}
+
long currentNanos = SystemClock.elapsedRealtimeNanos();
- long deltaMs = (currentNanos - location.getElapsedRealtimeNanos()) / 1000000L;
+ long deltaMs = NANOSECONDS.toMillis(
+ currentNanos - location.getElapsedRealtimeNanos());
return new LocationTime(location.getTime() + deltaMs, currentNanos);
}
}
@Override
- public boolean injectLocation(Location location) {
+ public void injectLocation(Location location) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, null);
mContext.enforceCallingPermission(ACCESS_FINE_LOCATION, null);
Preconditions.checkArgument(location.isComplete());
+ int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
LocationProviderManager manager = getLocationProviderManager(location.getProvider());
- if (manager == null || !manager.isEnabled()) {
- return false;
+ if (manager != null && manager.isEnabled(userId)) {
+ manager.injectLastLocation(Objects.requireNonNull(location), userId);
}
-
- // NOTE: If last location is already available, location is not injected. If
- // provider's normal source (like a GPS chipset) have already provided an output
- // there is no need to inject this location.
- if (mLastLocation.get(manager.getName()) != null) {
- return false;
- }
-
- updateLastLocationLocked(location, manager.getName());
- return true;
}
}
@@ -2440,18 +2526,15 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
- public boolean isProviderEnabledForUser(String providerName, int userId) {
+ public boolean isProviderEnabledForUser(String provider, int userId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false, "isProviderEnabledForUser", null);
// Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
// so we discourage its use
- if (FUSED_PROVIDER.equals(providerName)) return false;
+ if (FUSED_PROVIDER.equals(provider)) return false;
- synchronized (mLock) {
- LocationProviderManager manager = getLocationProviderManager(providerName);
- return manager != null && manager.isEnabled(userId);
- }
+ return mLocalService.isProviderEnabledForUser(provider, userId);
}
@GuardedBy("mLock")
@@ -2464,7 +2547,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// Check whether sufficient time has passed
long minTime = record.mRealRequest.getFastestInterval();
- long deltaMs = TimeUnit.NANOSECONDS.toMillis(
+ long deltaMs = NANOSECONDS.toMillis(
loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos());
if (deltaMs < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) {
return false;
@@ -2488,62 +2571,23 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@GuardedBy("mLock")
- private void handleLocationChangedLocked(Location location, LocationProviderManager manager) {
+ private void handleLocationChangedLocked(LocationProviderManager manager, Location location,
+ Location coarseLocation) {
if (!mProviderManagers.contains(manager)) {
Log.w(TAG, "received location from unknown provider: " + manager.getName());
return;
}
- if (!location.isComplete()) {
- Log.w(TAG, "dropping incomplete location from " + manager.getName() + " provider: "
- + location);
- return;
- }
// notify passive provider
if (manager != mPassiveManager) {
- mPassiveManager.updateLocation(new Location(location));
+ mPassiveManager.updateLocation(location);
}
- if (D) Log.d(TAG, "incoming location: " + location);
long now = SystemClock.elapsedRealtime();
-
- // only update last location for locations that come from enabled providers
- if (manager.isEnabled()) {
- updateLastLocationLocked(location, manager.getName());
- }
-
- // Update last known coarse interval location if enough time has passed.
- Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(
- manager.getName());
- if (lastLocationCoarseInterval == null) {
- lastLocationCoarseInterval = new Location(location);
-
- if (manager.isEnabled()) {
- mLastLocationCoarseInterval.put(manager.getName(), lastLocationCoarseInterval);
- }
- }
- long timeDeltaMs = TimeUnit.NANOSECONDS.toMillis(location.getElapsedRealtimeNanos()
- - lastLocationCoarseInterval.getElapsedRealtimeNanos());
- if (timeDeltaMs > LocationFudger.FASTEST_INTERVAL_MS) {
- lastLocationCoarseInterval.set(location);
- }
- // Don't ever return a coarse location that is more recent than the allowed update
- // interval (i.e. don't allow an app to keep registering and unregistering for
- // location updates to overcome the minimum interval).
- Location noGPSLocation =
- lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
-
- // Skip if there are no UpdateRecords for this provider.
ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
if (records == null || records.size() == 0) return;
- // Fetch coarse location
- Location coarseLocation = null;
- if (noGPSLocation != null) {
- coarseLocation = mLocationFudger.getOrCreate(noGPSLocation);
- }
-
ArrayList<Receiver> deadReceivers = null;
ArrayList<UpdateRecord> deadUpdateRecords = null;
@@ -2551,22 +2595,23 @@ public class LocationManagerService extends ILocationManager.Stub {
for (UpdateRecord r : records) {
Receiver receiver = r.mReceiver;
boolean receiverDead = false;
+ int userId = UserHandle.getUserId(receiver.mCallerIdentity.mUid);
- if (!manager.isEnabled() && !isSettingsExempt(r)) {
+
+ if (!manager.isEnabled(userId) && !isSettingsExempt(r)) {
continue;
}
- int receiverUserId = UserHandle.getUserId(receiver.mCallerIdentity.mUid);
- if (!mUserInfoHelper.isCurrentUserOrProfile(receiverUserId)
+ if (!mUserInfoHelper.isCurrentUserId(userId)
&& !isProviderPackage(receiver.mCallerIdentity.mPackageName)) {
if (D) {
- Log.d(TAG, "skipping loc update for background user " + receiverUserId +
- " (app: " + receiver.mCallerIdentity.mPackageName + ")");
+ Log.d(TAG, "skipping loc update for background user " + userId
+ + " (app: " + receiver.mCallerIdentity.mPackageName + ")");
}
continue;
}
- if (mSettingsHelper.isLocationPackageBlacklisted(receiverUserId,
+ if (mSettingsHelper.isLocationPackageBlacklisted(userId,
receiver.mCallerIdentity.mPackageName)) {
if (D) {
Log.d(TAG, "skipping loc update for blacklisted app: " +
@@ -2581,39 +2626,30 @@ public class LocationManagerService extends ILocationManager.Stub {
} else {
notifyLocation = location; // use fine location
}
- if (notifyLocation != null) {
- Location lastLoc = r.mLastFixBroadcast;
- if ((lastLoc == null)
- || shouldBroadcastSafeLocked(notifyLocation, lastLoc, r, now)) {
- if (lastLoc == null) {
- lastLoc = new Location(notifyLocation);
- r.mLastFixBroadcast = lastLoc;
- } else {
- lastLoc.set(notifyLocation);
- }
- // Report location access before delivering location to the client. This will
- // note location delivery to appOps, so it should be called only when a
- // location is really being delivered to the client.
- if (!reportLocationAccessNoThrow(
- receiver.mCallerIdentity.mPid,
- receiver.mCallerIdentity.mUid,
- receiver.mCallerIdentity.mPackageName,
- receiver.mCallerIdentity.mFeatureId,
- receiver.mAllowedResolutionLevel,
- "Location sent to " + receiver.mCallerIdentity.mListenerIdentifier)) {
- if (D) {
- Log.d(TAG, "skipping loc update for no op app: "
- + receiver.mCallerIdentity.mPackageName);
- }
- continue;
- }
- if (!receiver.callLocationChangedLocked(notifyLocation)) {
- Slog.w(TAG, "RemoteException calling onLocationChanged on "
- + receiver);
- receiverDead = true;
+ if (shouldBroadcastSafeLocked(notifyLocation, r.mLastFixBroadcast, r, now)) {
+ r.mLastFixBroadcast = notifyLocation;
+ // Report location access before delivering location to the client. This will
+ // note location delivery to appOps, so it should be called only when a
+ // location is really being delivered to the client.
+ if (!reportLocationAccessNoThrow(
+ receiver.mCallerIdentity.mPid,
+ receiver.mCallerIdentity.mUid,
+ receiver.mCallerIdentity.mPackageName,
+ receiver.mCallerIdentity.mFeatureId,
+ receiver.mAllowedResolutionLevel,
+ "Location sent to " + receiver.mCallerIdentity.mListenerIdentifier)) {
+ if (D) {
+ Log.d(TAG, "skipping loc update for no op app: "
+ + receiver.mCallerIdentity.mPackageName);
}
- r.mRealRequest.decrementNumUpdates();
+ continue;
}
+ if (!receiver.callLocationChangedLocked(notifyLocation)) {
+ Log.w(TAG, "RemoteException calling onLocationChanged on "
+ + receiver);
+ receiverDead = true;
+ }
+ r.mRealRequest.decrementNumUpdates();
}
// track expired records
@@ -2650,30 +2686,6 @@ public class LocationManagerService extends ILocationManager.Stub {
}
}
- @GuardedBy("mLock")
- private void updateLastLocationLocked(Location location, String provider) {
- Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
- Location lastNoGPSLocation;
- Location lastLocation = mLastLocation.get(provider);
- if (lastLocation == null) {
- lastLocation = new Location(provider);
- mLastLocation.put(provider, lastLocation);
- } else {
- lastNoGPSLocation = lastLocation.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
- if (noGPSLocation == null && lastNoGPSLocation != null) {
- // New location has no no-GPS location: adopt last no-GPS location. This is set
- // directly into location because we do not want to notify COARSE clients.
- Bundle extras = location.getExtras();
- if (extras == null) {
- extras = new Bundle();
- }
- extras.putParcelable(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation);
- location.setExtras(extras);
- }
- }
- lastLocation.set(location);
- }
-
// Geocoder
@Override
@@ -2743,8 +2755,6 @@ public class LocationManagerService extends ILocationManager.Stub {
manager.setMockProvider(null);
if (!manager.hasProvider()) {
mProviderManagers.remove(manager);
- mLastLocation.remove(manager.getName());
- mLastLocationCoarseInterval.remove(manager.getName());
}
}
}
@@ -2854,8 +2864,8 @@ public class LocationManagerService extends ILocationManager.Stub {
ipw.println("Historical Records by Provider:");
ipw.increaseIndent();
- TreeMap<PackageProviderKey, PackageStatistics> sorted = new TreeMap<>();
- sorted.putAll(mRequestStatistics.statistics);
+ TreeMap<PackageProviderKey, PackageStatistics> sorted = new TreeMap<>(
+ mRequestStatistics.statistics);
for (Map.Entry<PackageProviderKey, PackageStatistics> entry
: sorted.entrySet()) {
PackageProviderKey key = entry.getKey();
@@ -2865,20 +2875,6 @@ public class LocationManagerService extends ILocationManager.Stub {
mRequestStatistics.history.dump(ipw);
- ipw.println("Last Known Locations:");
- ipw.increaseIndent();
- for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) {
- ipw.println(entry.getKey() + ": " + entry.getValue());
- }
- ipw.decreaseIndent();
-
- ipw.println("Last Known Coarse Locations:");
- ipw.increaseIndent();
- for (Map.Entry<String, Location> entry : mLastLocationCoarseInterval.entrySet()) {
- ipw.println(entry.getKey() + ": " + entry.getValue());
- }
- ipw.decreaseIndent();
-
if (mGeofenceManager != null) {
ipw.println("Geofences:");
ipw.increaseIndent();
@@ -2890,21 +2886,16 @@ public class LocationManagerService extends ILocationManager.Stub {
ipw.println("Location Controller Extra Package: " + mExtraLocationControllerPackage
+ (mExtraLocationControllerPackageEnabled ? " [enabled]" : "[disabled]"));
}
+ }
- if (mLocationFudger != null) {
- ipw.println("Location Fudger:");
- ipw.increaseIndent();
- mLocationFudger.dump(fd, ipw, args);
- ipw.decreaseIndent();
- }
-
- ipw.println("Location Providers:");
- ipw.increaseIndent();
- for (LocationProviderManager manager : mProviderManagers) {
- manager.dump(fd, ipw, args);
- }
- ipw.decreaseIndent();
+ ipw.println("Location Providers:");
+ ipw.increaseIndent();
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.dump(fd, ipw, args);
+ }
+ ipw.decreaseIndent();
+ synchronized (mLock) {
if (mGnssManagerService != null) {
ipw.println("GNSS:");
ipw.increaseIndent();
@@ -2929,6 +2920,18 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
+ public boolean isProviderEnabledForUser(@NonNull String provider, int userId) {
+ synchronized (mLock) {
+ LocationProviderManager manager = getLocationProviderManager(provider);
+ if (manager == null) {
+ return false;
+ }
+
+ return manager.isEnabled(userId);
+ }
+ }
+
+ @Override
public boolean isProviderPackage(String packageName) {
for (LocationProviderManager manager : mProviderManagers) {
if (manager.getPackages().contains(packageName)) {
diff --git a/services/core/java/com/android/server/location/LocationFudger.java b/services/core/java/com/android/server/location/LocationFudger.java
index 04c7714f07f7..a069e7ace636 100644
--- a/services/core/java/com/android/server/location/LocationFudger.java
+++ b/services/core/java/com/android/server/location/LocationFudger.java
@@ -16,297 +16,165 @@
package com.android.server.location;
-import android.content.Context;
-import android.database.ContentObserver;
+import android.annotation.Nullable;
import android.location.Location;
-import android.os.Bundle;
-import android.os.Handler;
import android.os.SystemClock;
-import android.provider.Settings;
-import android.util.Log;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.security.SecureRandom;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import java.security.SecureRandom;
+import java.time.Clock;
+import java.util.Random;
/**
- * Contains the logic to obfuscate (fudge) locations for coarse applications.
- *
- * <p>The goal is just to prevent applications with only
- * the coarse location permission from receiving a fine location.
+ * Contains the logic to obfuscate (fudge) locations for coarse applications. The goal is just to
+ * prevent applications with only the coarse location permission from receiving a fine location.
*/
public class LocationFudger {
- private static final boolean D = false;
- private static final String TAG = "LocationFudge";
-
- /**
- * Default coarse accuracy in meters.
- */
- private static final float DEFAULT_ACCURACY_IN_METERS = 2000.0f;
-
- /**
- * Minimum coarse accuracy in meters.
- */
- private static final float MINIMUM_ACCURACY_IN_METERS = 200.0f;
-
- /**
- * Secure settings key for coarse accuracy.
- */
- private static final String COARSE_ACCURACY_CONFIG_NAME = "locationCoarseAccuracy";
- /**
- * This is the fastest interval that applications can receive coarse
- * locations.
- */
- public static final long FASTEST_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
+ // minimum accuracy a coarsened location can have
+ private static final float MIN_ACCURACY_M = 200.0f;
- /**
- * The duration until we change the random offset.
- */
- private static final long CHANGE_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
+ // how often random offsets are updated
+ @VisibleForTesting
+ static final long OFFSET_UPDATE_INTERVAL_MS = 60 * 60 * 1000;
- /**
- * The percentage that we change the random offset at every interval.
- *
- * <p>0.0 indicates the random offset doesn't change. 1.0
- * indicates the random offset is completely replaced every interval.
- */
+ // the percentage that we change the random offset at every interval. 0.0 indicates the random
+ // offset doesn't change. 1.0 indicates the random offset is completely replaced every interval
private static final double CHANGE_PER_INTERVAL = 0.03; // 3% change
- // Pre-calculated weights used to move the random offset.
- //
- // The goal is to iterate on the previous offset, but keep
- // the resulting standard deviation the same. The variance of
- // two gaussian distributions summed together is equal to the
- // sum of the variance of each distribution. So some quick
- // algebra results in the following sqrt calculation to
- // weigh in a new offset while keeping the final standard
- // deviation unchanged.
+ // weights used to move the random offset. the goal is to iterate on the previous offset, but
+ // keep the resulting standard deviation the same. the variance of two gaussian distributions
+ // summed together is equal to the sum of the variance of each distribution. so some quick
+ // algebra results in the following sqrt calculation to weight in a new offset while keeping the
+ // final standard deviation unchanged.
private static final double NEW_WEIGHT = CHANGE_PER_INTERVAL;
- private static final double PREVIOUS_WEIGHT = Math.sqrt(1 - NEW_WEIGHT * NEW_WEIGHT);
-
- /**
- * This number actually varies because the earth is not round, but
- * 111,000 meters is considered generally acceptable.
- */
- private static final int APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR = 111000;
-
- /**
- * Maximum latitude.
- *
- * <p>We pick a value 1 meter away from 90.0 degrees in order
- * to keep cosine(MAX_LATITUDE) to a non-zero value, so that we avoid
- * divide by zero fails.
- */
- private static final double MAX_LATITUDE = 90.0 -
- (1.0 / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR);
-
- private final Object mLock = new Object();
- private final SecureRandom mRandom = new SecureRandom();
-
- /**
- * Used to monitor coarse accuracy secure setting for changes.
- */
- private final ContentObserver mSettingsObserver;
-
- /**
- * Used to resolve coarse accuracy setting.
- */
- private final Context mContext;
-
- // all fields below protected by mLock
- private double mOffsetLatitudeMeters;
- private double mOffsetLongitudeMeters;
- private long mNextInterval;
-
- /**
- * Best location accuracy allowed for coarse applications.
- * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
- */
- private float mAccuracyInMeters;
+ private static final double OLD_WEIGHT = Math.sqrt(1 - NEW_WEIGHT * NEW_WEIGHT);
+
+ // this number actually varies because the earth is not round, but 111,000 meters is considered
+ // generally acceptable
+ private static final int APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR = 111_000;
+
+ // we pick a value 1 meter away from 90.0 degrees in order to keep cosine(MAX_LATITUDE) to a
+ // non-zero value, so that we avoid divide by zero errors
+ private static final double MAX_LATITUDE =
+ 90.0 - (1.0 / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR);
+
+ private final float mAccuracyM;
+ private final Clock mClock;
+ private final Random mRandom;
+
+ @GuardedBy("this")
+ private double mLatitudeOffsetM;
+ @GuardedBy("this")
+ private double mLongitudeOffsetM;
+ @GuardedBy("this")
+ private long mNextUpdateRealtimeMs;
+
+ @GuardedBy("this")
+ @Nullable private Location mCachedFineLocation;
+ @GuardedBy("this")
+ @Nullable private Location mCachedCoarseLocation;
+
+ public LocationFudger(float accuracyM) {
+ this(accuracyM, SystemClock.elapsedRealtimeClock(), new SecureRandom());
+ }
- /**
- * The distance between grids for snap-to-grid. See {@link #createCoarse}.
- * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
- */
- private double mGridSizeInMeters;
+ @VisibleForTesting
+ LocationFudger(float accuracyM, Clock clock, Random random) {
+ mClock = clock;
+ mRandom = random;
+ mAccuracyM = Math.max(accuracyM, MIN_ACCURACY_M);
- /**
- * Standard deviation of the (normally distributed) random offset applied
- * to coarse locations. It does not need to be as large as
- * {@link #COARSE_ACCURACY_METERS} because snap-to-grid is the primary obfuscation
- * method. See further details in the implementation.
- * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
- */
- private double mStandardDeviationInMeters;
-
- public LocationFudger(Context context, Handler handler) {
- mContext = context;
- mSettingsObserver = new ContentObserver(handler) {
- @Override
- public void onChange(boolean selfChange) {
- setAccuracyInMeters(loadCoarseAccuracy());
- }
- };
- mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
- COARSE_ACCURACY_CONFIG_NAME), false, mSettingsObserver);
-
- float accuracy = loadCoarseAccuracy();
- synchronized (mLock) {
- setAccuracyInMetersLocked(accuracy);
- mOffsetLatitudeMeters = nextOffsetLocked();
- mOffsetLongitudeMeters = nextOffsetLocked();
- mNextInterval = SystemClock.elapsedRealtime() + CHANGE_INTERVAL_MS;
- }
+ mLatitudeOffsetM = nextRandomOffset();
+ mLongitudeOffsetM = nextRandomOffset();
+ mNextUpdateRealtimeMs = mClock.millis() + OFFSET_UPDATE_INTERVAL_MS;
}
/**
- * Get the cached coarse location, or generate a new one and cache it.
- */
- public Location getOrCreate(Location location) {
- synchronized (mLock) {
- Location coarse = location.getExtraLocation(Location.EXTRA_COARSE_LOCATION);
- if (coarse == null) {
- return addCoarseLocationExtraLocked(location);
- }
- if (coarse.getAccuracy() < mAccuracyInMeters) {
- return addCoarseLocationExtraLocked(location);
+ * Create a coarse location using two technique, random offsets and snap-to-grid.
+ *
+ * First we add a random offset to mitigate against detecting grid transitions. Without a random
+ * offset it is possible to detect a user's position quite accurately when they cross a grid
+ * boundary. The random offset changes very slowly over time, to mitigate against taking many
+ * location samples and averaging them out. Second we snap-to-grid (quantize). This has the nice
+ * property of producing stable results, and mitigating against taking many samples to average
+ * out a random offset.
+ */
+ public Location createCoarse(Location fine) {
+ synchronized (this) {
+ if (fine == mCachedFineLocation) {
+ return new Location(mCachedCoarseLocation);
}
- return coarse;
}
- }
- private Location addCoarseLocationExtraLocked(Location location) {
- Location coarse = createCoarseLocked(location);
- Bundle extras = location.getExtras();
- if (extras == null) {
- extras = new Bundle();
- }
- extras.putParcelable(Location.EXTRA_COARSE_LOCATION, coarse);
- location.setExtras(extras);
- return coarse;
- }
+ // update the offsets in use
+ updateOffsets();
- /**
- * Create a coarse location.
- *
- * <p>Two techniques are used: random offsets and snap-to-grid.
- *
- * <p>First we add a random offset. This mitigates against detecting
- * grid transitions. Without a random offset it is possible to detect
- * a users position very accurately when they cross a grid boundary.
- * The random offset changes very slowly over time, to mitigate against
- * taking many location samples and averaging them out.
- *
- * <p>Second we snap-to-grid (quantize). This has the nice property of
- * producing stable results, and mitigating against taking many samples
- * to average out a random offset.
- */
- private Location createCoarseLocked(Location fine) {
Location coarse = new Location(fine);
- // clean all the optional information off the location, because
- // this can leak detailed location information
+ // clear any fields that could leak more detailed location information
coarse.removeBearing();
coarse.removeSpeed();
coarse.removeAltitude();
coarse.setExtras(null);
- double lat = coarse.getLatitude();
- double lon = coarse.getLongitude();
+ double latitude = wrapLatitude(coarse.getLatitude());
+ double longitude = wrapLongitude(coarse.getLongitude());
- // wrap
- lat = wrapLatitude(lat);
- lon = wrapLongitude(lon);
+ // add offsets - update longitude first using the non-offset latitude
+ longitude += wrapLongitude(metersToDegreesLongitude(mLongitudeOffsetM, latitude));
+ latitude += wrapLatitude(metersToDegreesLatitude(mLatitudeOffsetM));
- // Step 1) apply a random offset
- //
- // The goal of the random offset is to prevent the application
- // from determining that the device is on a grid boundary
- // when it crosses from one grid to the next.
- //
- // We apply the offset even if the location already claims to be
- // inaccurate, because it may be more accurate than claimed.
- updateRandomOffsetLocked();
- // perform lon first whilst lat is still within bounds
- lon += metersToDegreesLongitude(mOffsetLongitudeMeters, lat);
- lat += metersToDegreesLatitude(mOffsetLatitudeMeters);
- if (D) Log.d(TAG, String.format("applied offset of %.0f, %.0f (meters)",
- mOffsetLongitudeMeters, mOffsetLatitudeMeters));
-
- // wrap
- lat = wrapLatitude(lat);
- lon = wrapLongitude(lon);
-
- // Step 2) Snap-to-grid (quantize)
+ // quantize location by snapping to a grid. this is the primary means of obfuscation. it
+ // gives nice consistent results and is very effective at hiding the true location (as long
+ // as you are not sitting on a grid boundary, which the random offsets mitigate).
//
- // This is the primary means of obfuscation. It gives nice consistent
- // results and is very effective at hiding the true location
- // (as long as you are not sitting on a grid boundary, which
- // step 1 mitigates).
- //
- // Note we quantize the latitude first, since the longitude
- // quantization depends on the latitude value and so leaks information
- // about the latitude
- double latGranularity = metersToDegreesLatitude(mGridSizeInMeters);
- lat = Math.round(lat / latGranularity) * latGranularity;
- double lonGranularity = metersToDegreesLongitude(mGridSizeInMeters, lat);
- lon = Math.round(lon / lonGranularity) * lonGranularity;
-
- // wrap again
- lat = wrapLatitude(lat);
- lon = wrapLongitude(lon);
-
- // apply
- coarse.setLatitude(lat);
- coarse.setLongitude(lon);
- coarse.setAccuracy(Math.max(mAccuracyInMeters, coarse.getAccuracy()));
-
- if (D) Log.d(TAG, "fudged " + fine + " to " + coarse);
- return coarse;
+ // note that we quantize the latitude first, since the longitude quantization depends on the
+ // latitude value and so leaks information about the latitude
+ double latGranularity = metersToDegreesLatitude(mAccuracyM);
+ latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
+ double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
+ longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+
+ coarse.setLatitude(latitude);
+ coarse.setLongitude(longitude);
+ coarse.setAccuracy(Math.max(mAccuracyM, coarse.getAccuracy()));
+
+ synchronized (this) {
+ mCachedFineLocation = fine;
+ mCachedCoarseLocation = coarse;
+ }
+
+ return new Location(mCachedCoarseLocation);
}
/**
- * Update the random offset over time.
+ * Update the random offsets over time.
*
- * <p>If the random offset was new for every location
- * fix then an application can more easily average location results
- * over time,
- * especially when the location is near a grid boundary. On the
- * other hand if the random offset is constant then if an application
- * found a way to reverse engineer the offset they would be able
- * to detect location at grid boundaries very accurately. So
- * we choose a random offset and then very slowly move it, to
- * make both approaches very hard.
- *
- * <p>The random offset does not need to be large, because snap-to-grid
- * is the primary obfuscation mechanism. It just needs to be large
- * enough to stop information leakage as we cross grid boundaries.
- */
- private void updateRandomOffsetLocked() {
- long now = SystemClock.elapsedRealtime();
- if (now < mNextInterval) {
+ * If the random offset was reset for every location fix then an application could more easily
+ * average location results over time, especially when the location is near a grid boundary. On
+ * the other hand if the random offset is constant then if an application finds a way to reverse
+ * engineer the offset they would be able to detect location at grid boundaries very accurately.
+ * So we choose a random offset and then very slowly move it, to make both approaches very hard.
+ * The random offset does not need to be large, because snap-to-grid is the primary obfuscation
+ * mechanism. It just needs to be large enough to stop information leakage as we cross grid
+ * boundaries.
+ */
+ private synchronized void updateOffsets() {
+ long now = mClock.millis();
+ if (now < mNextUpdateRealtimeMs) {
return;
}
- if (D) Log.d(TAG, String.format("old offset: %.0f, %.0f (meters)",
- mOffsetLongitudeMeters, mOffsetLatitudeMeters));
-
- // ok, need to update the random offset
- mNextInterval = now + CHANGE_INTERVAL_MS;
-
- mOffsetLatitudeMeters *= PREVIOUS_WEIGHT;
- mOffsetLatitudeMeters += NEW_WEIGHT * nextOffsetLocked();
- mOffsetLongitudeMeters *= PREVIOUS_WEIGHT;
- mOffsetLongitudeMeters += NEW_WEIGHT * nextOffsetLocked();
-
- if (D) Log.d(TAG, String.format("new offset: %.0f, %.0f (meters)",
- mOffsetLongitudeMeters, mOffsetLatitudeMeters));
+ mLatitudeOffsetM = (OLD_WEIGHT * mLatitudeOffsetM) + (NEW_WEIGHT * nextRandomOffset());
+ mLongitudeOffsetM = (OLD_WEIGHT * mLongitudeOffsetM) + (NEW_WEIGHT * nextRandomOffset());
+ mNextUpdateRealtimeMs = now + OFFSET_UPDATE_INTERVAL_MS;
}
- private double nextOffsetLocked() {
- return mRandom.nextGaussian() * mStandardDeviationInMeters;
+ private double nextRandomOffset() {
+ return mRandom.nextGaussian() * (mAccuracyM / 4.0);
}
private static double wrapLatitude(double lat) {
@@ -334,56 +202,8 @@ public class LocationFudger {
return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR;
}
- /**
- * Requires latitude since longitudinal distances change with distance from equator.
- */
+ // requires latitude since longitudinal distances change with distance from equator.
private static double metersToDegreesLongitude(double distance, double lat) {
return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR / Math.cos(Math.toRadians(lat));
}
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println(String.format("offset: %.0f, %.0f (meters)", mOffsetLongitudeMeters,
- mOffsetLatitudeMeters));
- }
-
- /**
- * This is the main control: call this to set the best location accuracy
- * allowed for coarse applications and all derived values.
- */
- private void setAccuracyInMetersLocked(float accuracyInMeters) {
- mAccuracyInMeters = Math.max(accuracyInMeters, MINIMUM_ACCURACY_IN_METERS);
- if (D) {
- Log.d(TAG, "setAccuracyInMetersLocked: new accuracy = " + mAccuracyInMeters);
- }
- mGridSizeInMeters = mAccuracyInMeters;
- mStandardDeviationInMeters = mGridSizeInMeters / 4.0;
- }
-
- /**
- * Same as setAccuracyInMetersLocked without the pre-lock requirement.
- */
- private void setAccuracyInMeters(float accuracyInMeters) {
- synchronized (mLock) {
- setAccuracyInMetersLocked(accuracyInMeters);
- }
- }
-
- /**
- * Loads the coarse accuracy value from secure settings.
- */
- private float loadCoarseAccuracy() {
- String newSetting = Settings.Secure.getString(mContext.getContentResolver(),
- COARSE_ACCURACY_CONFIG_NAME);
- if (D) {
- Log.d(TAG, "loadCoarseAccuracy: newSetting = \"" + newSetting + "\"");
- }
- if (newSetting == null) {
- return DEFAULT_ACCURACY_IN_METERS;
- }
- try {
- return Float.parseFloat(newSetting);
- } catch (NumberFormatException e) {
- return DEFAULT_ACCURACY_IN_METERS;
- }
- }
}
diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java
index b0e133061fc6..f43669e488d1 100644
--- a/services/core/java/com/android/server/location/MockableLocationProvider.java
+++ b/services/core/java/com/android/server/location/MockableLocationProvider.java
@@ -237,6 +237,10 @@ public class MockableLocationProvider extends AbstractLocationProvider {
* Dumps the current provider implementation.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ // holding the owner lock outside this could lead to deadlock since we don't run dump on the
+ // executor specified by the provider, we run it directly
+ Preconditions.checkState(!Thread.holdsLock(mOwnerLock));
+
AbstractLocationProvider provider;
synchronized (mOwnerLock) {
provider = mProvider;
diff --git a/services/core/java/com/android/server/location/SettingsHelper.java b/services/core/java/com/android/server/location/SettingsHelper.java
index 6d1d1f901eb3..370a3f762d32 100644
--- a/services/core/java/com/android/server/location/SettingsHelper.java
+++ b/services/core/java/com/android/server/location/SettingsHelper.java
@@ -23,6 +23,7 @@ import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACK
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS;
import static android.provider.Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST;
import static android.provider.Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS;
+import static android.provider.Settings.Secure.LOCATION_COARSE_ACCURACY_M;
import static android.provider.Settings.Secure.LOCATION_MODE;
import static android.provider.Settings.Secure.LOCATION_MODE_OFF;
@@ -88,6 +89,7 @@ public class SettingsHelper {
private static final long DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS =
30 * 60 * 1000;
private static final long DEFAULT_MAX_LAST_LOCATION_AGE_MS = 20 * 60 * 1000;
+ private static final float DEFAULT_COARSE_LOCATION_ACCURACY_M = 2000.0f;
private final Context mContext;
@@ -268,19 +270,45 @@ public class SettingsHelper {
* Retrieve the background throttling proximity alert interval.
*/
public long getBackgroundThrottleProximityAlertIntervalMs() {
- return Settings.Global.getLong(mContext.getContentResolver(),
- LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
- DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return Settings.Global.getLong(mContext.getContentResolver(),
+ LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
+ DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
/**
* Retrieve maximum age of the last location.
*/
public long getMaxLastLocationAgeMs() {
- return Settings.Global.getLong(
- mContext.getContentResolver(),
- LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
- DEFAULT_MAX_LAST_LOCATION_AGE_MS);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return Settings.Global.getLong(
+ mContext.getContentResolver(),
+ LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
+ DEFAULT_MAX_LAST_LOCATION_AGE_MS);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Retrieve the accuracy for coarsening location, ie, the grid size used for snap-to-grid
+ * coarsening.
+ */
+ public float getCoarseLocationAccuracyM() {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return Settings.Secure.getFloat(
+ mContext.getContentResolver(),
+ LOCATION_COARSE_ACCURACY_M,
+ DEFAULT_COARSE_LOCATION_ACCURACY_M);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
/**
diff --git a/services/core/java/com/android/server/location/UserInfoHelper.java b/services/core/java/com/android/server/location/UserInfoHelper.java
index 94f3a88cb859..a33e2da4eb87 100644
--- a/services/core/java/com/android/server/location/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/UserInfoHelper.java
@@ -16,6 +16,9 @@
package com.android.server.location;
+import static android.os.UserManager.DISALLOW_SHARE_LOCATION;
+
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -23,7 +26,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Build;
import android.os.UserHandle;
@@ -36,6 +38,8 @@ import com.android.server.FgThread;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -47,15 +51,24 @@ public class UserInfoHelper {
/**
* Listener for current user changes.
*/
- public interface UserChangedListener {
+ public interface UserListener {
+
+ int USER_SWITCHED = 1;
+ int USER_STARTED = 2;
+ int USER_STOPPED = 3;
+
+ @IntDef({USER_SWITCHED, USER_STARTED, USER_STOPPED})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface UserChange {}
+
/**
- * Called when the current user changes.
+ * Called when something has changed about the given user.
*/
- void onUserChanged(@UserIdInt int oldUserId, @UserIdInt int newUserId);
+ void onUserChanged(@UserIdInt int userId, @UserChange int change);
}
private final Context mContext;
- private final CopyOnWriteArrayList<UserChangedListener> mListeners;
+ private final CopyOnWriteArrayList<UserListener> mListeners;
@GuardedBy("this")
@Nullable private UserManager mUserManager;
@@ -86,6 +99,8 @@ public class UserInfoHelper {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ intentFilter.addAction(Intent.ACTION_USER_STARTED);
+ intentFilter.addAction(Intent.ACTION_USER_STOPPED);
intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
@@ -96,12 +111,24 @@ public class UserInfoHelper {
if (action == null) {
return;
}
+ int userId;
switch (action) {
case Intent.ACTION_USER_SWITCHED:
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL);
+ userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId != UserHandle.USER_NULL) {
+ onCurrentUserChanged(userId);
+ }
+ break;
+ case Intent.ACTION_USER_STARTED:
+ userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId != UserHandle.USER_NULL) {
+ onUserChanged(userId, UserListener.USER_STARTED);
+ }
+ break;
+ case Intent.ACTION_USER_STOPPED:
+ userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
if (userId != UserHandle.USER_NULL) {
- onUserChanged(userId);
+ onUserChanged(userId, UserListener.USER_STOPPED);
}
break;
case Intent.ACTION_MANAGED_PROFILE_ADDED:
@@ -118,18 +145,18 @@ public class UserInfoHelper {
/**
* Adds a listener for user changed events. Callbacks occur on an unspecified thread.
*/
- public void addListener(UserChangedListener listener) {
+ public void addListener(UserListener listener) {
mListeners.add(listener);
}
/**
* Removes a listener for user changed events.
*/
- public void removeListener(UserChangedListener listener) {
+ public void removeListener(UserListener listener) {
mListeners.remove(listener);
}
- private void onUserChanged(@UserIdInt int newUserId) {
+ private void onCurrentUserChanged(@UserIdInt int newUserId) {
if (newUserId == mCurrentUserId) {
return;
}
@@ -137,8 +164,13 @@ public class UserInfoHelper {
int oldUserId = mCurrentUserId;
mCurrentUserId = newUserId;
- for (UserChangedListener listener : mListeners) {
- listener.onUserChanged(oldUserId, newUserId);
+ onUserChanged(oldUserId, UserListener.USER_SWITCHED);
+ onUserChanged(newUserId, UserListener.USER_SWITCHED);
+ }
+
+ private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) {
+ for (UserListener listener : mListeners) {
+ listener.onUserChanged(userId, change);
}
}
@@ -151,53 +183,23 @@ public class UserInfoHelper {
}
/**
- * Returns the user id of the current user.
+ * Returns an array of current user ids. This will always include the current user, and will
+ * also include any profiles of the current user.
*/
- @UserIdInt
- public int getCurrentUserId() {
- return mCurrentUserId;
+ public int[] getCurrentUserIds() {
+ return getProfileUserIdsForParentUser(mCurrentUserId);
}
/**
* Returns true if the given user id is either the current user or a profile of the current
* user.
*/
- public boolean isCurrentUserOrProfile(@UserIdInt int userId) {
+ public boolean isCurrentUserId(@UserIdInt int userId) {
int currentUserId = mCurrentUserId;
return userId == currentUserId || ArrayUtils.contains(
getProfileUserIdsForParentUser(currentUserId), userId);
}
- /**
- * Returns the parent user id of the given user id, or the user id itself if the user id either
- * is a parent or has no profiles.
- */
- @UserIdInt
- public int getParentUserId(@UserIdInt int userId) {
- synchronized (this) {
- if (userId == mCachedParentUserId || ArrayUtils.contains(mCachedProfileUserIds,
- userId)) {
- return mCachedParentUserId;
- }
-
- Preconditions.checkState(mUserManager != null);
- }
-
- int parentUserId;
-
- long identity = Binder.clearCallingIdentity();
- try {
- UserInfo userInfo = mUserManager.getProfileParent(userId);
- parentUserId = userInfo != null ? userInfo.id : userId;
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
-
- // force profiles into cache
- getProfileUserIdsForParentUser(parentUserId);
- return parentUserId;
- }
-
@GuardedBy("this")
private synchronized int[] getProfileUserIdsForParentUser(@UserIdInt int parentUserId) {
if (parentUserId != mCachedParentUserId) {
@@ -225,8 +227,22 @@ public class UserInfoHelper {
* Dump info for debugging.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- int currentUserId = mCurrentUserId;
- pw.println("Current User: " + currentUserId + " " + Arrays.toString(
- getProfileUserIdsForParentUser(currentUserId)));
+ boolean systemRunning;
+ synchronized (this) {
+ systemRunning = mUserManager != null;
+ }
+
+ if (systemRunning) {
+ int[] currentUserIds = getProfileUserIdsForParentUser(mCurrentUserId);
+ pw.println("current users: " + Arrays.toString(currentUserIds));
+ for (int userId : currentUserIds) {
+ if (mUserManager.hasUserRestrictionForUser(DISALLOW_SHARE_LOCATION,
+ UserHandle.of(userId))) {
+ pw.println(" u" + userId + " restricted");
+ }
+ }
+ } else {
+ pw.println("current user: " + mCurrentUserId);
+ }
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 02c1bddd8c00..87bb28e710c1 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.location.gnss;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
+import static android.location.LocationManager.GPS_PROVIDER;
import android.Manifest;
import android.annotation.NonNull;
@@ -39,6 +40,7 @@ import android.os.IBinder;
import android.os.IInterface;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.stats.location.LocationStatsEnums;
import android.util.ArrayMap;
import android.util.Log;
@@ -588,18 +590,26 @@ public class GnssManagerService {
*/
public void onReportLocation(List<Location> locations) {
IBatchedLocationCallback gnssBatchingCallback;
+ LinkedListener<IBatchedLocationCallback> gnssBatchingDeathCallback;
synchronized (mGnssBatchingLock) {
gnssBatchingCallback = mGnssBatchingCallback;
+ gnssBatchingDeathCallback = mGnssBatchingDeathCallback;
}
- if (gnssBatchingCallback == null) {
+ if (gnssBatchingCallback == null || gnssBatchingDeathCallback == null) {
+ return;
+ }
+
+ int userId = UserHandle.getUserId(gnssBatchingDeathCallback.getCallerIdentity().mUid);
+ if (!mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER, userId)) {
+ Log.w(TAG, "reportLocationBatch() called without user permission");
return;
}
try {
gnssBatchingCallback.onLocationBatch(locations);
} catch (RemoteException e) {
- Log.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e);
+ Log.e(TAG, "reportLocationBatch() failed", e);
}
}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 339ff6b8b526..ff34ebd8aa9d 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -24,6 +24,7 @@ android_test {
"service-permission",
"service-blobstore",
"androidx.test.runner",
+ "androidx.test.ext.truth",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
"truth-prebuilt",
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java
new file mode 100644
index 000000000000..f2246dac01ca
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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 androidx.test.ext.truth.location.LocationSubject.assertThat;
+
+import static com.android.server.location.LocationUtils.createLocation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.location.Location;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Random;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LocationFudgerTest {
+
+ private static final String TAG = "LocationFudgerTest";
+
+ private static final double APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR = 111_000;
+ private static final float ACCURACY_M = 2000;
+ private static final float MAX_COARSE_FUDGE_DISTANCE_M =
+ (float) Math.sqrt(2 * ACCURACY_M * ACCURACY_M);
+
+ private Random mRandom;
+
+ private LocationFudger mFudger;
+
+ @Before
+ public void setUp() {
+ long seed = System.currentTimeMillis();
+ Log.i(TAG, "location random seed: " + seed);
+
+ mRandom = new Random(seed);
+ mFudger = new LocationFudger(
+ ACCURACY_M,
+ Clock.fixed(Instant.ofEpochMilli(0), ZoneId.systemDefault()),
+ mRandom);
+ }
+
+ @Test
+ public void testCoarsen() {
+ // test that the coarsened location is not the same as the fine location and no leaks
+ for (int i = 0; i < 100; i++) {
+ Location fine = createLocation("test", mRandom);
+ fine.setBearing(1);
+ fine.setSpeed(1);
+ fine.setAltitude(1);
+
+ Location coarse = mFudger.createCoarse(fine);
+
+ assertThat(coarse).isNotNull();
+ assertThat(coarse).isNotSameAs(fine);
+ assertThat(coarse.hasBearing()).isFalse();
+ assertThat(coarse.hasSpeed()).isFalse();
+ assertThat(coarse.hasAltitude()).isFalse();
+ assertThat(coarse.getAccuracy()).isEqualTo(ACCURACY_M);
+ assertThat(coarse.distanceTo(fine)).isGreaterThan(1F);
+ assertThat(coarse).isNearby(fine, MAX_COARSE_FUDGE_DISTANCE_M);
+ }
+ }
+
+ @Test
+ public void testCoarsen_Consistent() {
+ // test that coarsening the same location will always return the same coarse location
+ // (and thus that averaging to eliminate random noise won't work)
+ for (int i = 0; i < 100; i++) {
+ Location fine = createLocation("test", mRandom);
+ Location coarse = mFudger.createCoarse(fine);
+ assertThat(mFudger.createCoarse(new Location(fine))).isEqualTo(coarse);
+ assertThat(mFudger.createCoarse(new Location(fine))).isEqualTo(coarse);
+ }
+ }
+
+ @Test
+ public void testCoarsen_AvgMany() {
+ // test that a set of locations normally distributed around the user's real location still
+ // cannot be easily average to reveal the user's real location
+
+ int passed = 0;
+ int iterations = 100;
+ for (int j = 0; j < iterations; j++) {
+ Location fine = createLocation("test", mRandom);
+
+ // generate a point cloud around a single location
+ ArrayList<Location> finePoints = new ArrayList<>(100);
+ for (int i = 0; i < 100; i++) {
+ finePoints.add(step(fine, mRandom.nextGaussian() * ACCURACY_M));
+ }
+
+ // generate the coarsened version of that point cloud
+ ArrayList<Location> coarsePoints = new ArrayList<>(100);
+ for (int i = 0; i < 100; i++) {
+ coarsePoints.add(mFudger.createCoarse(finePoints.get(i)));
+ }
+
+ double avgFineLatitude = finePoints.stream().mapToDouble(
+ Location::getLatitude).average()
+ .orElseThrow(IllegalStateException::new);
+ double avgFineLongitude = finePoints.stream().mapToDouble(
+ Location::getLongitude).average()
+ .orElseThrow(IllegalStateException::new);
+ Location fineAvg = createLocation("test", avgFineLatitude, avgFineLongitude, 0);
+
+ double avgCoarseLatitude = coarsePoints.stream().mapToDouble(
+ Location::getLatitude).average()
+ .orElseThrow(IllegalStateException::new);
+ double avgCoarseLongitude = coarsePoints.stream().mapToDouble(
+ Location::getLongitude).average()
+ .orElseThrow(IllegalStateException::new);
+ Location coarseAvg = createLocation("test", avgCoarseLatitude, avgCoarseLongitude, 0);
+
+ if (coarseAvg.distanceTo(fine) > fineAvg.distanceTo(fine)) {
+ passed++;
+ }
+ }
+
+ // very generally speaking, the closer the initial fine point is to a grid point, the more
+ // accurate the coarsened average will be. we use 70% as a lower bound by -very- roughly
+ // taking the area within a grid where we expect a reasonable percentage of points generated
+ // by step() to fall in another grid square. this likely doesn't have much mathematical
+ // validity, but it serves as a sanity test as least.
+ assertThat(passed / (double) iterations).isGreaterThan(.70);
+ }
+
+ // step in a random direction by distance - assume cartesian
+ private Location step(Location input, double distanceM) {
+ double radians = mRandom.nextDouble() * 2 * Math.PI;
+ double deltaXM = Math.cos(radians) * distanceM;
+ double deltaYM = Math.sin(radians) * distanceM;
+ return createLocation("test",
+ input.getLatitude() + deltaXM / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR,
+ input.getLongitude() + deltaYM / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR,
+ 0);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationUtils.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationUtils.java
new file mode 100644
index 000000000000..decb3a6e334a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationUtils.java
@@ -0,0 +1,51 @@
+/*
+ * 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.location.Location;
+import android.os.SystemClock;
+
+import java.util.Random;
+
+public final class LocationUtils {
+
+ private static final double MIN_LATITUDE = -90D;
+ private static final double MAX_LATITUDE = 90D;
+ private static final double MIN_LONGITUDE = -180D;
+ private static final double MAX_LONGITUDE = 180D;
+
+ private static final float MIN_ACCURACY = 1;
+ private static final float MAX_ACCURACY = 100;
+
+ public static Location createLocation(String provider, Random random) {
+ return createLocation(provider,
+ MIN_LATITUDE + random.nextDouble() * (MAX_LATITUDE - MIN_LATITUDE),
+ MIN_LONGITUDE + random.nextDouble() * (MAX_LONGITUDE - MIN_LONGITUDE),
+ MIN_ACCURACY + random.nextFloat() * (MAX_ACCURACY - MIN_ACCURACY));
+ }
+
+ public static Location createLocation(String provider, double latitude, double longitude,
+ float accuracy) {
+ Location location = new Location(provider);
+ location.setLatitude(latitude);
+ location.setLongitude(longitude);
+ location.setAccuracy(accuracy);
+ location.setTime(System.currentTimeMillis());
+ location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+ return location;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java
index 389fdf9b0abc..71e79b331cae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java
@@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
@@ -43,6 +44,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.server.location.UserInfoHelper.UserListener;
import org.junit.After;
import org.junit.Before;
@@ -117,62 +119,88 @@ public class UserInfoHelperTest {
}
}
+ private void startUser(int userId) {
+ Intent intent = new Intent(Intent.ACTION_USER_STARTED).putExtra(Intent.EXTRA_USER_HANDLE,
+ userId);
+ for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) {
+ broadcastReceiver.onReceive(mContext, intent);
+ }
+ }
+
+ private void stopUser(int userId) {
+ Intent intent = new Intent(Intent.ACTION_USER_STOPPED).putExtra(Intent.EXTRA_USER_HANDLE,
+ userId);
+ for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) {
+ broadcastReceiver.onReceive(mContext, intent);
+ }
+ }
+
@Test
- public void testListeners() {
- UserInfoHelper.UserChangedListener listener = mock(
- UserInfoHelper.UserChangedListener.class);
+ public void testListener_SwitchUser() {
+ UserListener listener = mock(UserListener.class);
mHelper.addListener(listener);
switchUser(USER1_ID);
verify(listener, never()).onUserChanged(anyInt(), anyInt());
switchUser(USER2_ID);
- verify(listener).onUserChanged(USER1_ID, USER2_ID);
+ verify(listener, times(1)).onUserChanged(USER1_ID, UserListener.USER_SWITCHED);
+ verify(listener, times(1)).onUserChanged(USER2_ID, UserListener.USER_SWITCHED);
switchUser(USER1_ID);
- verify(listener).onUserChanged(USER2_ID, USER1_ID);
+ verify(listener, times(2)).onUserChanged(USER1_ID, UserListener.USER_SWITCHED);
+ verify(listener, times(2)).onUserChanged(USER2_ID, UserListener.USER_SWITCHED);
}
@Test
- public void testCurrentUser() {
- assertThat(mHelper.getCurrentUserId()).isEqualTo(USER1_ID);
+ public void testListener_StartUser() {
+ UserListener listener = mock(UserListener.class);
+ mHelper.addListener(listener);
- switchUser(USER2_ID);
+ startUser(USER1_ID);
+ verify(listener).onUserChanged(USER1_ID, UserListener.USER_STARTED);
- assertThat(mHelper.getCurrentUserId()).isEqualTo(USER2_ID);
+ startUser(USER2_ID);
+ verify(listener).onUserChanged(USER2_ID, UserListener.USER_STARTED);
+ }
- switchUser(USER1_ID);
+ @Test
+ public void testListener_StopUser() {
+ UserListener listener = mock(UserListener.class);
+ mHelper.addListener(listener);
+
+ stopUser(USER1_ID);
+ verify(listener).onUserChanged(USER1_ID, UserListener.USER_STOPPED);
- assertThat(mHelper.getCurrentUserId()).isEqualTo(USER1_ID);
+ stopUser(USER2_ID);
+ verify(listener).onUserChanged(USER2_ID, UserListener.USER_STOPPED);
}
@Test
- public void testIsCurrentUserOrProfile() {
- assertThat(mHelper.isCurrentUserOrProfile(USER1_ID)).isTrue();
- assertThat(mHelper.isCurrentUserOrProfile(USER1_MANAGED_ID)).isTrue();
- assertThat(mHelper.isCurrentUserOrProfile(USER2_ID)).isFalse();
- assertThat(mHelper.isCurrentUserOrProfile(USER2_MANAGED_ID)).isFalse();
+ public void testCurrentUserIds() {
+ assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER1_PROFILES);
switchUser(USER2_ID);
- assertThat(mHelper.isCurrentUserOrProfile(USER1_ID)).isFalse();
- assertThat(mHelper.isCurrentUserOrProfile(USER2_ID)).isTrue();
- assertThat(mHelper.isCurrentUserOrProfile(USER1_MANAGED_ID)).isFalse();
- assertThat(mHelper.isCurrentUserOrProfile(USER2_MANAGED_ID)).isTrue();
+ assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER2_PROFILES);
+
+ switchUser(USER1_ID);
+
+ assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER1_PROFILES);
}
@Test
- public void testGetParentUserId() {
- assertThat(mHelper.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
- assertThat(mHelper.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
- assertThat(mHelper.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
- assertThat(mHelper.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+ public void testIsCurrentUserId() {
+ assertThat(mHelper.isCurrentUserId(USER1_ID)).isTrue();
+ assertThat(mHelper.isCurrentUserId(USER1_MANAGED_ID)).isTrue();
+ assertThat(mHelper.isCurrentUserId(USER2_ID)).isFalse();
+ assertThat(mHelper.isCurrentUserId(USER2_MANAGED_ID)).isFalse();
switchUser(USER2_ID);
- assertThat(mHelper.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
- assertThat(mHelper.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
- assertThat(mHelper.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
- assertThat(mHelper.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+ assertThat(mHelper.isCurrentUserId(USER1_ID)).isFalse();
+ assertThat(mHelper.isCurrentUserId(USER2_ID)).isTrue();
+ assertThat(mHelper.isCurrentUserId(USER1_MANAGED_ID)).isFalse();
+ assertThat(mHelper.isCurrentUserId(USER2_MANAGED_ID)).isTrue();
}
}