diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 561ec10e67098c024a094c3f00885682d025edec..d7567fc120a081bf89c593bb588f8f38c8b0ed75 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -6246,6 +6246,7 @@ package android.location { method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback(@NonNull android.location.BatchedLocationCallback); field public static final String ACTION_ADAS_GNSS_ENABLED_CHANGED = "android.location.action.ADAS_GNSS_ENABLED_CHANGED"; field public static final String EXTRA_ADAS_GNSS_ENABLED = "android.location.extra.ADAS_GNSS_ENABLED"; + field @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public static final String GPS_HARDWARE_PROVIDER = "gps_hardware"; } public final class LocationRequest implements android.os.Parcelable { @@ -6385,6 +6386,7 @@ package android.location.provider { method public void setAllowed(boolean); method public void setProperties(@NonNull android.location.provider.ProviderProperties); field public static final String ACTION_FUSED_PROVIDER = "com.android.location.service.FusedLocationProvider"; + field public static final String ACTION_GNSS_PROVIDER = "android.location.provider.action.GNSS_PROVIDER"; field public static final String ACTION_NETWORK_PROVIDER = "com.android.location.service.v3.NetworkLocationProvider"; } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 9b81c09f5c5b17e50a7adc8df88b7cbf947cc483..3619d3ac5253e48785316070f2e2dc36ebb59339 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -194,6 +194,21 @@ public class LocationManager { */ public static final String GPS_PROVIDER = "gps"; + /** + * Standard name of the GNSS hardware location provider. + * + * <p>This provider is similar to {@link LocationManager#GPS_PROVIDER}, but it directly uses the + * HAL GNSS implementation and doesn't go through any provider overrides that may exist. This + * provider will only be available when the GPS_PROVIDER is overridden with a proxy using {@link + * android.location.provider.LocationProviderBase#ACTION_GNSS_PROVIDER}, and is intended only + * for use internally by the location provider system. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) + public static final String GPS_HARDWARE_PROVIDER = "gps_hardware"; + /** * A special location provider for receiving locations without actively initiating a location * fix. This location provider is always present. diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java index 5acec79a2d3b2d4ffae72d8879ba306762e1f61f..18672b7fe10d1ef6600c1308a33db23c34a1ced5 100644 --- a/location/java/android/location/provider/LocationProviderBase.java +++ b/location/java/android/location/provider/LocationProviderBase.java @@ -104,8 +104,6 @@ public abstract class LocationProviderBase { /** * The action the wrapping service should have in its intent filter to implement the * {@link android.location.LocationManager#GPS_PROVIDER}. - * - * @hide */ public static final String ACTION_GNSS_PROVIDER = "android.location.provider.action.GNSS_PROVIDER"; diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 3ce51c3d1412b2db958f33510955fa4193098817..b4c9596933117f654bbdb66a0394f95444b79b93 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -24,6 +24,7 @@ import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.location.LocationManager.BLOCK_PENDING_INTENT_SYSTEM_API_USAGE; import static android.location.LocationManager.FUSED_PROVIDER; +import static android.location.LocationManager.GPS_HARDWARE_PROVIDER; import static android.location.LocationManager.GPS_PROVIDER; import static android.location.LocationManager.NETWORK_PROVIDER; import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS; @@ -95,6 +96,7 @@ import android.util.IndentingPrintWriter; import android.util.Log; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.FgThread; @@ -319,6 +321,9 @@ public class LocationManagerService extends ILocationManager.Stub implements for (LocationProviderManager manager : mProviderManagers) { if (providerName.equals(manager.getName())) { + if (!manager.isVisibleToCaller()) { + return null; + } return manager; } } @@ -341,8 +346,9 @@ public class LocationManagerService extends ILocationManager.Stub implements } } - private void addLocationProviderManager(LocationProviderManager manager, - @Nullable AbstractLocationProvider realProvider) { + @VisibleForTesting + void addLocationProviderManager( + LocationProviderManager manager, @Nullable AbstractLocationProvider realProvider) { synchronized (mProviderManagers) { Preconditions.checkState(getLocationProviderManager(manager.getName()) == null); @@ -453,6 +459,20 @@ public class LocationManagerService extends ILocationManager.Stub implements } if (gnssProvider == null) { gnssProvider = mGnssManagerService.getGnssLocationProvider(); + } else { + // If we have a GNSS provider override, add the hardware provider as a standalone + // option for use by apps with the correct permission. Note the GNSS HAL can only + // support a single client, so mGnssManagerService.getGnssLocationProvider() can + // only be installed with a single provider. + LocationProviderManager gnssHardwareManager = + new LocationProviderManager( + mContext, + mInjector, + GPS_HARDWARE_PROVIDER, + mPassiveManager, + Collections.singletonList(Manifest.permission.LOCATION_HARDWARE)); + addLocationProviderManager( + gnssHardwareManager, mGnssManagerService.getGnssLocationProvider()); } LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector, @@ -629,7 +649,9 @@ public class LocationManagerService extends ILocationManager.Stub implements public List<String> getAllProviders() { ArrayList<String> providers = new ArrayList<>(mProviderManagers.size()); for (LocationProviderManager manager : mProviderManagers) { - providers.add(manager.getName()); + if (manager.isVisibleToCaller()) { + providers.add(manager.getName()); + } } return providers; } @@ -644,15 +666,18 @@ public class LocationManagerService extends ILocationManager.Stub implements synchronized (mLock) { ArrayList<String> providers = new ArrayList<>(mProviderManagers.size()); for (LocationProviderManager manager : mProviderManagers) { - String name = manager.getName(); - if (enabledOnly && !manager.isEnabled(UserHandle.getCallingUserId())) { - continue; - } - if (criteria != null && !LocationProvider.propertiesMeetCriteria(name, - manager.getProperties(), criteria)) { - continue; + if (manager.isVisibleToCaller()) { + String name = manager.getName(); + if (enabledOnly && !manager.isEnabled(UserHandle.getCallingUserId())) { + continue; + } + if (criteria != null + && !LocationProvider.propertiesMeetCriteria( + name, manager.getProperties(), criteria)) { + continue; + } + providers.add(name); } - providers.add(name); } return providers; } @@ -1059,7 +1084,9 @@ public class LocationManagerService extends ILocationManager.Stub implements public void addProviderRequestListener(IProviderRequestListener listener) { mContext.enforceCallingOrSelfPermission(INTERACT_ACROSS_USERS, null); for (LocationProviderManager manager : mProviderManagers) { - manager.addProviderRequestListener(listener); + if (manager.isVisibleToCaller()) { + manager.addProviderRequestListener(listener); + } } } @@ -1649,7 +1676,7 @@ public class LocationManagerService extends ILocationManager.Stub implements if (provider != null && !provider.equals(manager.getName())) { continue; } - if (identity.equals(manager.getProviderIdentity())) { + if (identity.equals(manager.getProviderIdentity()) && manager.isVisibleToCaller()) { return true; } } 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 7063cb80d514a6fab4fc3fe8dbd9b610292bcb1f..ffdb53142567d711e423367317b38b15e9f158e5 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -19,6 +19,7 @@ package com.android.server.location.provider; 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; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS; import static android.location.LocationManager.GPS_PROVIDER; import static android.location.LocationManager.KEY_FLUSH_COMPLETE; @@ -48,6 +49,7 @@ import static java.lang.Math.min; import android.annotation.IntDef; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.AlarmManager.OnAlarmListener; import android.app.BroadcastOptions; import android.app.PendingIntent; @@ -124,6 +126,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.NoSuchElementException; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; @@ -1362,6 +1365,10 @@ public class LocationProviderManager extends @GuardedBy("mMultiplexerLock") private final ArrayList<ProviderEnabledListener> mEnabledListeners; + // Extra permissions required to use this provider (on top of the usual location permissions). + // Not guarded because it's read only. + private final Collection<String> mRequiredPermissions; + private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners; protected final LocationManagerInternal mLocationManagerInternal; @@ -1435,12 +1442,34 @@ public class LocationProviderManager extends public LocationProviderManager(Context context, Injector injector, String name, @Nullable PassiveLocationProviderManager passiveManager) { + this(context, injector, name, passiveManager, Collections.emptyList()); + } + + /** + * Creates a manager for a location provider (the two have a 1:1 correspondence). + * + * @param context Context in which the manager is running. + * @param injector Injector to retrieve system components (useful to override in testing) + * @param name Name of this provider (used in LocationManager APIs by client apps). + * @param passiveManager The "passive" manager (special case provider that returns locations + * from all other providers). + * @param requiredPermissions Required permissions for accessing this provider. All of the given + * permissions are required to access the provider. If a caller doesn't hold the correct + * permission, the provider will be invisible to it. + */ + public LocationProviderManager( + Context context, + Injector injector, + String name, + @Nullable PassiveLocationProviderManager passiveManager, + Collection<String> requiredPermissions) { mContext = context; mName = Objects.requireNonNull(name); mPassiveManager = passiveManager; mState = STATE_STOPPED; mEnabled = new SparseBooleanArray(2); mLastLocations = new SparseArray<>(2); + mRequiredPermissions = requiredPermissions; mEnabledListeners = new ArrayList<>(); mProviderRequestListeners = new CopyOnWriteArrayList<>(); @@ -1559,6 +1588,24 @@ public class LocationProviderManager extends } } + /** + * Returns true if this provider is visible to the current caller (whether called from a binder + * thread or not). If a provider isn't visible, then all APIs return the same data they would if + * the provider didn't exist (i.e. the caller can't see or use the provider). + * + * <p>This method doesn't require any permissions, but uses permissions to determine which + * subset of providers are visible. + */ + @SuppressLint("AndroidFrameworkRequiresPermission") + public boolean isVisibleToCaller() { + for (String permission : mRequiredPermissions) { + if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) { + return false; + } + } + return true; + } + public void addEnabledListener(ProviderEnabledListener listener) { synchronized (mMultiplexerLock) { Preconditions.checkState(mState != STATE_STOPPED); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4d112965b93233d7301aa24d9c4776b69e8c667b --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.location.ILocationListener; +import android.location.LocationManagerInternal; +import android.location.LocationRequest; +import android.location.provider.ProviderRequest; +import android.os.IBinder; +import android.os.PowerManager; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.LocalServices; +import com.android.server.location.injector.FakeUserInfoHelper; +import com.android.server.location.injector.TestInjector; +import com.android.server.location.provider.AbstractLocationProvider; +import com.android.server.location.provider.LocationProviderManager; +import com.android.server.pm.permission.LegacyPermissionManagerInternal; + +import com.google.common.util.concurrent.MoreExecutors; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; + +import java.util.Collections; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class LocationManagerServiceTest { + private static final String PROVIDER_WITH_PERMISSION = "provider_with_permission"; + private static final String PROVIDER_WITHOUT_PERMISSION = "provider_without_permission"; + private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID; + private static final String CALLER_PACKAGE = "caller_package"; + private static final String MISSING_PERMISSION = "missing_permission"; + + private TestInjector mInjector; + private LocationManagerService mLocationManagerService; + + @Spy private FakeAbstractLocationProvider mProviderWithPermission; + @Spy private FakeAbstractLocationProvider mProviderWithoutPermission; + @Mock private ILocationListener mLocationListener; + @Mock private IBinder mBinder; + @Mock private Context mContext; + @Mock private Resources mResources; + @Mock private PackageManager mPackageManager; + @Mock private AppOpsManager mAppOpsManager; + @Mock private PowerManager mPowerManager; + @Mock private PowerManager.WakeLock mWakeLock; + @Mock private LegacyPermissionManagerInternal mPermissionManagerInternal; + + @Before + public void setUp() { + initMocks(this); + + doReturn(mContext).when(mContext).createAttributionContext(any()); + doReturn("android").when(mContext).getPackageName(); + doReturn(mResources).when(mContext).getResources(); + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); + doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString()); + doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class); + String[] packages = {CALLER_PACKAGE}; + doReturn(InstrumentationRegistry.getInstrumentation().getContext().getContentResolver()) + .when(mContext) + .getContentResolver(); + doReturn(packages).when(mPackageManager).getPackagesForUid(anyInt()); + doReturn(mBinder).when(mLocationListener).asBinder(); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext) + .checkCallingOrSelfPermission(MISSING_PERMISSION); + + mInjector = new TestInjector(mContext); + mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true); + + LocalServices.addService(LegacyPermissionManagerInternal.class, mPermissionManagerInternal); + + mLocationManagerService = new LocationManagerService(mContext, mInjector); + + LocationProviderManager managerWithPermission = + new LocationProviderManager( + mContext, mInjector, PROVIDER_WITH_PERMISSION, /* passiveManager= */ null); + mLocationManagerService.addLocationProviderManager( + managerWithPermission, mProviderWithPermission); + LocationProviderManager managerWithoutPermission = + new LocationProviderManager( + mContext, + mInjector, + PROVIDER_WITHOUT_PERMISSION, + /* passiveManager= */ null, + Collections.singletonList(MISSING_PERMISSION)); + mLocationManagerService.addLocationProviderManager( + managerWithoutPermission, mProviderWithoutPermission); + } + + @After + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(LegacyPermissionManagerInternal.class); + LocalServices.removeServiceForTest(LocationManagerInternal.class); + } + + @Test + public void testRequestLocationUpdates() { + LocationRequest request = new LocationRequest.Builder(0).build(); + mLocationManagerService.registerLocationListener( + PROVIDER_WITH_PERMISSION, + request, + mLocationListener, + CALLER_PACKAGE, + /* attributionTag= */ null, + "any_listener_id"); + verify(mProviderWithPermission).onSetRequestPublic(any()); + } + + @Test + public void testRequestLocationUpdates_noPermission() { + LocationRequest request = new LocationRequest.Builder(0).build(); + assertThrows( + IllegalArgumentException.class, + () -> + mLocationManagerService.registerLocationListener( + PROVIDER_WITHOUT_PERMISSION, + request, + mLocationListener, + CALLER_PACKAGE, + /* attributionTag= */ null, + "any_listener_id")); + } + + @Test + public void testHasProvider() { + assertThat(mLocationManagerService.hasProvider(PROVIDER_WITH_PERMISSION)).isTrue(); + } + + @Test + public void testHasProvider_noPermission() { + assertThat(mLocationManagerService.hasProvider(PROVIDER_WITHOUT_PERMISSION)).isFalse(); + } + + @Test + public void testGetAllProviders() { + assertThat(mLocationManagerService.getAllProviders()).contains(PROVIDER_WITH_PERMISSION); + assertThat(mLocationManagerService.getAllProviders()) + .doesNotContain(PROVIDER_WITHOUT_PERMISSION); + } + + abstract static class FakeAbstractLocationProvider extends AbstractLocationProvider { + FakeAbstractLocationProvider() { + super( + MoreExecutors.directExecutor(), + /* identity= */ null, + /* properties= */ null, + /* extraAttributionTags= */ Collections.emptySet()); + setAllowed(true); + } + + @Override + protected void onSetRequest(ProviderRequest request) { + // Call a public version of this method so mockito can verify. + onSetRequestPublic(request); + } + + public abstract void onSetRequestPublic(ProviderRequest request); + } +} 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 aa28ad489027f90074647abec20aaa7e0ddfd64b..7dc1935f89ba1efa191681979104c7ebd40eaccc 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 @@ -102,6 +102,7 @@ import org.mockito.Mock; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Random; @@ -133,6 +134,7 @@ public class LocationProviderManagerTest { private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1, "mypackage", "attribution", "listener"); private static final WorkSource WORK_SOURCE = new WorkSource(IDENTITY.getUid()); + private static final String MISSING_PERMISSION = "missing_permission"; private Random mRandom; @@ -173,6 +175,9 @@ public class LocationProviderManagerTest { doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString()); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext) + .checkCallingOrSelfPermission(MISSING_PERMISSION); mInjector = new TestInjector(mContext); mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true); @@ -187,12 +192,18 @@ public class LocationProviderManagerTest { } private void createManager(String name) { + createManager(name, Collections.emptyList()); + } + + private void createManager(String name, Collection<String> requiredPermissions) { mStateChangedListener = mock(LocationProviderManager.StateChangedListener.class); mProvider = new TestProvider(PROPERTIES, PROVIDER_IDENTITY); mProvider.setProviderAllowed(true); - mManager = new LocationProviderManager(mContext, mInjector, name, mPassive); + mManager = + new LocationProviderManager( + mContext, mInjector, name, mPassive, requiredPermissions); mManager.startManager(mStateChangedListener); mManager.setRealProvider(mProvider); } @@ -1317,6 +1328,17 @@ public class LocationProviderManagerTest { assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse(); } + @Test + public void testIsVisibleToCaller() { + assertThat(mManager.isVisibleToCaller()).isTrue(); + } + + @Test + public void testIsVisibleToCaller_noPermissions() { + createManager("any_name", Collections.singletonList(MISSING_PERMISSION)); + assertThat(mManager.isVisibleToCaller()).isFalse(); + } + private ILocationListener createMockLocationListener() { return spy(new ILocationListener.Stub() { @Override