diff options
| author | 2024-03-11 15:43:38 -0700 | |
|---|---|---|
| committer | 2024-03-21 11:35:18 -0700 | |
| commit | a4e75a93c3e3960f02602655db1d24c29d783fd9 (patch) | |
| tree | ffdc60d17cbcb7ab9190655921dae6ead34bf3ce | |
| parent | 4bd98c57874eb2b9d4d82601a912effc1e2bed80 (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
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( |