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
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index f33bcb7..5b110e5 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -8,6 +8,13 @@
}
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 a608049..6e991b4 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 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.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 @@
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 @@
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 @@
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 @@
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 40e538b..542a29a 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.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.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.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 @@
// 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 @@
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 final boolean isPermitted() {
synchronized (mMultiplexerLock) {
- return mPermitted;
+ return mPermitted || mBypassPermitted;
+ }
+ }
+
+ public final boolean isOnlyBypassPermitted() {
+ synchronized (mMultiplexerLock) {
+ return mBypassPermitted && !mPermitted;
}
}
@@ -562,6 +577,33 @@
}
}
+ 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 @@
}
// 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 @@
}
// 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 @@
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 @@
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 @@
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 @@
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 @@
mAppForegroundHelper.addListener(mAppForegroundChangedListener);
mLocationPowerSaveModeHelper.addListener(mLocationPowerSaveModeChangedListener);
mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
+ if (Flags.enableLocationBypass()) {
+ mEmergencyHelper.addOnEmergencyStateChangedListener(mEmergencyStateChangedListener);
+ }
mPackageResetHelper.register(mPackageResetResponder);
}
@@ -2088,6 +2154,9 @@
mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
mLocationPowerSaveModeHelper.removeListener(mLocationPowerSaveModeChangedListener);
mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
+ if (Flags.enableLocationBypass()) {
+ mEmergencyHelper.removeOnEmergencyStateChangedListener(mEmergencyStateChangedListener);
+ }
mPackageResetHelper.unregister(mPackageResetResponder);
}
@@ -2466,6 +2535,12 @@
}
}
+ 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 ca73091..7f1a0bb 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 @@
}
@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 32878b3..0928264 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 @@
}
@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(