Merge "Add permission gated provider to access the hardware GPS directly."

This commit is contained in:
David Christie
2022-12-12 23:53:40 +00:00
committed by Android (Google) Code Review
7 changed files with 328 additions and 16 deletions

View File

@@ -6252,6 +6252,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 {
@@ -6391,6 +6392,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";
}

View File

@@ -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.

View File

@@ -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";

View File

@@ -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 (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);
}
if (criteria != null && !LocationProvider.propertiesMeetCriteria(name,
manager.getProperties(), criteria)) {
continue;
}
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;
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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