| /* |
| * Copyright (C) 2020 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 android.Manifest.permission.ACCESS_FINE_LOCATION; |
| import static android.app.compat.CompatChanges.isChangeEnabled; |
| import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; |
| 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_PROVIDER; |
| import static android.location.LocationManager.NETWORK_PROVIDER; |
| import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS; |
| import static android.location.provider.LocationProviderBase.ACTION_FUSED_PROVIDER; |
| import static android.location.provider.LocationProviderBase.ACTION_NETWORK_PROVIDER; |
| |
| import static com.android.server.location.LocationPermissions.PERMISSION_COARSE; |
| import static com.android.server.location.LocationPermissions.PERMISSION_FINE; |
| import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG; |
| |
| import static java.util.concurrent.TimeUnit.NANOSECONDS; |
| |
| import android.Manifest; |
| import android.Manifest.permission; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.ActivityManager; |
| import android.app.AppOpsManager; |
| import android.app.PendingIntent; |
| import android.app.compat.CompatChanges; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.location.Criteria; |
| import android.location.GeocoderParams; |
| import android.location.Geofence; |
| import android.location.GnssAntennaInfo; |
| import android.location.GnssCapabilities; |
| import android.location.GnssMeasurementCorrections; |
| import android.location.GnssMeasurementRequest; |
| import android.location.IGeocodeListener; |
| import android.location.IGnssAntennaInfoListener; |
| import android.location.IGnssMeasurementsListener; |
| import android.location.IGnssNavigationMessageListener; |
| import android.location.IGnssNmeaListener; |
| import android.location.IGnssStatusListener; |
| import android.location.ILocationCallback; |
| import android.location.ILocationListener; |
| import android.location.ILocationManager; |
| import android.location.LastLocationRequest; |
| import android.location.Location; |
| import android.location.LocationManager; |
| import android.location.LocationManagerInternal; |
| import android.location.LocationManagerInternal.LocationPackageTagsListener; |
| import android.location.LocationProvider; |
| import android.location.LocationRequest; |
| import android.location.LocationTime; |
| import android.location.provider.IProviderRequestListener; |
| import android.location.provider.ProviderProperties; |
| import android.location.util.identity.CallerIdentity; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.ICancellationSignal; |
| import android.os.PackageTagsList; |
| import android.os.ParcelFileDescriptor; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.os.WorkSource; |
| import android.os.WorkSource.WorkChain; |
| import android.provider.Settings; |
| import android.stats.location.LocationStatsEnums; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.IndentingPrintWriter; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.util.ArrayUtils; |
| import com.android.internal.util.DumpUtils; |
| import com.android.internal.util.Preconditions; |
| import com.android.server.FgThread; |
| import com.android.server.LocalServices; |
| import com.android.server.SystemService; |
| import com.android.server.location.eventlog.LocationEventLog; |
| import com.android.server.location.geofence.GeofenceManager; |
| import com.android.server.location.geofence.GeofenceProxy; |
| import com.android.server.location.gnss.GnssConfiguration; |
| import com.android.server.location.gnss.GnssManagerService; |
| import com.android.server.location.gnss.hal.GnssNative; |
| import com.android.server.location.injector.AlarmHelper; |
| import com.android.server.location.injector.AppForegroundHelper; |
| import com.android.server.location.injector.AppOpsHelper; |
| import com.android.server.location.injector.DeviceIdleHelper; |
| import com.android.server.location.injector.DeviceStationaryHelper; |
| import com.android.server.location.injector.EmergencyHelper; |
| import com.android.server.location.injector.Injector; |
| import com.android.server.location.injector.LocationAttributionHelper; |
| import com.android.server.location.injector.LocationPermissionsHelper; |
| import com.android.server.location.injector.LocationPowerSaveModeHelper; |
| import com.android.server.location.injector.LocationUsageLogger; |
| import com.android.server.location.injector.ScreenInteractiveHelper; |
| import com.android.server.location.injector.SettingsHelper; |
| import com.android.server.location.injector.SystemAlarmHelper; |
| import com.android.server.location.injector.SystemAppForegroundHelper; |
| import com.android.server.location.injector.SystemAppOpsHelper; |
| import com.android.server.location.injector.SystemDeviceIdleHelper; |
| import com.android.server.location.injector.SystemDeviceStationaryHelper; |
| import com.android.server.location.injector.SystemEmergencyHelper; |
| import com.android.server.location.injector.SystemLocationPermissionsHelper; |
| import com.android.server.location.injector.SystemLocationPowerSaveModeHelper; |
| import com.android.server.location.injector.SystemScreenInteractiveHelper; |
| import com.android.server.location.injector.SystemSettingsHelper; |
| import com.android.server.location.injector.SystemUserInfoHelper; |
| import com.android.server.location.injector.UserInfoHelper; |
| import com.android.server.location.provider.AbstractLocationProvider; |
| import com.android.server.location.provider.LocationProviderManager; |
| import com.android.server.location.provider.MockLocationProvider; |
| import com.android.server.location.provider.PassiveLocationProvider; |
| import com.android.server.location.provider.PassiveLocationProviderManager; |
| import com.android.server.location.provider.StationaryThrottlingLocationProvider; |
| import com.android.server.location.provider.proxy.ProxyLocationProvider; |
| import com.android.server.location.settings.LocationSettings; |
| import com.android.server.location.settings.LocationUserSettings; |
| import com.android.server.pm.permission.LegacyPermissionManagerInternal; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| /** |
| * The service class that manages LocationProviders and issues location |
| * updates and alerts. |
| */ |
| public class LocationManagerService extends ILocationManager.Stub implements |
| LocationProviderManager.StateChangedListener { |
| |
| /** |
| * Controls lifecycle of LocationManagerService. |
| */ |
| public static class Lifecycle extends SystemService { |
| |
| private final LifecycleUserInfoHelper mUserInfoHelper; |
| private final SystemInjector mSystemInjector; |
| private final LocationManagerService mService; |
| |
| public Lifecycle(Context context) { |
| super(context); |
| mUserInfoHelper = new LifecycleUserInfoHelper(context); |
| mSystemInjector = new SystemInjector(context, mUserInfoHelper); |
| mService = new LocationManagerService(context, mSystemInjector); |
| } |
| |
| @Override |
| public void onStart() { |
| publishBinderService(Context.LOCATION_SERVICE, mService); |
| |
| // client caching behavior is only enabled after seeing the first invalidate |
| LocationManager.invalidateLocalLocationEnabledCaches(); |
| // disable caching for our own process |
| LocationManager.disableLocalLocationEnabledCaches(); |
| } |
| |
| @Override |
| public void onBootPhase(int phase) { |
| if (phase == PHASE_SYSTEM_SERVICES_READY) { |
| // the location service must be functioning after this boot phase |
| mSystemInjector.onSystemReady(); |
| mService.onSystemReady(); |
| } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { |
| // some providers rely on third party code, so we wait to initialize |
| // providers until third party code is allowed to run |
| mService.onSystemThirdPartyAppsCanStart(); |
| } |
| } |
| |
| @Override |
| public void onUserStarting(TargetUser user) { |
| mUserInfoHelper.onUserStarted(user.getUserIdentifier()); |
| } |
| |
| @Override |
| public void onUserSwitching(TargetUser from, TargetUser to) { |
| mUserInfoHelper.onCurrentUserChanged(from.getUserIdentifier(), |
| to.getUserIdentifier()); |
| } |
| |
| @Override |
| public void onUserStopped(TargetUser user) { |
| mUserInfoHelper.onUserStopped(user.getUserIdentifier()); |
| } |
| |
| private static class LifecycleUserInfoHelper extends SystemUserInfoHelper { |
| |
| LifecycleUserInfoHelper(Context context) { |
| super(context); |
| } |
| |
| void onUserStarted(int userId) { |
| dispatchOnUserStarted(userId); |
| } |
| |
| void onUserStopped(int userId) { |
| dispatchOnUserStopped(userId); |
| } |
| |
| void onCurrentUserChanged(int fromUserId, int toUserId) { |
| dispatchOnCurrentUserChanged(fromUserId, toUserId); |
| } |
| } |
| } |
| |
| public static final String TAG = "LocationManagerService"; |
| public static final boolean D = Log.isLoggable(TAG, Log.DEBUG); |
| |
| private static final String ATTRIBUTION_TAG = "LocationService"; |
| |
| final Object mLock = new Object(); |
| |
| private final Context mContext; |
| private final Injector mInjector; |
| private final LocalService mLocalService; |
| |
| private final GeofenceManager mGeofenceManager; |
| private volatile @Nullable GnssManagerService mGnssManagerService = null; |
| private GeocoderProxy mGeocodeProvider; |
| |
| private final Object mDeprecatedGnssBatchingLock = new Object(); |
| @GuardedBy("mDeprecatedGnssBatchingLock") |
| private @Nullable ILocationListener mDeprecatedGnssBatchingListener; |
| |
| @GuardedBy("mLock") |
| private String mExtraLocationControllerPackage; |
| @GuardedBy("mLock") |
| private boolean mExtraLocationControllerPackageEnabled; |
| |
| // location provider managers |
| |
| private final PassiveLocationProviderManager mPassiveManager; |
| |
| // @GuardedBy("mProviderManagers") |
| // hold lock for writes, no lock necessary for simple reads |
| final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers = |
| new CopyOnWriteArrayList<>(); |
| |
| @GuardedBy("mLock") |
| @Nullable LocationPackageTagsListener mLocationTagsChangedListener; |
| |
| LocationManagerService(Context context, Injector injector) { |
| mContext = context.createAttributionContext(ATTRIBUTION_TAG); |
| mInjector = injector; |
| mLocalService = new LocalService(); |
| LocalServices.addService(LocationManagerInternal.class, mLocalService); |
| |
| mGeofenceManager = new GeofenceManager(mContext, injector); |
| |
| mInjector.getLocationSettings().registerLocationUserSettingsListener( |
| this::onLocationUserSettingsChanged); |
| mInjector.getSettingsHelper().addOnLocationEnabledChangedListener( |
| this::onLocationModeChanged); |
| mInjector.getSettingsHelper().addIgnoreSettingsAllowlistChangedListener( |
| () -> refreshAppOpsRestrictions(UserHandle.USER_ALL)); |
| mInjector.getUserInfoHelper().addListener((userId, change) -> { |
| if (change == UserInfoHelper.UserListener.USER_STARTED) { |
| refreshAppOpsRestrictions(userId); |
| } |
| }); |
| |
| // set up passive provider first since it will be required for all other location providers, |
| // which are loaded later once the system is ready. |
| mPassiveManager = new PassiveLocationProviderManager(mContext, injector); |
| addLocationProviderManager(mPassiveManager, new PassiveLocationProvider(mContext)); |
| |
| // TODO: load the gps provider here as well, which will require refactoring |
| |
| // Let the package manager query which are the default location |
| // providers as they get certain permissions granted by default. |
| LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService( |
| LegacyPermissionManagerInternal.class); |
| permissionManagerInternal.setLocationPackagesProvider( |
| userId -> mContext.getResources().getStringArray( |
| com.android.internal.R.array.config_locationProviderPackageNames)); |
| permissionManagerInternal.setLocationExtraPackagesProvider( |
| userId -> mContext.getResources().getStringArray( |
| com.android.internal.R.array.config_locationExtraPackageNames)); |
| } |
| |
| @Nullable |
| LocationProviderManager getLocationProviderManager(String providerName) { |
| if (providerName == null) { |
| return null; |
| } |
| |
| for (LocationProviderManager manager : mProviderManagers) { |
| if (providerName.equals(manager.getName())) { |
| return manager; |
| } |
| } |
| |
| return null; |
| } |
| |
| private LocationProviderManager getOrAddLocationProviderManager(String providerName) { |
| synchronized (mProviderManagers) { |
| for (LocationProviderManager manager : mProviderManagers) { |
| if (providerName.equals(manager.getName())) { |
| return manager; |
| } |
| } |
| |
| LocationProviderManager manager = new LocationProviderManager(mContext, mInjector, |
| providerName, mPassiveManager); |
| addLocationProviderManager(manager, null); |
| return manager; |
| } |
| } |
| |
| private void addLocationProviderManager(LocationProviderManager manager, |
| @Nullable AbstractLocationProvider realProvider) { |
| synchronized (mProviderManagers) { |
| Preconditions.checkState(getLocationProviderManager(manager.getName()) == null); |
| |
| manager.startManager(this); |
| |
| if (realProvider != null) { |
| // custom logic wrapping all non-passive providers |
| if (manager != mPassiveManager) { |
| boolean enableStationaryThrottling = Settings.Global.getInt( |
| mContext.getContentResolver(), |
| Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, 1) != 0; |
| if (enableStationaryThrottling) { |
| realProvider = new StationaryThrottlingLocationProvider(manager.getName(), |
| mInjector, realProvider); |
| } |
| } |
| manager.setRealProvider(realProvider); |
| } |
| mProviderManagers.add(manager); |
| } |
| } |
| |
| private void removeLocationProviderManager(LocationProviderManager manager) { |
| synchronized (mProviderManagers) { |
| boolean removed = mProviderManagers.remove(manager); |
| Preconditions.checkArgument(removed); |
| manager.setMockProvider(null); |
| manager.setRealProvider(null); |
| manager.stopManager(); |
| } |
| } |
| |
| void onSystemReady() { |
| if (Build.IS_DEBUGGABLE) { |
| // on debug builds, watch for location noteOps while location is off. there are some |
| // scenarios (emergency location) where this is expected, but generally this should |
| // rarely occur, and may indicate bugs. dump occurrences to logs for further evaluation |
| AppOpsManager appOps = Objects.requireNonNull( |
| mContext.getSystemService(AppOpsManager.class)); |
| appOps.startWatchingNoted( |
| new int[]{AppOpsManager.OP_FINE_LOCATION, AppOpsManager.OP_COARSE_LOCATION}, |
| (code, uid, packageName, attributionTag, flags, result) -> { |
| if (!isLocationEnabledForUser(UserHandle.getUserId(uid))) { |
| Log.w(TAG, "location noteOp with location off - " |
| + CallerIdentity.forTest(uid, 0, packageName, attributionTag)); |
| } |
| }); |
| } |
| } |
| |
| void onSystemThirdPartyAppsCanStart() { |
| // network provider should always be initialized before the gps provider since the gps |
| // provider has unfortunate hard dependencies on the network provider |
| ProxyLocationProvider networkProvider = ProxyLocationProvider.create( |
| mContext, |
| NETWORK_PROVIDER, |
| ACTION_NETWORK_PROVIDER, |
| com.android.internal.R.bool.config_enableNetworkLocationOverlay, |
| com.android.internal.R.string.config_networkLocationProviderPackageName); |
| if (networkProvider != null) { |
| LocationProviderManager networkManager = new LocationProviderManager(mContext, |
| mInjector, NETWORK_PROVIDER, mPassiveManager); |
| addLocationProviderManager(networkManager, networkProvider); |
| } else { |
| Log.w(TAG, "no network location provider found"); |
| } |
| |
| // ensure that a fused provider exists which will work in direct boot |
| Preconditions.checkState(!mContext.getPackageManager().queryIntentServicesAsUser( |
| new Intent(ACTION_FUSED_PROVIDER), |
| MATCH_DIRECT_BOOT_AWARE | MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM).isEmpty(), |
| "Unable to find a direct boot aware fused location provider"); |
| |
| ProxyLocationProvider fusedProvider = ProxyLocationProvider.create( |
| mContext, |
| FUSED_PROVIDER, |
| ACTION_FUSED_PROVIDER, |
| com.android.internal.R.bool.config_enableFusedLocationOverlay, |
| com.android.internal.R.string.config_fusedLocationProviderPackageName); |
| if (fusedProvider != null) { |
| LocationProviderManager fusedManager = new LocationProviderManager(mContext, mInjector, |
| FUSED_PROVIDER, mPassiveManager); |
| addLocationProviderManager(fusedManager, fusedProvider); |
| } else { |
| Log.wtf(TAG, "no fused location provider found"); |
| } |
| |
| // initialize gnss last because it has no awareness of boot phases and blindly assumes that |
| // all other location providers are loaded at initialization |
| if (GnssNative.isSupported()) { |
| GnssConfiguration gnssConfiguration = new GnssConfiguration(mContext); |
| GnssNative gnssNative = GnssNative.create(mInjector, gnssConfiguration); |
| mGnssManagerService = new GnssManagerService(mContext, mInjector, gnssNative); |
| mGnssManagerService.onSystemReady(); |
| |
| LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector, |
| GPS_PROVIDER, mPassiveManager); |
| addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider()); |
| } |
| |
| // bind to geocoder provider |
| mGeocodeProvider = GeocoderProxy.createAndRegister(mContext); |
| if (mGeocodeProvider == null) { |
| Log.e(TAG, "no geocoder provider found"); |
| } |
| |
| // bind to hardware activity recognition |
| HardwareActivityRecognitionProxy hardwareActivityRecognitionProxy = |
| HardwareActivityRecognitionProxy.createAndRegister(mContext); |
| if (hardwareActivityRecognitionProxy == null) { |
| Log.e(TAG, "unable to bind ActivityRecognitionProxy"); |
| } |
| |
| // bind to gnss geofence proxy |
| if (mGnssManagerService != null) { |
| GeofenceProxy provider = GeofenceProxy.createAndBind(mContext, |
| mGnssManagerService.getGnssGeofenceProxy()); |
| if (provider == null) { |
| Log.e(TAG, "unable to bind to GeofenceProxy"); |
| } |
| } |
| |
| // create any predefined test providers |
| String[] testProviderStrings = mContext.getResources().getStringArray( |
| com.android.internal.R.array.config_testLocationProviders); |
| for (String testProviderString : testProviderStrings) { |
| String[] fragments = testProviderString.split(","); |
| String name = fragments[0].trim(); |
| ProviderProperties properties = new ProviderProperties.Builder() |
| .setHasNetworkRequirement(Boolean.parseBoolean(fragments[1])) |
| .setHasSatelliteRequirement(Boolean.parseBoolean(fragments[2])) |
| .setHasCellRequirement(Boolean.parseBoolean(fragments[3])) |
| .setHasMonetaryCost(Boolean.parseBoolean(fragments[4])) |
| .setHasAltitudeSupport(Boolean.parseBoolean(fragments[5])) |
| .setHasSpeedSupport(Boolean.parseBoolean(fragments[6])) |
| .setHasBearingSupport(Boolean.parseBoolean(fragments[7])) |
| .setPowerUsage(Integer.parseInt(fragments[8])) |
| .setAccuracy(Integer.parseInt(fragments[9])) |
| .build(); |
| final LocationProviderManager manager = getOrAddLocationProviderManager(name); |
| manager.setMockProvider(new MockLocationProvider(properties, |
| CallerIdentity.fromContext(mContext), Collections.emptySet())); |
| } |
| } |
| |
| private void onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings, |
| LocationUserSettings newSettings) { |
| if (oldSettings.isAdasGnssLocationEnabled() != newSettings.isAdasGnssLocationEnabled()) { |
| boolean enabled = newSettings.isAdasGnssLocationEnabled(); |
| |
| if (D) { |
| Log.d(TAG, "[u" + userId + "] adas gnss location enabled = " + enabled); |
| } |
| |
| EVENT_LOG.logAdasLocationEnabled(userId, enabled); |
| |
| Intent intent = new Intent(LocationManager.ACTION_ADAS_GNSS_ENABLED_CHANGED) |
| .putExtra(LocationManager.EXTRA_ADAS_GNSS_ENABLED, enabled) |
| .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); |
| } |
| } |
| |
| private void onLocationModeChanged(int userId) { |
| boolean enabled = mInjector.getSettingsHelper().isLocationEnabled(userId); |
| LocationManager.invalidateLocalLocationEnabledCaches(); |
| |
| if (D) { |
| Log.d(TAG, "[u" + userId + "] location enabled = " + enabled); |
| } |
| |
| EVENT_LOG.logLocationEnabled(userId, enabled); |
| |
| Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION) |
| .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled) |
| .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); |
| |
| refreshAppOpsRestrictions(userId); |
| } |
| |
| @Override |
| public int getGnssYearOfHardware() { |
| return mGnssManagerService == null ? 0 : mGnssManagerService.getGnssYearOfHardware(); |
| } |
| |
| @Override |
| @Nullable |
| public String getGnssHardwareModelName() { |
| return mGnssManagerService == null ? "" : mGnssManagerService.getGnssHardwareModelName(); |
| } |
| |
| @Override |
| public int getGnssBatchSize() { |
| return mGnssManagerService == null ? 0 : mGnssManagerService.getGnssBatchSize(); |
| } |
| |
| @Override |
| public void startGnssBatch(long periodNanos, ILocationListener listener, String packageName, |
| @Nullable String attributionTag, String listenerId) { |
| mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); |
| |
| if (mGnssManagerService == null) { |
| return; |
| } |
| |
| long intervalMs = NANOSECONDS.toMillis(periodNanos); |
| |
| synchronized (mDeprecatedGnssBatchingLock) { |
| stopGnssBatch(); |
| |
| registerLocationListener( |
| GPS_PROVIDER, |
| new LocationRequest.Builder(intervalMs) |
| .setMaxUpdateDelayMillis( |
| intervalMs * mGnssManagerService.getGnssBatchSize()) |
| .setHiddenFromAppOps(true) |
| .build(), |
| listener, |
| packageName, |
| attributionTag, |
| listenerId); |
| mDeprecatedGnssBatchingListener = listener; |
| } |
| } |
| |
| @Override |
| public void flushGnssBatch() { |
| mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); |
| |
| if (mGnssManagerService == null) { |
| return; |
| } |
| |
| synchronized (mDeprecatedGnssBatchingLock) { |
| if (mDeprecatedGnssBatchingListener != null) { |
| requestListenerFlush(GPS_PROVIDER, mDeprecatedGnssBatchingListener, 0); |
| } |
| } |
| } |
| |
| @Override |
| public void stopGnssBatch() { |
| mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); |
| |
| if (mGnssManagerService == null) { |
| return; |
| } |
| |
| synchronized (mDeprecatedGnssBatchingLock) { |
| if (mDeprecatedGnssBatchingListener != null) { |
| ILocationListener listener = mDeprecatedGnssBatchingListener; |
| mDeprecatedGnssBatchingListener = null; |
| unregisterLocationListener(listener); |
| } |
| } |
| } |
| |
| @Override |
| public boolean hasProvider(String provider) { |
| return getLocationProviderManager(provider) != null; |
| } |
| |
| @Override |
| public List<String> getAllProviders() { |
| ArrayList<String> providers = new ArrayList<>(mProviderManagers.size()); |
| for (LocationProviderManager manager : mProviderManagers) { |
| providers.add(manager.getName()); |
| } |
| return providers; |
| } |
| |
| @Override |
| public List<String> getProviders(Criteria criteria, boolean enabledOnly) { |
| if (!LocationPermissions.checkCallingOrSelfLocationPermission(mContext, |
| PERMISSION_COARSE)) { |
| return Collections.emptyList(); |
| } |
| |
| 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; |
| } |
| providers.add(name); |
| } |
| return providers; |
| } |
| } |
| |
| @Override |
| public String getBestProvider(Criteria criteria, boolean enabledOnly) { |
| List<String> providers; |
| synchronized (mLock) { |
| providers = getProviders(criteria, enabledOnly); |
| if (providers.isEmpty()) { |
| providers = getProviders(null, enabledOnly); |
| } |
| } |
| |
| if (!providers.isEmpty()) { |
| if (providers.contains(FUSED_PROVIDER)) { |
| return FUSED_PROVIDER; |
| } else if (providers.contains(GPS_PROVIDER)) { |
| return GPS_PROVIDER; |
| } else if (providers.contains(NETWORK_PROVIDER)) { |
| return NETWORK_PROVIDER; |
| } else { |
| return providers.get(0); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public String[] getBackgroundThrottlingWhitelist() { |
| return mInjector.getSettingsHelper().getBackgroundThrottlePackageWhitelist().toArray( |
| new String[0]); |
| } |
| |
| @Override |
| public PackageTagsList getIgnoreSettingsAllowlist() { |
| return mInjector.getSettingsHelper().getIgnoreSettingsAllowlist(); |
| } |
| |
| @Nullable |
| @Override |
| public ICancellationSignal getCurrentLocation(String provider, LocationRequest request, |
| ILocationCallback consumer, String packageName, @Nullable String attributionTag, |
| String listenerId) { |
| CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag, |
| listenerId); |
| int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(), |
| identity.getPid()); |
| 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); |
| |
| request = validateLocationRequest(provider, request, identity); |
| |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| Preconditions.checkArgument(manager != null, |
| "provider \"" + provider + "\" does not exist"); |
| |
| return manager.getCurrentLocation(request, identity, permissionLevel, consumer); |
| } |
| |
| @Override |
| public void registerLocationListener(String provider, LocationRequest request, |
| ILocationListener listener, String packageName, @Nullable String attributionTag, |
| String listenerId) { |
| CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag, |
| listenerId); |
| int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(), |
| identity.getPid()); |
| 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) { |
| Log.w(TAG, "system location request with no attribution tag", |
| new IllegalArgumentException()); |
| } |
| |
| request = validateLocationRequest(provider, request, identity); |
| |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| Preconditions.checkArgument(manager != null, |
| "provider \"" + provider + "\" does not exist"); |
| |
| manager.registerLocationRequest(request, identity, permissionLevel, listener); |
| } |
| |
| @Override |
| public void registerLocationPendingIntent(String provider, LocationRequest request, |
| PendingIntent pendingIntent, String packageName, @Nullable String attributionTag) { |
| CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag, |
| AppOpsManager.toReceiverId(pendingIntent)); |
| int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(), |
| identity.getPid()); |
| 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); |
| |
| // pending intents requests may not use system apis because we do not keep track if clients |
| // lose the relevant permissions, and thus should not get the benefit of those apis. its |
| // simplest to ensure these apis are simply never set for pending intent requests. the same |
| // does not apply for listener requests since those will have the process (including the |
| // listener) killed on permission removal |
| if (isChangeEnabled(BLOCK_PENDING_INTENT_SYSTEM_API_USAGE, identity.getUid())) { |
| boolean usesSystemApi = request.isLowPower() |
| || request.isHiddenFromAppOps() |
| || request.isLocationSettingsIgnored() |
| || !request.getWorkSource().isEmpty(); |
| if (usesSystemApi) { |
| throw new SecurityException( |
| "PendingIntent location requests may not use system APIs: " + request); |
| } |
| } |
| |
| request = validateLocationRequest(provider, request, identity); |
| |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| Preconditions.checkArgument(manager != null, |
| "provider \"" + provider + "\" does not exist"); |
| |
| manager.registerLocationRequest(request, identity, permissionLevel, pendingIntent); |
| } |
| |
| private LocationRequest validateLocationRequest(String provider, LocationRequest request, |
| CallerIdentity identity) { |
| // validate unsanitized request |
| if (!request.getWorkSource().isEmpty()) { |
| mContext.enforceCallingOrSelfPermission( |
| permission.UPDATE_DEVICE_STATS, |
| "setting a work source requires " + permission.UPDATE_DEVICE_STATS); |
| } |
| |
| // sanitize request |
| LocationRequest.Builder sanitized = new LocationRequest.Builder(request); |
| |
| if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) |
| && GPS_PROVIDER.equals(provider) |
| && ArrayUtils.contains(mContext.getResources().getStringArray( |
| com.android.internal.R.array.config_locationDriverAssistancePackageNames), |
| identity.getPackageName())) { |
| sanitized.setAdasGnssBypass(true); |
| } |
| |
| if (!CompatChanges.isChangeEnabled(LOW_POWER_EXCEPTIONS, Binder.getCallingUid())) { |
| if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE) |
| != PERMISSION_GRANTED) { |
| sanitized.setLowPower(false); |
| } |
| } |
| |
| WorkSource workSource = new WorkSource(request.getWorkSource()); |
| if (workSource.size() > 0 && workSource.getPackageName(0) == null) { |
| Log.w(TAG, "received (and ignoring) illegal worksource with no package name"); |
| workSource.clear(); |
| } else { |
| List<WorkChain> workChains = workSource.getWorkChains(); |
| if (workChains != null && !workChains.isEmpty() |
| && workChains.get(0).getAttributionTag() == null) { |
| Log.w(TAG, |
| "received (and ignoring) illegal worksource with no attribution tag"); |
| workSource.clear(); |
| } |
| } |
| |
| if (workSource.isEmpty()) { |
| identity.addToWorkSource(workSource); |
| } |
| sanitized.setWorkSource(workSource); |
| |
| request = sanitized.build(); |
| |
| // validate sanitized request |
| boolean isLocationProvider = mLocalService.isProvider(null, identity); |
| |
| if (request.isLowPower() && CompatChanges.isChangeEnabled(LOW_POWER_EXCEPTIONS, |
| identity.getUid())) { |
| mContext.enforceCallingOrSelfPermission( |
| permission.LOCATION_HARDWARE, |
| "low power request requires " + permission.LOCATION_HARDWARE); |
| } |
| if (request.isHiddenFromAppOps()) { |
| mContext.enforceCallingOrSelfPermission( |
| permission.UPDATE_APP_OPS_STATS, |
| "hiding from app ops requires " + permission.UPDATE_APP_OPS_STATS); |
| } |
| if (request.isAdasGnssBypass()) { |
| if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { |
| throw new IllegalArgumentException( |
| "adas gnss bypass requests are only allowed on automotive devices"); |
| } |
| if (!GPS_PROVIDER.equals(provider)) { |
| throw new IllegalArgumentException( |
| "adas gnss bypass requests are only allowed on the \"gps\" provider"); |
| } |
| if (!ArrayUtils.contains(mContext.getResources().getStringArray( |
| com.android.internal.R.array.config_locationDriverAssistancePackageNames), |
| identity.getPackageName())) { |
| throw new SecurityException( |
| "only verified adas packages may use adas gnss bypass requests"); |
| } |
| if (!isLocationProvider) { |
| mContext.enforceCallingOrSelfPermission( |
| permission.WRITE_SECURE_SETTINGS, |
| "adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS); |
| } |
| } |
| if (request.isLocationSettingsIgnored()) { |
| if (!isLocationProvider) { |
| mContext.enforceCallingOrSelfPermission( |
| permission.WRITE_SECURE_SETTINGS, |
| "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS); |
| } |
| } |
| |
| return request; |
| } |
| |
| @Override |
| public void requestListenerFlush(String provider, ILocationListener listener, int requestCode) { |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| Preconditions.checkArgument(manager != null, |
| "provider \"" + provider + "\" does not exist"); |
| |
| manager.flush(Objects.requireNonNull(listener), requestCode); |
| } |
| |
| @Override |
| public void requestPendingIntentFlush(String provider, PendingIntent pendingIntent, |
| int requestCode) { |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| Preconditions.checkArgument(manager != null, |
| "provider \"" + provider + "\" does not exist"); |
| |
| manager.flush(Objects.requireNonNull(pendingIntent), requestCode); |
| } |
| |
| @Override |
| public void unregisterLocationListener(ILocationListener listener) { |
| for (LocationProviderManager manager : mProviderManagers) { |
| manager.unregisterLocationRequest(listener); |
| } |
| } |
| |
| @Override |
| public void unregisterLocationPendingIntent(PendingIntent pendingIntent) { |
| for (LocationProviderManager manager : mProviderManagers) { |
| manager.unregisterLocationRequest(pendingIntent); |
| } |
| } |
| |
| @Override |
| public Location getLastLocation(String provider, LastLocationRequest request, |
| String packageName, @Nullable String attributionTag) { |
| CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); |
| int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(), |
| identity.getPid()); |
| 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); |
| |
| request = validateLastLocationRequest(provider, request, identity); |
| |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| if (manager == null) { |
| return null; |
| } |
| |
| return manager.getLastLocation(request, identity, permissionLevel); |
| } |
| |
| private LastLocationRequest validateLastLocationRequest(String provider, |
| LastLocationRequest request, |
| CallerIdentity identity) { |
| // sanitize request |
| LastLocationRequest.Builder sanitized = new LastLocationRequest.Builder(request); |
| |
| if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) |
| && GPS_PROVIDER.equals(provider) |
| && ArrayUtils.contains(mContext.getResources().getStringArray( |
| com.android.internal.R.array.config_locationDriverAssistancePackageNames), |
| identity.getPackageName())) { |
| sanitized.setAdasGnssBypass(true); |
| } |
| |
| request = sanitized.build(); |
| |
| // validate request |
| boolean isLocationProvider = mLocalService.isProvider(null, identity); |
| |
| if (request.isHiddenFromAppOps()) { |
| mContext.enforceCallingOrSelfPermission( |
| permission.UPDATE_APP_OPS_STATS, |
| "hiding from app ops requires " + permission.UPDATE_APP_OPS_STATS); |
| } |
| |
| if (request.isAdasGnssBypass()) { |
| if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { |
| throw new IllegalArgumentException( |
| "adas gnss bypass requests are only allowed on automotive devices"); |
| } |
| if (!GPS_PROVIDER.equals(provider)) { |
| throw new IllegalArgumentException( |
| "adas gnss bypass requests are only allowed on the \"gps\" provider"); |
| } |
| if (!ArrayUtils.contains(mContext.getResources().getStringArray( |
| com.android.internal.R.array.config_locationDriverAssistancePackageNames), |
| identity.getPackageName())) { |
| throw new SecurityException( |
| "only verified adas packages may use adas gnss bypass requests"); |
| } |
| if (!isLocationProvider) { |
| mContext.enforceCallingOrSelfPermission( |
| permission.WRITE_SECURE_SETTINGS, |
| "adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS); |
| } |
| } |
| if (request.isLocationSettingsIgnored()) { |
| if (!isLocationProvider) { |
| mContext.enforceCallingOrSelfPermission( |
| permission.WRITE_SECURE_SETTINGS, |
| "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS); |
| } |
| } |
| |
| return request; |
| } |
| |
| @Override |
| public LocationTime getGnssTimeMillis() { |
| return mLocalService.getGnssTimeMillis(); |
| } |
| |
| @Override |
| public void injectLocation(Location location) { |
| mContext.enforceCallingPermission(permission.LOCATION_HARDWARE, null); |
| mContext.enforceCallingPermission(ACCESS_FINE_LOCATION, null); |
| |
| Preconditions.checkArgument(location.isComplete()); |
| |
| int userId = UserHandle.getCallingUserId(); |
| LocationProviderManager manager = getLocationProviderManager(location.getProvider()); |
| if (manager != null && manager.isEnabled(userId)) { |
| manager.injectLastLocation(Objects.requireNonNull(location), userId); |
| } |
| } |
| |
| @Override |
| public void requestGeofence(Geofence geofence, PendingIntent intent, String packageName, |
| String attributionTag) { |
| mGeofenceManager.addGeofence(geofence, intent, packageName, attributionTag); |
| } |
| |
| @Override |
| public void removeGeofence(PendingIntent pendingIntent) { |
| mGeofenceManager.removeGeofence(pendingIntent); |
| } |
| |
| @Override |
| public void registerGnssStatusCallback(IGnssStatusListener listener, String packageName, |
| @Nullable String attributionTag, String listenerId) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.registerGnssStatusCallback(listener, packageName, attributionTag, |
| listenerId); |
| } |
| } |
| |
| @Override |
| public void unregisterGnssStatusCallback(IGnssStatusListener listener) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.unregisterGnssStatusCallback(listener); |
| } |
| } |
| |
| @Override |
| public void registerGnssNmeaCallback(IGnssNmeaListener listener, String packageName, |
| @Nullable String attributionTag, String listenerId) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.registerGnssNmeaCallback(listener, packageName, attributionTag, |
| listenerId); |
| } |
| } |
| |
| @Override |
| public void unregisterGnssNmeaCallback(IGnssNmeaListener listener) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.unregisterGnssNmeaCallback(listener); |
| } |
| } |
| |
| @Override |
| public void addGnssMeasurementsListener(GnssMeasurementRequest request, |
| IGnssMeasurementsListener listener, String packageName, @Nullable String attributionTag, |
| String listenerId) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.addGnssMeasurementsListener(request, listener, packageName, |
| attributionTag, listenerId); |
| } |
| } |
| |
| @Override |
| public void removeGnssMeasurementsListener(IGnssMeasurementsListener listener) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.removeGnssMeasurementsListener( |
| listener); |
| } |
| } |
| |
| @Override |
| public void addGnssAntennaInfoListener(IGnssAntennaInfoListener listener, String packageName, |
| @Nullable String attributionTag, String listenerId) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.addGnssAntennaInfoListener(listener, packageName, attributionTag, |
| listenerId); |
| } |
| } |
| |
| @Override |
| public void removeGnssAntennaInfoListener(IGnssAntennaInfoListener listener) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.removeGnssAntennaInfoListener(listener); |
| } |
| } |
| |
| @Override |
| public void addProviderRequestListener(IProviderRequestListener listener) { |
| for (LocationProviderManager manager : mProviderManagers) { |
| manager.addProviderRequestListener(listener); |
| } |
| } |
| |
| @Override |
| public void removeProviderRequestListener(IProviderRequestListener listener) { |
| for (LocationProviderManager manager : mProviderManagers) { |
| manager.removeProviderRequestListener(listener); |
| } |
| } |
| |
| @Override |
| public void injectGnssMeasurementCorrections(GnssMeasurementCorrections corrections) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.injectGnssMeasurementCorrections(corrections); |
| } |
| } |
| |
| @Override |
| public GnssCapabilities getGnssCapabilities() { |
| return mGnssManagerService == null ? new GnssCapabilities.Builder().build() |
| : mGnssManagerService.getGnssCapabilities(); |
| } |
| |
| @Override |
| public List<GnssAntennaInfo> getGnssAntennaInfos() { |
| return mGnssManagerService == null ? null : mGnssManagerService.getGnssAntennaInfos(); |
| } |
| |
| @Override |
| public void addGnssNavigationMessageListener(IGnssNavigationMessageListener listener, |
| String packageName, @Nullable String attributionTag, String listenerId) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.addGnssNavigationMessageListener(listener, packageName, |
| attributionTag, listenerId); |
| } |
| } |
| |
| @Override |
| public void removeGnssNavigationMessageListener(IGnssNavigationMessageListener listener) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.removeGnssNavigationMessageListener( |
| listener); |
| } |
| } |
| |
| @Override |
| public void sendExtraCommand(String provider, String command, Bundle extras) { |
| LocationPermissions.enforceCallingOrSelfLocationPermission(mContext, PERMISSION_COARSE); |
| mContext.enforceCallingOrSelfPermission( |
| permission.ACCESS_LOCATION_EXTRA_COMMANDS, null); |
| |
| LocationProviderManager manager = getLocationProviderManager( |
| Objects.requireNonNull(provider)); |
| if (manager != null) { |
| manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), |
| Objects.requireNonNull(command), extras); |
| } |
| |
| mInjector.getLocationUsageLogger().logLocationApiUsage( |
| LocationStatsEnums.USAGE_STARTED, |
| LocationStatsEnums.API_SEND_EXTRA_COMMAND, |
| provider); |
| mInjector.getLocationUsageLogger().logLocationApiUsage( |
| LocationStatsEnums.USAGE_ENDED, |
| LocationStatsEnums.API_SEND_EXTRA_COMMAND, |
| provider); |
| } |
| |
| @Override |
| public ProviderProperties getProviderProperties(String provider) { |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| Preconditions.checkArgument(manager != null, |
| "provider \"" + provider + "\" does not exist"); |
| return manager.getProperties(); |
| } |
| |
| @Override |
| public boolean isProviderPackage(@Nullable String provider, String packageName, |
| @Nullable String attributionTag) { |
| mContext.enforceCallingOrSelfPermission(permission.READ_DEVICE_CONFIG, null); |
| |
| for (LocationProviderManager manager : mProviderManagers) { |
| if (provider != null && !provider.equals(manager.getName())) { |
| continue; |
| } |
| CallerIdentity identity = manager.getIdentity(); |
| if (identity == null) { |
| continue; |
| } |
| if (identity.getPackageName().equals(packageName) && (attributionTag == null |
| || Objects.equals(identity.getAttributionTag(), attributionTag))) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public List<String> getProviderPackages(String provider) { |
| mContext.enforceCallingOrSelfPermission(permission.READ_DEVICE_CONFIG, null); |
| |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| if (manager == null) { |
| return Collections.emptyList(); |
| } |
| |
| CallerIdentity identity = manager.getIdentity(); |
| if (identity == null) { |
| return Collections.emptyList(); |
| } |
| |
| return Collections.singletonList(identity.getPackageName()); |
| } |
| |
| @Override |
| public void setExtraLocationControllerPackage(String packageName) { |
| mContext.enforceCallingPermission(permission.LOCATION_HARDWARE, |
| permission.LOCATION_HARDWARE + " permission required"); |
| synchronized (mLock) { |
| mExtraLocationControllerPackage = packageName; |
| } |
| } |
| |
| @Override |
| public String getExtraLocationControllerPackage() { |
| synchronized (mLock) { |
| return mExtraLocationControllerPackage; |
| } |
| } |
| |
| @Override |
| public void setExtraLocationControllerPackageEnabled(boolean enabled) { |
| mContext.enforceCallingPermission(permission.LOCATION_HARDWARE, |
| permission.LOCATION_HARDWARE + " permission required"); |
| synchronized (mLock) { |
| mExtraLocationControllerPackageEnabled = enabled; |
| } |
| } |
| |
| @Override |
| public boolean isExtraLocationControllerPackageEnabled() { |
| synchronized (mLock) { |
| return mExtraLocationControllerPackageEnabled |
| && (mExtraLocationControllerPackage != null); |
| } |
| } |
| |
| @Override |
| public void setLocationEnabledForUser(boolean enabled, int userId) { |
| userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), |
| userId, false, false, "setLocationEnabledForUser", null); |
| |
| mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null); |
| |
| LocationManager.invalidateLocalLocationEnabledCaches(); |
| mInjector.getSettingsHelper().setLocationEnabled(enabled, userId); |
| } |
| |
| @Override |
| public boolean isLocationEnabledForUser(int userId) { |
| userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), |
| userId, false, false, "isLocationEnabledForUser", null); |
| return mInjector.getSettingsHelper().isLocationEnabled(userId); |
| } |
| |
| @Override |
| public void setAdasGnssLocationEnabledForUser(boolean enabled, int userId) { |
| userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), |
| userId, false, false, "setAdasGnssLocationEnabledForUser", null); |
| |
| mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null); |
| |
| mInjector.getLocationSettings().updateUserSettings(userId, |
| settings -> settings.withAdasGnssLocationEnabled(enabled)); |
| } |
| |
| @Override |
| public boolean isAdasGnssLocationEnabledForUser(int userId) { |
| userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), |
| userId, false, false, "isAdasGnssLocationEnabledForUser", null); |
| return mInjector.getLocationSettings().getUserSettings(userId).isAdasGnssLocationEnabled(); |
| } |
| |
| @Override |
| public boolean isProviderEnabledForUser(String provider, int userId) { |
| return mLocalService.isProviderEnabledForUser(provider, userId); |
| } |
| |
| @Override |
| public boolean geocoderIsPresent() { |
| return mGeocodeProvider != null; |
| } |
| |
| @Override |
| public void getFromLocation(double latitude, double longitude, int maxResults, |
| GeocoderParams params, IGeocodeListener listener) { |
| // validate identity |
| CallerIdentity identity = CallerIdentity.fromBinder(mContext, params.getClientPackage(), |
| params.getClientAttributionTag()); |
| Preconditions.checkArgument(identity.getUid() == params.getClientUid()); |
| |
| if (mGeocodeProvider != null) { |
| mGeocodeProvider.getFromLocation(latitude, longitude, maxResults, params, listener); |
| } else { |
| try { |
| listener.onResults(null, Collections.emptyList()); |
| } catch (RemoteException e) { |
| // ignore |
| } |
| } |
| } |
| |
| @Override |
| public void getFromLocationName(String locationName, |
| double lowerLeftLatitude, double lowerLeftLongitude, |
| double upperRightLatitude, double upperRightLongitude, int maxResults, |
| GeocoderParams params, IGeocodeListener listener) { |
| // validate identity |
| CallerIdentity identity = CallerIdentity.fromBinder(mContext, params.getClientPackage(), |
| params.getClientAttributionTag()); |
| Preconditions.checkArgument(identity.getUid() == params.getClientUid()); |
| |
| if (mGeocodeProvider != null) { |
| mGeocodeProvider.getFromLocationName(locationName, lowerLeftLatitude, |
| lowerLeftLongitude, upperRightLatitude, upperRightLongitude, |
| maxResults, params, listener); |
| } else { |
| try { |
| listener.onResults(null, Collections.emptyList()); |
| } catch (RemoteException e) { |
| // ignore |
| } |
| } |
| } |
| |
| @Override |
| public void addTestProvider(String provider, ProviderProperties properties, |
| List<String> extraAttributionTags, String packageName, String attributionTag) { |
| // unsafe is ok because app ops will verify the package name |
| CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); |
| if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) { |
| return; |
| } |
| |
| final LocationProviderManager manager = getOrAddLocationProviderManager(provider); |
| manager.setMockProvider(new MockLocationProvider(properties, identity, |
| new ArraySet<>(extraAttributionTags))); |
| } |
| |
| @Override |
| public void removeTestProvider(String provider, String packageName, String attributionTag) { |
| // unsafe is ok because app ops will verify the package name |
| CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); |
| if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) { |
| return; |
| } |
| |
| synchronized (mLock) { |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| if (manager == null) { |
| return; |
| } |
| |
| manager.setMockProvider(null); |
| if (!manager.hasProvider()) { |
| removeLocationProviderManager(manager); |
| } |
| } |
| } |
| |
| @Override |
| public void setTestProviderLocation(String provider, Location location, String packageName, |
| String attributionTag) { |
| // unsafe is ok because app ops will verify the package name |
| CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, |
| attributionTag); |
| if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) { |
| return; |
| } |
| |
| Preconditions.checkArgument(location.isComplete(), |
| "incomplete location object, missing timestamp or accuracy?"); |
| |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| if (manager == null) { |
| throw new IllegalArgumentException("provider doesn't exist: " + provider); |
| } |
| |
| manager.setMockProviderLocation(location); |
| } |
| |
| @Override |
| public void setTestProviderEnabled(String provider, boolean enabled, String packageName, |
| String attributionTag) { |
| // unsafe is ok because app ops will verify the package name |
| CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, |
| attributionTag); |
| if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) { |
| return; |
| } |
| |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| if (manager == null) { |
| throw new IllegalArgumentException("provider doesn't exist: " + provider); |
| } |
| |
| manager.setMockProviderAllowed(enabled); |
| } |
| |
| @Override |
| public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, |
| ParcelFileDescriptor err, String[] args) { |
| return new LocationShellCommand(mContext, this).exec( |
| this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), |
| args); |
| } |
| |
| @Override |
| protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { |
| return; |
| } |
| |
| IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); |
| |
| if (args.length > 0) { |
| LocationProviderManager manager = getLocationProviderManager(args[0]); |
| if (manager != null) { |
| ipw.println("Provider:"); |
| ipw.increaseIndent(); |
| manager.dump(fd, ipw, args); |
| ipw.decreaseIndent(); |
| |
| ipw.println("Event Log:"); |
| ipw.increaseIndent(); |
| EVENT_LOG.iterate(manager.getName(), ipw::println); |
| ipw.decreaseIndent(); |
| return; |
| } |
| |
| if ("--gnssmetrics".equals(args[0])) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.dump(fd, ipw, args); |
| } |
| return; |
| } |
| } |
| |
| ipw.println("Location Manager State:"); |
| ipw.increaseIndent(); |
| |
| ipw.println("User Info:"); |
| ipw.increaseIndent(); |
| mInjector.getUserInfoHelper().dump(fd, ipw, args); |
| ipw.decreaseIndent(); |
| |
| ipw.println("Location Settings:"); |
| ipw.increaseIndent(); |
| mInjector.getSettingsHelper().dump(fd, ipw, args); |
| ipw.decreaseIndent(); |
| |
| synchronized (mLock) { |
| if (mExtraLocationControllerPackage != null) { |
| ipw.println( |
| "Location Controller Extra Package: " + mExtraLocationControllerPackage |
| + (mExtraLocationControllerPackageEnabled ? " [enabled]" |
| : " [disabled]")); |
| } |
| } |
| |
| ipw.println("Location Providers:"); |
| ipw.increaseIndent(); |
| for (LocationProviderManager manager : mProviderManagers) { |
| manager.dump(fd, ipw, args); |
| } |
| ipw.decreaseIndent(); |
| |
| ipw.println("Historical Aggregate Location Provider Data:"); |
| ipw.increaseIndent(); |
| ArrayMap<String, ArrayMap<CallerIdentity, LocationEventLog.AggregateStats>> aggregateStats = |
| EVENT_LOG.copyAggregateStats(); |
| for (int i = 0; i < aggregateStats.size(); i++) { |
| ipw.print(aggregateStats.keyAt(i)); |
| ipw.println(":"); |
| ipw.increaseIndent(); |
| ArrayMap<CallerIdentity, LocationEventLog.AggregateStats> providerStats = |
| aggregateStats.valueAt(i); |
| for (int j = 0; j < providerStats.size(); j++) { |
| ipw.print(providerStats.keyAt(j)); |
| ipw.print(": "); |
| providerStats.valueAt(j).updateTotals(); |
| ipw.println(providerStats.valueAt(j)); |
| } |
| ipw.decreaseIndent(); |
| } |
| ipw.decreaseIndent(); |
| |
| if (mGnssManagerService != null) { |
| ipw.println("GNSS Manager:"); |
| ipw.increaseIndent(); |
| mGnssManagerService.dump(fd, ipw, args); |
| ipw.decreaseIndent(); |
| } |
| |
| ipw.println("Geofence Manager:"); |
| ipw.increaseIndent(); |
| mGeofenceManager.dump(fd, ipw, args); |
| ipw.decreaseIndent(); |
| |
| ipw.println("Event Log:"); |
| ipw.increaseIndent(); |
| EVENT_LOG.iterate(ipw::println); |
| ipw.decreaseIndent(); |
| } |
| |
| @Override |
| public void onStateChanged(String provider, AbstractLocationProvider.State oldState, |
| AbstractLocationProvider.State newState) { |
| if (!Objects.equals(oldState.identity, newState.identity)) { |
| refreshAppOpsRestrictions(UserHandle.USER_ALL); |
| } |
| |
| if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags) |
| || !Objects.equals(oldState.identity, newState.identity)) { |
| // since we're potentially affecting the tag lists for two different uids, acquire the |
| // lock to ensure providers cannot change while we're looping over the providers |
| // multiple times, which could lead to inconsistent results. |
| synchronized (mLock) { |
| LocationPackageTagsListener listener = mLocationTagsChangedListener; |
| if (listener != null) { |
| int oldUid = oldState.identity != null ? oldState.identity.getUid() : -1; |
| int newUid = newState.identity != null ? newState.identity.getUid() : -1; |
| if (oldUid != -1) { |
| PackageTagsList tags = calculateAppOpsLocationSourceTags(oldUid); |
| FgThread.getHandler().post( |
| () -> listener.onLocationPackageTagsChanged(oldUid, tags)); |
| } |
| // if the new app id is the same as the old app id, no need to invoke the |
| // listener twice, it's already been taken care of |
| if (newUid != -1 && newUid != oldUid) { |
| PackageTagsList tags = calculateAppOpsLocationSourceTags(newUid); |
| FgThread.getHandler().post( |
| () -> listener.onLocationPackageTagsChanged(newUid, tags)); |
| } |
| } |
| } |
| } |
| } |
| |
| private void refreshAppOpsRestrictions(int userId) { |
| if (userId == UserHandle.USER_ALL) { |
| final int[] runningUserIds = mInjector.getUserInfoHelper().getRunningUserIds(); |
| for (int i = 0; i < runningUserIds.length; i++) { |
| refreshAppOpsRestrictions(runningUserIds[i]); |
| } |
| return; |
| } |
| |
| Preconditions.checkArgument(userId >= 0); |
| |
| boolean enabled = mInjector.getSettingsHelper().isLocationEnabled(userId); |
| |
| PackageTagsList allowedPackages = null; |
| if (!enabled) { |
| PackageTagsList.Builder builder = new PackageTagsList.Builder(); |
| for (LocationProviderManager manager : mProviderManagers) { |
| CallerIdentity identity = manager.getIdentity(); |
| if (identity != null) { |
| builder.add(identity.getPackageName(), identity.getAttributionTag()); |
| } |
| } |
| builder.add(mInjector.getSettingsHelper().getIgnoreSettingsAllowlist()); |
| allowedPackages = builder.build(); |
| } |
| |
| AppOpsManager appOpsManager = Objects.requireNonNull( |
| mContext.getSystemService(AppOpsManager.class)); |
| appOpsManager.setUserRestrictionForUser( |
| AppOpsManager.OP_COARSE_LOCATION, |
| !enabled, |
| LocationManagerService.this, |
| allowedPackages, |
| userId); |
| appOpsManager.setUserRestrictionForUser( |
| AppOpsManager.OP_FINE_LOCATION, |
| !enabled, |
| LocationManagerService.this, |
| allowedPackages, |
| userId); |
| } |
| |
| PackageTagsList calculateAppOpsLocationSourceTags(int uid) { |
| PackageTagsList.Builder builder = new PackageTagsList.Builder(); |
| for (LocationProviderManager manager : mProviderManagers) { |
| AbstractLocationProvider.State managerState = manager.getState(); |
| if (managerState.identity == null) { |
| continue; |
| } |
| if (managerState.identity.getUid() != uid) { |
| continue; |
| } |
| |
| builder.add(managerState.identity.getPackageName(), managerState.extraAttributionTags); |
| if (managerState.extraAttributionTags.isEmpty() |
| || managerState.identity.getAttributionTag() != null) { |
| builder.add(managerState.identity.getPackageName(), |
| managerState.identity.getAttributionTag()); |
| } else { |
| Log.e(TAG, manager.getName() + " provider has specified a null attribution tag and " |
| + "a non-empty set of extra attribution tags - dropping the null " |
| + "attribution tag"); |
| } |
| } |
| return builder.build(); |
| } |
| |
| private class LocalService extends LocationManagerInternal { |
| |
| LocalService() {} |
| |
| @Override |
| public boolean isProviderEnabledForUser(@NonNull String provider, int userId) { |
| userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), |
| Binder.getCallingUid(), userId, false, false, "isProviderEnabledForUser", null); |
| |
| LocationProviderManager manager = getLocationProviderManager(provider); |
| if (manager == null) { |
| return false; |
| } |
| |
| return manager.isEnabled(userId); |
| } |
| |
| @Override |
| public void addProviderEnabledListener(String provider, ProviderEnabledListener listener) { |
| LocationProviderManager manager = Objects.requireNonNull( |
| getLocationProviderManager(provider)); |
| manager.addEnabledListener(listener); |
| } |
| |
| @Override |
| public void removeProviderEnabledListener(String provider, |
| ProviderEnabledListener listener) { |
| LocationProviderManager manager = Objects.requireNonNull( |
| getLocationProviderManager(provider)); |
| manager.removeEnabledListener(listener); |
| } |
| |
| @Override |
| public boolean isProvider(@Nullable String provider, CallerIdentity identity) { |
| for (LocationProviderManager manager : mProviderManagers) { |
| if (provider != null && !provider.equals(manager.getName())) { |
| continue; |
| } |
| if (identity.equals(manager.getIdentity())) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public void sendNiResponse(int notifId, int userResponse) { |
| if (mGnssManagerService != null) { |
| mGnssManagerService.sendNiResponse(notifId, userResponse); |
| } |
| } |
| |
| @Override |
| public @Nullable LocationTime getGnssTimeMillis() { |
| LocationProviderManager gpsManager = getLocationProviderManager(GPS_PROVIDER); |
| if (gpsManager == null) { |
| return null; |
| } |
| |
| Location location = gpsManager.getLastLocationUnsafe(UserHandle.USER_ALL, |
| PERMISSION_FINE, false, Long.MAX_VALUE); |
| if (location == null) { |
| return null; |
| } |
| |
| return new LocationTime(location.getTime(), location.getElapsedRealtimeNanos()); |
| } |
| |
| @Override |
| public void setLocationPackageTagsListener( |
| @Nullable LocationPackageTagsListener listener) { |
| synchronized (mLock) { |
| mLocationTagsChangedListener = listener; |
| |
| // calculate initial tag list and send to listener |
| if (listener != null) { |
| ArraySet<Integer> uids = new ArraySet<>(mProviderManagers.size()); |
| for (LocationProviderManager manager : mProviderManagers) { |
| CallerIdentity identity = manager.getIdentity(); |
| if (identity != null) { |
| uids.add(identity.getUid()); |
| } |
| } |
| |
| for (int uid : uids) { |
| PackageTagsList tags = calculateAppOpsLocationSourceTags(uid); |
| if (!tags.isEmpty()) { |
| FgThread.getHandler().post( |
| () -> listener.onLocationPackageTagsChanged(uid, tags)); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static final class SystemInjector implements Injector { |
| |
| private final Context mContext; |
| |
| private final UserInfoHelper mUserInfoHelper; |
| private final LocationSettings mLocationSettings; |
| private final AlarmHelper mAlarmHelper; |
| private final SystemAppOpsHelper mAppOpsHelper; |
| private final SystemLocationPermissionsHelper mLocationPermissionsHelper; |
| private final SystemSettingsHelper mSettingsHelper; |
| private final SystemAppForegroundHelper mAppForegroundHelper; |
| private final SystemLocationPowerSaveModeHelper mLocationPowerSaveModeHelper; |
| private final SystemScreenInteractiveHelper mScreenInteractiveHelper; |
| private final SystemDeviceStationaryHelper mDeviceStationaryHelper; |
| private final SystemDeviceIdleHelper mDeviceIdleHelper; |
| private final LocationAttributionHelper mLocationAttributionHelper; |
| private final LocationUsageLogger mLocationUsageLogger; |
| |
| // lazily instantiated since they may not always be used |
| |
| @GuardedBy("this") |
| private @Nullable SystemEmergencyHelper mEmergencyCallHelper; |
| |
| @GuardedBy("this") |
| private boolean mSystemReady; |
| |
| SystemInjector(Context context, UserInfoHelper userInfoHelper) { |
| mContext = context; |
| |
| mUserInfoHelper = userInfoHelper; |
| mLocationSettings = new LocationSettings(context); |
| mAlarmHelper = new SystemAlarmHelper(context); |
| mAppOpsHelper = new SystemAppOpsHelper(context); |
| mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context, |
| mAppOpsHelper); |
| mSettingsHelper = new SystemSettingsHelper(context); |
| mAppForegroundHelper = new SystemAppForegroundHelper(context); |
| mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context); |
| mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context); |
| mDeviceStationaryHelper = new SystemDeviceStationaryHelper(); |
| mDeviceIdleHelper = new SystemDeviceIdleHelper(context); |
| mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper); |
| mLocationUsageLogger = new LocationUsageLogger(); |
| } |
| |
| synchronized void onSystemReady() { |
| mAppOpsHelper.onSystemReady(); |
| mLocationPermissionsHelper.onSystemReady(); |
| mSettingsHelper.onSystemReady(); |
| mAppForegroundHelper.onSystemReady(); |
| mLocationPowerSaveModeHelper.onSystemReady(); |
| mScreenInteractiveHelper.onSystemReady(); |
| mDeviceStationaryHelper.onSystemReady(); |
| mDeviceIdleHelper.onSystemReady(); |
| |
| if (mEmergencyCallHelper != null) { |
| mEmergencyCallHelper.onSystemReady(); |
| } |
| |
| mSystemReady = true; |
| } |
| |
| @Override |
| public UserInfoHelper getUserInfoHelper() { |
| return mUserInfoHelper; |
| } |
| |
| @Override |
| public LocationSettings getLocationSettings() { |
| return mLocationSettings; |
| } |
| |
| @Override |
| public AlarmHelper getAlarmHelper() { |
| return mAlarmHelper; |
| } |
| |
| @Override |
| public AppOpsHelper getAppOpsHelper() { |
| return mAppOpsHelper; |
| } |
| |
| @Override |
| public LocationPermissionsHelper getLocationPermissionsHelper() { |
| return mLocationPermissionsHelper; |
| } |
| |
| @Override |
| public SettingsHelper getSettingsHelper() { |
| return mSettingsHelper; |
| } |
| |
| @Override |
| public AppForegroundHelper getAppForegroundHelper() { |
| return mAppForegroundHelper; |
| } |
| |
| @Override |
| public LocationPowerSaveModeHelper getLocationPowerSaveModeHelper() { |
| return mLocationPowerSaveModeHelper; |
| } |
| |
| @Override |
| public ScreenInteractiveHelper getScreenInteractiveHelper() { |
| return mScreenInteractiveHelper; |
| } |
| |
| @Override |
| public DeviceStationaryHelper getDeviceStationaryHelper() { |
| return mDeviceStationaryHelper; |
| } |
| |
| @Override |
| public DeviceIdleHelper getDeviceIdleHelper() { |
| return mDeviceIdleHelper; |
| } |
| |
| @Override |
| public LocationAttributionHelper getLocationAttributionHelper() { |
| return mLocationAttributionHelper; |
| } |
| |
| @Override |
| public synchronized EmergencyHelper getEmergencyHelper() { |
| if (mEmergencyCallHelper == null) { |
| mEmergencyCallHelper = new SystemEmergencyHelper(mContext); |
| if (mSystemReady) { |
| mEmergencyCallHelper.onSystemReady(); |
| } |
| } |
| |
| return mEmergencyCallHelper; |
| } |
| |
| @Override |
| public LocationUsageLogger getLocationUsageLogger() { |
| return mLocationUsageLogger; |
| } |
| } |
| } |