diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 2a5354c9dab99..542c311663c74 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -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"; } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 9b81c09f5c5b1..3619d3ac5253e 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. + * + *
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 5acec79a2d3b2..18672b7fe10d1 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 3ce51c3d1412b..b4c9596933117 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 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 0000000000000..4d112965b9323
--- /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 aa28ad489027f..7dc1935f89ba1 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