summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Soonil Nagarkar <sooniln@google.com> 2024-03-11 15:43:38 -0700
committer Soonil Nagarkar <sooniln@google.com> 2024-03-21 11:35:18 -0700
commita4e75a93c3e3960f02602655db1d24c29d783fd9 (patch)
treeffdc60d17cbcb7ab9190655921dae6ead34bf3ce
parent4bd98c57874eb2b9d4d82601a912effc1e2bed80 (diff)
Allow LOCATION_BYPASS permission to be used for location
During an emergency call, allow the LOCATION_BYPASS permission to bypass normal location permission, using a noteOp to OP_EMERGENCY_LOCATION instead. Bug: 301150056 Test: atest LocationProviderManagerTest Change-Id: Ie52134caa840bc8b90d6b1aea30c255731463a66
-rw-r--r--location/java/android/location/flags/location.aconfig7
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java63
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java99
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java60
5 files changed, 210 insertions, 21 deletions
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index f33bcb7f9643..5b110e55dd5d 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -8,6 +8,13 @@ flag {
}
flag {
+ name: "enable_location_bypass"
+ namespace: "location"
+ description: "Enable location bypass feature"
+ bug: "301150056"
+}
+
+flag {
name: "location_bypass"
namespace: "location"
description: "Enable location bypass appops behavior"
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index a608049cd677..6e991b4db2b1 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.location;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.LOCATION_BYPASS;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.app.compat.CompatChanges.isChangeEnabled;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
@@ -34,6 +35,7 @@ import static android.location.provider.LocationProviderBase.ACTION_NETWORK_PROV
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
+import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@@ -73,6 +75,7 @@ import android.location.LocationManagerInternal.LocationPackageTagsListener;
import android.location.LocationProvider;
import android.location.LocationRequest;
import android.location.LocationTime;
+import android.location.flags.Flags;
import android.location.provider.ForwardGeocodeRequest;
import android.location.provider.IGeocodeCallback;
import android.location.provider.IProviderRequestListener;
@@ -776,8 +779,19 @@ public class LocationManagerService extends ILocationManager.Stub implements
listenerId);
int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
identity.getPid());
- LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
- PERMISSION_COARSE);
+ if (Flags.enableLocationBypass()) {
+ if (permissionLevel == PERMISSION_NONE) {
+ if (mContext.checkCallingPermission(LOCATION_BYPASS) != PERMISSION_GRANTED) {
+ LocationPermissions.enforceLocationPermission(
+ identity.getUid(), permissionLevel, PERMISSION_COARSE);
+ } else {
+ permissionLevel = PERMISSION_FINE;
+ }
+ }
+ } else {
+ LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
+ PERMISSION_COARSE);
+ }
// clients in the system process must have an attribution tag set
Preconditions.checkState(identity.getPid() != Process.myPid() || attributionTag != null);
@@ -805,8 +819,19 @@ public class LocationManagerService extends ILocationManager.Stub implements
listenerId);
int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
identity.getPid());
- LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
- PERMISSION_COARSE);
+ if (Flags.enableLocationBypass()) {
+ if (permissionLevel == PERMISSION_NONE) {
+ if (mContext.checkCallingPermission(LOCATION_BYPASS) != PERMISSION_GRANTED) {
+ LocationPermissions.enforceLocationPermission(
+ identity.getUid(), permissionLevel, PERMISSION_COARSE);
+ } else {
+ permissionLevel = PERMISSION_FINE;
+ }
+ }
+ } else {
+ LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
+ PERMISSION_COARSE);
+ }
// clients in the system process should have an attribution tag set
if (identity.getPid() == Process.myPid() && attributionTag == null) {
@@ -830,8 +855,19 @@ public class LocationManagerService extends ILocationManager.Stub implements
AppOpsManager.toReceiverId(pendingIntent));
int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
identity.getPid());
- LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
- PERMISSION_COARSE);
+ if (Flags.enableLocationBypass()) {
+ if (permissionLevel == PERMISSION_NONE) {
+ if (mContext.checkCallingPermission(LOCATION_BYPASS) != PERMISSION_GRANTED) {
+ LocationPermissions.enforceLocationPermission(
+ identity.getUid(), permissionLevel, PERMISSION_COARSE);
+ } else {
+ permissionLevel = PERMISSION_FINE;
+ }
+ }
+ } else {
+ LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
+ PERMISSION_COARSE);
+ }
// clients in the system process must have an attribution tag set
Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null);
@@ -982,8 +1018,19 @@ public class LocationManagerService extends ILocationManager.Stub implements
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
identity.getPid());
- LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
- PERMISSION_COARSE);
+ if (Flags.enableLocationBypass()) {
+ if (permissionLevel == PERMISSION_NONE) {
+ if (mContext.checkCallingPermission(LOCATION_BYPASS) != PERMISSION_GRANTED) {
+ LocationPermissions.enforceLocationPermission(
+ identity.getUid(), permissionLevel, PERMISSION_COARSE);
+ } else {
+ permissionLevel = PERMISSION_FINE;
+ }
+ }
+ } else {
+ LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
+ PERMISSION_COARSE);
+ }
// clients in the system process must have an attribution tag set
Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null);
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 40e538b02728..542a29ae4172 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -16,6 +16,7 @@
package com.android.server.location.provider;
+import static android.Manifest.permission.LOCATION_BYPASS;
import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
import static android.app.compat.CompatChanges.isChangeEnabled;
@@ -51,6 +52,7 @@ import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.AlarmManager.OnAlarmListener;
+import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.content.Context;
@@ -66,6 +68,7 @@ import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.LocationResult.BadLocationException;
import android.location.altitude.AltitudeConverter;
+import android.location.flags.Flags;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
@@ -106,6 +109,7 @@ import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.AppForegroundHelper.AppForegroundListener;
import com.android.server.location.injector.AppOpsHelper;
+import com.android.server.location.injector.EmergencyHelper;
import com.android.server.location.injector.Injector;
import com.android.server.location.injector.LocationPermissionsHelper;
import com.android.server.location.injector.LocationPermissionsHelper.LocationPermissionsListener;
@@ -375,8 +379,13 @@ public class LocationProviderManager extends
// we cache these values because checking/calculating on the fly is more expensive
@GuardedBy("mMultiplexerLock")
private boolean mPermitted;
+
+ @GuardedBy("mMultiplexerLock")
+ private boolean mBypassPermitted;
+
@GuardedBy("mMultiplexerLock")
private boolean mForeground;
+
@GuardedBy("mMultiplexerLock")
private LocationRequest mProviderLocationRequest;
@GuardedBy("mMultiplexerLock")
@@ -421,8 +430,8 @@ public class LocationProviderManager extends
EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), mBaseRequest);
// initialization order is important as there are ordering dependencies
- mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
- getIdentity());
+ onLocationPermissionsChanged();
+ onBypassLocationPermissionsChanged(mEmergencyHelper.isInEmergency(0));
mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid());
mProviderLocationRequest = calculateProviderLocationRequest();
mIsUsingHighPower = isUsingHighPower();
@@ -491,7 +500,13 @@ public class LocationProviderManager extends
public final boolean isPermitted() {
synchronized (mMultiplexerLock) {
- return mPermitted;
+ return mPermitted || mBypassPermitted;
+ }
+ }
+
+ public final boolean isOnlyBypassPermitted() {
+ synchronized (mMultiplexerLock) {
+ return mBypassPermitted && !mPermitted;
}
}
@@ -562,6 +577,33 @@ public class LocationProviderManager extends
}
}
+ boolean onBypassLocationPermissionsChanged(boolean isInEmergency) {
+ synchronized (mMultiplexerLock) {
+ boolean bypassPermitted =
+ Flags.enableLocationBypass() && isInEmergency
+ && mContext.checkPermission(
+ LOCATION_BYPASS, mIdentity.getPid(), mIdentity.getUid())
+ == PERMISSION_GRANTED;
+ if (mBypassPermitted != bypassPermitted) {
+ if (D) {
+ Log.v(
+ TAG,
+ mName
+ + " provider package "
+ + getIdentity().getPackageName()
+ + " bypass permitted = "
+ + bypassPermitted);
+ }
+
+ mBypassPermitted = bypassPermitted;
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
@GuardedBy("mMultiplexerLock")
private boolean onLocationPermissionsChanged() {
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -941,8 +983,11 @@ public class LocationProviderManager extends
}
// note app ops
- if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(getPermissionLevel()),
- getIdentity())) {
+ int op =
+ Flags.enableLocationBypass() && isOnlyBypassPermitted()
+ ? AppOpsManager.OP_EMERGENCY_LOCATION
+ : LocationPermissions.asAppOp(getPermissionLevel());
+ if (!mAppOpsHelper.noteOpNoThrow(op, getIdentity())) {
if (D) {
Log.w(TAG,
mName + " provider registration " + getIdentity() + " noteOp denied");
@@ -1292,12 +1337,17 @@ public class LocationProviderManager extends
}
// lastly - note app ops
- if (fineLocationResult != null && !mAppOpsHelper.noteOpNoThrow(
- LocationPermissions.asAppOp(getPermissionLevel()), getIdentity())) {
- if (D) {
- Log.w(TAG, "noteOp denied for " + getIdentity());
+ if (fineLocationResult != null) {
+ int op =
+ Flags.enableLocationBypass() && isOnlyBypassPermitted()
+ ? AppOpsManager.OP_EMERGENCY_LOCATION
+ : LocationPermissions.asAppOp(getPermissionLevel());
+ if (!mAppOpsHelper.noteOpNoThrow(op, getIdentity())) {
+ if (D) {
+ Log.w(TAG, "noteOp denied for " + getIdentity());
+ }
+ fineLocationResult = null;
}
- fineLocationResult = null;
}
if (fineLocationResult != null) {
@@ -1399,6 +1449,7 @@ public class LocationProviderManager extends
protected final ScreenInteractiveHelper mScreenInteractiveHelper;
protected final LocationUsageLogger mLocationUsageLogger;
protected final LocationFudger mLocationFudger;
+ protected final EmergencyHelper mEmergencyHelper;
private final PackageResetHelper mPackageResetHelper;
private final UserListener mUserChangedListener = this::onUserChanged;
@@ -1434,6 +1485,8 @@ public class LocationProviderManager extends
this::onLocationPowerSaveModeChanged;
private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener =
this::onScreenInteractiveChanged;
+ private final EmergencyHelper.EmergencyStateChangedListener mEmergencyStateChangedListener =
+ this::onEmergencyStateChanged;
private final PackageResetHelper.Responder mPackageResetResponder =
new PackageResetHelper.Responder() {
@Override
@@ -1507,6 +1560,7 @@ public class LocationProviderManager extends
mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
mLocationUsageLogger = injector.getLocationUsageLogger();
mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
+ mEmergencyHelper = injector.getEmergencyHelper();
mPackageResetHelper = injector.getPackageResetHelper();
mProvider = new MockableLocationProvider(mMultiplexerLock);
@@ -1757,8 +1811,17 @@ public class LocationProviderManager extends
if (location != null) {
// lastly - note app ops
- if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
- identity)) {
+ int op =
+ (Flags.enableLocationBypass()
+ && !mLocationPermissionsHelper.hasLocationPermissions(
+ permissionLevel, identity)
+ && mEmergencyHelper.isInEmergency(0)
+ && mContext.checkPermission(
+ LOCATION_BYPASS, identity.getPid(), identity.getUid())
+ == PERMISSION_GRANTED)
+ ? AppOpsManager.OP_EMERGENCY_LOCATION
+ : LocationPermissions.asAppOp(permissionLevel);
+ if (!mAppOpsHelper.noteOpNoThrow(op, identity)) {
return null;
}
@@ -2069,6 +2132,9 @@ public class LocationProviderManager extends
mAppForegroundHelper.addListener(mAppForegroundChangedListener);
mLocationPowerSaveModeHelper.addListener(mLocationPowerSaveModeChangedListener);
mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
+ if (Flags.enableLocationBypass()) {
+ mEmergencyHelper.addOnEmergencyStateChangedListener(mEmergencyStateChangedListener);
+ }
mPackageResetHelper.register(mPackageResetResponder);
}
@@ -2088,6 +2154,9 @@ public class LocationProviderManager extends
mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
mLocationPowerSaveModeHelper.removeListener(mLocationPowerSaveModeChangedListener);
mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
+ if (Flags.enableLocationBypass()) {
+ mEmergencyHelper.removeOnEmergencyStateChangedListener(mEmergencyStateChangedListener);
+ }
mPackageResetHelper.unregister(mPackageResetResponder);
}
@@ -2466,6 +2535,12 @@ public class LocationProviderManager extends
}
}
+ private void onEmergencyStateChanged() {
+ boolean inEmergency = mEmergencyHelper.isInEmergency(0);
+ updateRegistrations(
+ registration -> registration.onBypassLocationPermissionsChanged(inEmergency));
+ }
+
private void onBackgroundThrottlePackageWhitelistChanged() {
updateRegistrations(Registration::onProviderLocationRequestChanged);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index ca730910943b..7f1a0bb1a5da 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -110,7 +110,7 @@ public class TestInjector implements Injector {
}
@Override
- public EmergencyHelper getEmergencyHelper() {
+ public FakeEmergencyHelper getEmergencyHelper() {
return mEmergencyHelper;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 32878b3e199f..09282646ff68 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -16,6 +16,9 @@
package com.android.server.location.provider;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.LOCATION_BYPASS;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
@@ -1170,6 +1173,63 @@ public class LocationProviderManagerTest {
}
@Test
+ public void testProviderRequest_IgnoreLocationSettings_LocationBypass() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LOCATION_BYPASS);
+
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mContext)
+ .checkPermission(LOCATION_BYPASS, IDENTITY.getPid(), IDENTITY.getUid());
+ mInjector.getLocationPermissionsHelper()
+ .revokePermission(IDENTITY.getPackageName(), ACCESS_FINE_LOCATION);
+ mInjector.getLocationPermissionsHelper()
+ .revokePermission(IDENTITY.getPackageName(), ACCESS_COARSE_LOCATION);
+ mInjector
+ .getSettingsHelper()
+ .setIgnoreSettingsAllowlist(
+ new PackageTagsList.Builder().add(IDENTITY.getPackageName()).build());
+
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request =
+ new LocationRequest.Builder(1)
+ .setLocationSettingsIgnored(true)
+ .setWorkSource(WORK_SOURCE)
+ .build();
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ }
+
+ @Test
+ public void testProviderRequest_IgnoreLocationSettings_LocationBypass_EmergencyCall() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LOCATION_BYPASS);
+
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mContext)
+ .checkPermission(LOCATION_BYPASS, IDENTITY.getPid(), IDENTITY.getUid());
+ mInjector.getLocationPermissionsHelper()
+ .revokePermission(IDENTITY.getPackageName(), ACCESS_FINE_LOCATION);
+ mInjector.getLocationPermissionsHelper()
+ .revokePermission(IDENTITY.getPackageName(), ACCESS_COARSE_LOCATION);
+ mInjector.getEmergencyHelper().setInEmergency(true);
+ mInjector
+ .getSettingsHelper()
+ .setIgnoreSettingsAllowlist(
+ new PackageTagsList.Builder().add(IDENTITY.getPackageName()).build());
+
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request =
+ new LocationRequest.Builder(1)
+ .setLocationSettingsIgnored(true)
+ .setWorkSource(WORK_SOURCE)
+ .build();
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ assertThat(mProvider.getRequest().isActive()).isTrue();
+ assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(1);
+ assertThat(mProvider.getRequest().isLocationSettingsIgnored()).isTrue();
+ }
+
+ @Test
public void testProviderRequest_BackgroundThrottle_IgnoreLocationSettings() {
mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
new PackageTagsList.Builder().add(