blob: dc393d1609dec2ad27cf7ee67be8851c2b633261 [file] [log] [blame]
/*
* Copyright (C) 2007 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;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
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.LocationManager.PASSIVE_PROVIDER;
import static android.os.PowerManager.locationPowerSaveModeToString;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.content.res.Resources;
import android.hardware.location.ActivityRecognitionHardware;
import android.location.Address;
import android.location.Criteria;
import android.location.GeocoderParams;
import android.location.Geofence;
import android.location.GnssMeasurementCorrections;
import android.location.IBatchedLocationCallback;
import android.location.IGnssMeasurementsListener;
import android.location.IGnssNavigationMessageListener;
import android.location.IGnssStatusListener;
import android.location.ILocationListener;
import android.location.ILocationManager;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationRequest;
import android.location.LocationTime;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
import android.stats.location.LocationStatsEnums;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.location.AbstractLocationProvider;
import com.android.server.location.AbstractLocationProvider.State;
import com.android.server.location.ActivityRecognitionProxy;
import com.android.server.location.CallerIdentity;
import com.android.server.location.GeocoderProxy;
import com.android.server.location.GeofenceManager;
import com.android.server.location.GeofenceProxy;
import com.android.server.location.LocationFudger;
import com.android.server.location.LocationProviderProxy;
import com.android.server.location.LocationRequestStatistics;
import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
import com.android.server.location.LocationRequestStatistics.PackageStatistics;
import com.android.server.location.LocationSettingsStore;
import com.android.server.location.LocationUsageLogger;
import com.android.server.location.MockProvider;
import com.android.server.location.MockableLocationProvider;
import com.android.server.location.PassiveProvider;
import com.android.server.location.UserInfoStore;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
/**
* The service class that manages LocationProviders and issues location
* updates and alerts.
*/
public class LocationManagerService extends ILocationManager.Stub {
/**
* Controls lifecycle of LocationManagerService.
*/
public static class Lifecycle extends SystemService {
private LocationManagerService mService;
public Lifecycle(Context context) {
super(context);
mService = new LocationManagerService(context);
}
@Override
public void onStart() {
publishBinderService(Context.LOCATION_SERVICE, mService);
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
// the location service must be functioning after this boot phase
mService.onSystemReady();
} else if (phase == SystemService.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();
}
}
}
public static final String TAG = "LocationManagerService";
public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
private static final String WAKELOCK_KEY = "*location*";
private static final int RESOLUTION_LEVEL_NONE = 0;
private static final int RESOLUTION_LEVEL_COARSE = 1;
private static final int RESOLUTION_LEVEL_FINE = 2;
private static final String NETWORK_LOCATION_SERVICE_ACTION =
"com.android.location.service.v3.NetworkLocationProvider";
private static final String FUSED_LOCATION_SERVICE_ACTION =
"com.android.location.service.FusedLocationProvider";
// The maximum interval a location request can have and still be considered "high power".
private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
// maximum age of a location before it is no longer considered "current"
private static final long MAX_CURRENT_LOCATION_AGE_MS = 10 * 1000;
private static final int FOREGROUND_IMPORTANCE_CUTOFF
= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
// Location Providers may sometimes deliver location updates
// slightly faster that requested - provide grace period so
// we don't unnecessarily filter events that are otherwise on
// time
private static final int MAX_PROVIDER_SCHEDULING_JITTER_MS = 100;
private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest();
private final Object mLock = new Object();
private final Context mContext;
private final Handler mHandler;
private final UserInfoStore mUserInfoStore;
private final LocationSettingsStore mSettingsStore;
private final LocationUsageLogger mLocationUsageLogger;
private final PassiveLocationProviderManager mPassiveManager;
private AppOpsManager mAppOps;
private PackageManager mPackageManager;
private PowerManager mPowerManager;
private ActivityManager mActivityManager;
private GeofenceManager mGeofenceManager;
private LocationFudger mLocationFudger;
private GeocoderProxy mGeocodeProvider;
@Nullable private GnssManagerService mGnssManagerService;
@GuardedBy("mLock")
private String mExtraLocationControllerPackage;
@GuardedBy("mLock")
private boolean mExtraLocationControllerPackageEnabled;
// @GuardedBy("mLock")
// hold lock for write or to prevent write, no lock for read
private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
new CopyOnWriteArrayList<>();
@GuardedBy("mLock")
private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
new HashMap<>();
private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
// mapping from provider name to last known location
@GuardedBy("mLock")
private final HashMap<String, Location> mLastLocation = new HashMap<>();
// same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS.
// locations stored here are not fudged for coarse permissions.
@GuardedBy("mLock")
private final HashMap<String, Location> mLastLocationCoarseInterval =
new HashMap<>();
@GuardedBy("mLock")
@PowerManager.LocationPowerSaveMode
private int mBatterySaverMode;
private LocationManagerService(Context context) {
mContext = context;
mHandler = FgThread.getHandler();
mUserInfoStore = new UserInfoStore(mContext);
mSettingsStore = new LocationSettingsStore(mContext, mHandler);
mLocationUsageLogger = new LocationUsageLogger();
// set up passive provider - we do this early because it has no dependencies on system
// services or external code that isn't ready yet, and because this allows the variable to
// be final. other more complex providers are initialized later, when system services are
// ready
mPassiveManager = new PassiveLocationProviderManager();
mProviderManagers.add(mPassiveManager);
mPassiveManager.setRealProvider(new PassiveProvider(mContext));
// Let the package manager query which are the default location
// providers as they get certain permissions granted by default.
PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
PermissionManagerServiceInternal.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));
// most startup is deferred until systemReady()
}
private void onSystemReady() {
mUserInfoStore.onSystemReady();
mSettingsStore.onSystemReady();
synchronized (mLock) {
mPackageManager = mContext.getPackageManager();
mAppOps = mContext.getSystemService(AppOpsManager.class);
mPowerManager = mContext.getSystemService(PowerManager.class);
mActivityManager = mContext.getSystemService(ActivityManager.class);
mLocationFudger = new LocationFudger(mContext, mHandler);
mGeofenceManager = new GeofenceManager(mContext, mSettingsStore);
PowerManagerInternal localPowerManager =
LocalServices.getService(PowerManagerInternal.class);
// add listeners
mAppOps.startWatchingMode(
AppOpsManager.OP_COARSE_LOCATION,
null,
AppOpsManager.WATCH_FOREGROUND_CHANGES,
new AppOpsManager.OnOpChangedInternalListener() {
public void onOpChanged(int op, String packageName) {
// onOpChanged invoked on ui thread, move to our thread to reduce risk
// of blocking ui thread
mHandler.post(() -> {
synchronized (mLock) {
onAppOpChangedLocked();
}
});
}
});
mPackageManager.addOnPermissionsChangeListener(
uid -> {
// listener invoked on ui thread, move to our thread to reduce risk of
// blocking ui thread
mHandler.post(() -> {
synchronized (mLock) {
onPermissionsChangedLocked();
}
});
});
mActivityManager.addOnUidImportanceListener(
(uid, importance) -> {
// listener invoked on ui thread, move to our thread to reduce risk of
// blocking ui thread
mHandler.post(() -> {
synchronized (mLock) {
onUidImportanceChangedLocked(uid, importance);
}
});
},
FOREGROUND_IMPORTANCE_CUTOFF);
localPowerManager.registerLowPowerModeObserver(ServiceType.LOCATION,
state -> {
// listener invoked on ui thread, move to our thread to reduce risk of
// blocking ui thread
mHandler.post(() -> {
synchronized (mLock) {
onBatterySaverModeChangedLocked(state.locationMode);
}
});
});
mBatterySaverMode = mPowerManager.getLocationPowerSaveMode();
mSettingsStore.addOnLocationEnabledChangedListener((userId) -> {
synchronized (mLock) {
onLocationModeChangedLocked(userId);
}
});
mSettingsStore.addOnBackgroundThrottleIntervalChangedListener(() -> {
synchronized (mLock) {
onBackgroundThrottleIntervalChangedLocked();
}
});
mSettingsStore.addOnBackgroundThrottlePackageWhitelistChangedListener(() -> {
synchronized (mLock) {
onBackgroundThrottleWhitelistChangedLocked();
}
});
mSettingsStore.addOnIgnoreSettingsPackageWhitelistChangedListener(() -> {
synchronized (mLock) {
onIgnoreSettingsWhitelistChangedLocked();
}
});
new PackageMonitor() {
@Override
public void onPackageDisappeared(String packageName, int reason) {
synchronized (mLock) {
LocationManagerService.this.onPackageDisappearedLocked(packageName);
}
}
}.register(mContext, mHandler.getLooper(), true);
mUserInfoStore.addListener((oldUserId, newUserId) -> {
synchronized (mLock) {
onUserChangedLocked(oldUserId, newUserId);
}
});
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action == null) {
return;
}
synchronized (mLock) {
switch (action) {
case Intent.ACTION_SCREEN_ON:
case Intent.ACTION_SCREEN_OFF:
onScreenStateChangedLocked();
break;
}
}
}
}, UserHandle.ALL, intentFilter, null, mHandler);
// switching the user from null to current here performs the bulk of the initialization
// work. the user being changed will cause a reload of all user specific settings, which
// causes initialization, and propagates changes until a steady state is reached
onUserChangedLocked(UserHandle.USER_NULL, mUserInfoStore.getCurrentUserId());
}
}
private void onSystemThirdPartyAppsCanStart() {
synchronized (mLock) {
// prepare providers
initializeProvidersLocked();
}
}
@GuardedBy("mLock")
private void onAppOpChangedLocked() {
for (Receiver receiver : mReceivers.values()) {
receiver.updateMonitoring(true);
}
for (LocationProviderManager manager : mProviderManagers) {
applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
private void onPermissionsChangedLocked() {
for (LocationProviderManager manager : mProviderManagers) {
applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
private void onBatterySaverModeChangedLocked(int newLocationMode) {
if (mBatterySaverMode == newLocationMode) {
return;
}
if (D) {
Slog.d(TAG,
"Battery Saver location mode changed from "
+ locationPowerSaveModeToString(mBatterySaverMode) + " to "
+ locationPowerSaveModeToString(newLocationMode));
}
mBatterySaverMode = newLocationMode;
for (LocationProviderManager manager : mProviderManagers) {
applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
private void onScreenStateChangedLocked() {
if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
for (LocationProviderManager manager : mProviderManagers) {
applyRequirementsLocked(manager);
}
}
}
@GuardedBy("mLock")
private void onLocationModeChangedLocked(int userId) {
if (D) {
Log.d(TAG, "[u" + userId + "] location enabled = " + isLocationEnabledForUser(userId));
}
Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION);
intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId));
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
for (LocationProviderManager manager : mProviderManagers) {
manager.onUseableChangedLocked(userId);
}
}
@GuardedBy("mLock")
private void onPackageDisappearedLocked(String packageName) {
ArrayList<Receiver> deadReceivers = null;
for (Receiver receiver : mReceivers.values()) {
if (receiver.mCallerIdentity.mPackageName.equals(packageName)) {
if (deadReceivers == null) {
deadReceivers = new ArrayList<>();
}
deadReceivers.add(receiver);
}
}
// perform removal outside of mReceivers loop
if (deadReceivers != null) {
for (Receiver receiver : deadReceivers) {
removeUpdatesLocked(receiver);
}
}
}
@GuardedBy("mLock")
private void onUidImportanceChangedLocked(int uid, int importance) {
boolean foreground = LocationManagerServiceUtils.isImportanceForeground(importance);
HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
String provider = entry.getKey();
for (UpdateRecord record : entry.getValue()) {
if (record.mReceiver.mCallerIdentity.mUid == uid
&& record.mIsForegroundUid != foreground) {
if (D) {
Log.d(TAG, "request from uid " + uid + " is now "
+ LocationManagerServiceUtils.foregroundAsString(
foreground));
}
record.updateForeground(foreground);
if (!isThrottlingExemptLocked(record.mReceiver.mCallerIdentity)) {
affectedProviders.add(provider);
}
}
}
}
for (String provider : affectedProviders) {
applyRequirementsLocked(provider);
}
}
@GuardedBy("mLock")
private void onBackgroundThrottleIntervalChangedLocked() {
for (LocationProviderManager manager : mProviderManagers) {
applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
private void onBackgroundThrottleWhitelistChangedLocked() {
for (LocationProviderManager manager : mProviderManagers) {
applyRequirementsLocked(manager);
}
}
@GuardedBy("lock")
private void onIgnoreSettingsWhitelistChangedLocked() {
for (LocationProviderManager manager : mProviderManagers) {
applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) {
PackageManager pm = mContext.getPackageManager();
String systemPackageName = mContext.getPackageName();
ArrayList<HashSet<Signature>> sigSets = ServiceWatcher.getSignatureSets(mContext, pkgs);
List<ResolveInfo> rInfos = pm.queryIntentServicesAsUser(
new Intent(FUSED_LOCATION_SERVICE_ACTION),
PackageManager.GET_META_DATA, mUserInfoStore.getCurrentUserId());
for (ResolveInfo rInfo : rInfos) {
String packageName = rInfo.serviceInfo.packageName;
// Check that the signature is in the list of supported sigs. If it's not in
// this list the standard provider binding logic won't bind to it.
try {
PackageInfo pInfo;
pInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
if (!ServiceWatcher.isSignatureMatch(pInfo.signatures, sigSets)) {
Log.w(TAG, packageName + " resolves service " + FUSED_LOCATION_SERVICE_ACTION +
", but has wrong signature, ignoring");
continue;
}
} catch (NameNotFoundException e) {
Log.e(TAG, "missing package: " + packageName);
continue;
}
// Get the version info
if (rInfo.serviceInfo.metaData == null) {
Log.w(TAG, "Found fused provider without metadata: " + packageName);
continue;
}
int version = rInfo.serviceInfo.metaData.getInt(
ServiceWatcher.EXTRA_SERVICE_VERSION, -1);
if (version == 0) {
// This should be the fallback fused location provider.
// Make sure it's in the system partition.
if ((rInfo.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
if (D) Log.d(TAG, "Fallback candidate not in /system: " + packageName);
continue;
}
// Check that the fallback is signed the same as the OS
// as a proxy for coreApp="true"
if (pm.checkSignatures(systemPackageName, packageName)
!= PackageManager.SIGNATURE_MATCH) {
if (D) {
Log.d(TAG, "Fallback candidate not signed the same as system: "
+ packageName);
}
continue;
}
// Found a valid fallback.
if (D) Log.d(TAG, "Found fallback provider: " + packageName);
return;
} else {
if (D) Log.d(TAG, "Fallback candidate not version 0: " + packageName);
}
}
throw new IllegalStateException("Unable to find a fused location provider that is in the "
+ "system partition with version 0 and signed with the platform certificate. "
+ "Such a package is needed to provide a default fused location provider in the "
+ "event that no other fused location provider has been installed or is currently "
+ "available. For example, coreOnly boot mode when decrypting the data "
+ "partition. The fallback must also be marked coreApp=\"true\" in the manifest");
}
@GuardedBy("mLock")
private void initializeProvidersLocked() {
if (GnssManagerService.isGnssSupported()) {
mGnssManagerService = new GnssManagerService(this, mContext, mLocationUsageLogger);
LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER);
mProviderManagers.add(gnssManager);
gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider());
}
/*
Load package name(s) containing location provider support.
These packages can contain services implementing location providers:
Geocoder Provider, Network Location Provider, and
Fused Location Provider. They will each be searched for
service components implementing these providers.
The location framework also has support for installation
of new location providers at run-time. The new package does not
have to be explicitly listed here, however it must have a signature
that matches the signature of at least one package on this list.
*/
Resources resources = mContext.getResources();
String[] pkgs = resources.getStringArray(
com.android.internal.R.array.config_locationProviderPackageNames);
if (D) {
Log.d(TAG, "certificates for location providers pulled from: " +
Arrays.toString(pkgs));
}
ensureFallbackFusedProviderPresentLocked(pkgs);
LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
mContext,
NETWORK_LOCATION_SERVICE_ACTION,
com.android.internal.R.bool.config_enableNetworkLocationOverlay,
com.android.internal.R.string.config_networkLocationProviderPackageName,
com.android.internal.R.array.config_locationProviderPackageNames);
if (networkProvider != null) {
LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER);
mProviderManagers.add(networkManager);
networkManager.setRealProvider(networkProvider);
} else {
Slog.w(TAG, "no network location provider found");
}
// bind to fused provider
LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind(
mContext,
FUSED_LOCATION_SERVICE_ACTION,
com.android.internal.R.bool.config_enableFusedLocationOverlay,
com.android.internal.R.string.config_fusedLocationProviderPackageName,
com.android.internal.R.array.config_locationProviderPackageNames);
if (fusedProvider != null) {
LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER);
mProviderManagers.add(fusedManager);
fusedManager.setRealProvider(fusedProvider);
} else {
Slog.e(TAG, "no fused location provider found",
new IllegalStateException("Location service needs a fused location provider"));
}
// bind to geocoder provider
mGeocodeProvider = GeocoderProxy.createAndBind(mContext,
com.android.internal.R.bool.config_enableGeocoderOverlay,
com.android.internal.R.string.config_geocoderProviderPackageName,
com.android.internal.R.array.config_locationProviderPackageNames);
if (mGeocodeProvider == null) {
Slog.e(TAG, "no geocoder provider found");
}
if (mGnssManagerService != null) {
// bind to geofence provider
GeofenceProxy provider = GeofenceProxy.createAndBind(
mContext, com.android.internal.R.bool.config_enableGeofenceOverlay,
com.android.internal.R.string.config_geofenceProviderPackageName,
com.android.internal.R.array.config_locationProviderPackageNames,
mGnssManagerService.getGpsGeofenceProxy(),
null);
if (provider == null) {
Slog.d(TAG, "Unable to bind FLP Geofence proxy.");
}
}
// bind to hardware activity recognition
boolean activityRecognitionHardwareIsSupported = ActivityRecognitionHardware.isSupported();
ActivityRecognitionHardware activityRecognitionHardware = null;
if (activityRecognitionHardwareIsSupported) {
activityRecognitionHardware = ActivityRecognitionHardware.getInstance(mContext);
} else {
Slog.d(TAG, "Hardware Activity-Recognition not supported.");
}
ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind(
mContext,
activityRecognitionHardwareIsSupported,
activityRecognitionHardware,
com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
com.android.internal.R.string.config_activityRecognitionHardwarePackageName,
com.android.internal.R.array.config_locationProviderPackageNames);
if (proxy == null) {
Slog.d(TAG, "Unable to bind ActivityRecognitionProxy.");
}
String[] testProviderStrings = resources.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(
Boolean.parseBoolean(fragments[1]) /* requiresNetwork */,
Boolean.parseBoolean(fragments[2]) /* requiresSatellite */,
Boolean.parseBoolean(fragments[3]) /* requiresCell */,
Boolean.parseBoolean(fragments[4]) /* hasMonetaryCost */,
Boolean.parseBoolean(fragments[5]) /* supportsAltitude */,
Boolean.parseBoolean(fragments[6]) /* supportsSpeed */,
Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
Integer.parseInt(fragments[8]) /* powerRequirement */,
Integer.parseInt(fragments[9]) /* accuracy */);
addTestProvider(name, properties, mContext.getOpPackageName());
}
}
@GuardedBy("mLock")
private void onUserChangedLocked(int oldUserId, int newUserId) {
if (D) {
Log.d(TAG, "foreground user is changing to " + newUserId);
}
for (LocationProviderManager manager : mProviderManagers) {
// update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
mSettingsStore.setLocationProviderAllowed(manager.getName(),
manager.isUseable(newUserId), newUserId);
manager.onUseableChangedLocked(oldUserId);
manager.onUseableChangedLocked(newUserId);
}
}
/**
* Location provider manager, manages a LocationProvider.
*/
class LocationProviderManager implements MockableLocationProvider.Listener {
private final String mName;
// acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
protected final MockableLocationProvider mProvider;
// useable state for parent user ids, no entry implies false. location state is only kept
// for parent user ids, the location state for a profile user id is assumed to be the same
// as for the parent. if querying this structure, ensure that the user id being used is a
// parent id or the results may be incorrect.
@GuardedBy("mLock")
private final SparseArray<Boolean> mUseable;
private LocationProviderManager(String name) {
mName = name;
mUseable = new SparseArray<>(1);
// initialize last since this lets our reference escape
mProvider = new MockableLocationProvider(mContext, mLock, this);
// we can assume all users start with unuseable location state since the initial state
// of all providers is disabled. no need to initialize mUseable further.
}
public String getName() {
return mName;
}
public boolean hasProvider() {
return mProvider.getProvider() != null;
}
public void setRealProvider(AbstractLocationProvider provider) {
mProvider.setRealProvider(provider);
}
public void setMockProvider(@Nullable MockProvider provider) {
mProvider.setMockProvider(provider);
}
public Set<String> getPackages() {
return mProvider.getState().providerPackageNames;
}
@Nullable
public ProviderProperties getProperties() {
return mProvider.getState().properties;
}
public void setMockProviderEnabled(boolean enabled) {
synchronized (mLock) {
if (!mProvider.isMock()) {
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
mProvider.setMockProviderEnabled(enabled);
}
}
public void setMockProviderLocation(Location location) {
synchronized (mLock) {
if (!mProvider.isMock()) {
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
String locationProvider = location.getProvider();
if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) {
// The location has an explicit provider that is different from the mock
// provider name. The caller may be trying to fool us via b/33091107.
EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
mName + "!=" + locationProvider);
}
mProvider.setMockProviderLocation(location);
}
}
public List<LocationRequest> getMockProviderRequests() {
synchronized (mLock) {
if (!mProvider.isMock()) {
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
return mProvider.getCurrentRequest().locationRequests;
}
}
public void setRequest(ProviderRequest request) {
mProvider.setRequest(request);
}
public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
mProvider.sendExtraCommand(uid, pid, command, extras);
}
@GuardedBy("mLock")
@Override
public void onReportLocation(Location location) {
// don't validate mock locations
if (!location.isFromMockProvider()) {
if (location.getLatitude() == 0 && location.getLongitude() == 0) {
Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
return;
}
}
handleLocationChangedLocked(location, this);
}
@GuardedBy("mLock")
@Override
public void onReportLocation(List<Location> locations) {
if (mGnssManagerService == null) {
return;
}
if (!GPS_PROVIDER.equals(mName) || !isUseable()) {
Slog.w(TAG, "reportLocationBatch() called without user permission");
return;
}
mGnssManagerService.onReportLocation(locations);
}
@GuardedBy("mLock")
@Override
public void onStateChanged(State oldState, State newState) {
if (oldState.enabled != newState.enabled) {
// it would be more correct to call this for all users, but we know this can
// only affect the current user since providers are disabled for non-current
// users
onUseableChangedLocked(mUserInfoStore.getCurrentUserId());
}
}
public boolean isUseable() {
return isUseable(mUserInfoStore.getCurrentUserId());
}
public boolean isUseable(int userId) {
synchronized (mLock) {
// normalize user id to always refer to parent since profile state is always the
// same as parent state
userId = mUserInfoStore.getParentUserId(userId);
return mUseable.get(userId, Boolean.FALSE);
}
}
@GuardedBy("mLock")
public void onUseableChangedLocked(int userId) {
if (userId == UserHandle.USER_NULL) {
// only used during initialization - we don't care about the null user
return;
}
// normalize user id to always refer to parent since profile state is always the same
// as parent state
userId = mUserInfoStore.getParentUserId(userId);
// if any property that contributes to "useability" here changes state, it MUST result
// in a direct or indrect call to onUseableChangedLocked. this allows the provider to
// guarantee that it will always eventually reach the correct state.
boolean useable = (userId == mUserInfoStore.getCurrentUserId())
&& mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled;
if (useable == isUseable(userId)) {
return;
}
mUseable.put(userId, useable);
if (D) {
Log.d(TAG, "[u" + userId + "] " + mName + " provider useable = " + useable);
}
// fused and passive provider never get public updates for legacy reasons
if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) {
// update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
mSettingsStore.setLocationProviderAllowed(mName, useable, userId);
Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION);
intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName);
intent.putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, useable);
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
}
if (!useable) {
// If any provider has been disabled, clear all last locations for all
// providers. This is to be on the safe side in case a provider has location
// derived from this disabled provider.
mLastLocation.clear();
mLastLocationCoarseInterval.clear();
}
updateProviderUseableLocked(this);
}
public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
synchronized (mLock) {
pw.print(mName + " provider");
if (mProvider.isMock()) {
pw.print(" [mock]");
}
pw.println(":");
pw.increaseIndent();
boolean useable = isUseable();
pw.println("useable=" + useable);
if (!useable) {
pw.println("enabled=" + mProvider.getState().enabled);
}
pw.println("properties=" + mProvider.getState().properties);
}
mProvider.dump(fd, pw, args);
pw.decreaseIndent();
}
}
class PassiveLocationProviderManager extends LocationProviderManager {
private PassiveLocationProviderManager() {
super(PASSIVE_PROVIDER);
}
@Override
public void setRealProvider(AbstractLocationProvider provider) {
Preconditions.checkArgument(provider instanceof PassiveProvider);
super.setRealProvider(provider);
}
@Override
public void setMockProvider(@Nullable MockProvider provider) {
if (provider != null) {
throw new IllegalArgumentException("Cannot mock the passive provider");
}
}
public void updateLocation(Location location) {
synchronized (mLock) {
PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider();
Preconditions.checkState(passiveProvider != null);
long identity = Binder.clearCallingIdentity();
try {
passiveProvider.updateLocation(location);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
}
/**
* A wrapper class holding either an ILocationListener or a PendingIntent to receive
* location updates.
*/
private final class Receiver extends LocationManagerServiceUtils.LinkedListenerBase implements
PendingIntent.OnFinished {
private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
private final int mAllowedResolutionLevel; // resolution level allowed to receiver
private final ILocationListener mListener;
final PendingIntent mPendingIntent;
final WorkSource mWorkSource; // WorkSource for battery blame, or null to assign to caller.
private final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
private final Object mKey;
final HashMap<String, UpdateRecord> mUpdateRecords = new HashMap<>();
// True if app ops has started monitoring this receiver for locations.
private boolean mOpMonitoring;
// True if app ops has started monitoring this receiver for high power (gps) locations.
private boolean mOpHighPowerMonitoring;
private int mPendingBroadcasts;
PowerManager.WakeLock mWakeLock;
private Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
String packageName, @Nullable String featureId, WorkSource workSource,
boolean hideFromAppOps, @NonNull String listenerIdentifier) {
super(new CallerIdentity(uid, pid, packageName, featureId, listenerIdentifier),
"LocationListener");
mListener = listener;
mPendingIntent = intent;
if (listener != null) {
mKey = listener.asBinder();
} else {
mKey = intent;
}
mAllowedResolutionLevel = getAllowedResolutionLevel(pid, uid);
if (workSource != null && workSource.isEmpty()) {
workSource = null;
}
mWorkSource = workSource;
mHideFromAppOps = hideFromAppOps;
updateMonitoring(true);
// construct/configure wakelock
mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
if (workSource == null) {
workSource = new WorkSource(mCallerIdentity.mUid, mCallerIdentity.mPackageName);
}
mWakeLock.setWorkSource(workSource);
// For a non-reference counted wakelock, each acquire will reset the timeout, and we
// only need to release it once.
mWakeLock.setReferenceCounted(false);
}
@Override
public boolean equals(Object otherObj) {
return (otherObj instanceof Receiver) && mKey.equals(((Receiver) otherObj).mKey);
}
@Override
public int hashCode() {
return mKey.hashCode();
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append("Reciever[");
s.append(Integer.toHexString(System.identityHashCode(this)));
if (mListener != null) {
s.append(" listener");
} else {
s.append(" intent");
}
for (String p : mUpdateRecords.keySet()) {
s.append(" ").append(mUpdateRecords.get(p).toString());
}
s.append(" monitoring location: ").append(mOpMonitoring);
s.append("]");
return s.toString();
}
/**
* Update AppOp monitoring for this receiver.
*
* @param allow If true receiver is currently active, if false it's been removed.
*/
public void updateMonitoring(boolean allow) {
if (mHideFromAppOps) {
return;
}
boolean requestingLocation = false;
boolean requestingHighPowerLocation = false;
if (allow) {
// See if receiver has any enabled update records. Also note if any update records
// are high power (has a high power provider with an interval under a threshold).
for (UpdateRecord updateRecord : mUpdateRecords.values()) {
LocationProviderManager manager = getLocationProviderManager(
updateRecord.mProvider);
if (manager == null) {
continue;
}
if (!manager.isUseable() && !isSettingsExemptLocked(updateRecord)) {
continue;
}
requestingLocation = true;
ProviderProperties properties = manager.getProperties();
if (properties != null
&& properties.mPowerRequirement == Criteria.POWER_HIGH
&& updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
requestingHighPowerLocation = true;
break;
}
}
}
// First update monitoring of any location request (including high power).
mOpMonitoring = updateMonitoring(
requestingLocation,
mOpMonitoring,
AppOpsManager.OP_MONITOR_LOCATION);
// Now update monitoring of high power requests only.
boolean wasHighPowerMonitoring = mOpHighPowerMonitoring;
mOpHighPowerMonitoring = updateMonitoring(
requestingHighPowerLocation,
mOpHighPowerMonitoring,
AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION);
if (mOpHighPowerMonitoring != wasHighPowerMonitoring) {
// Send an intent to notify that a high power request has been added/removed.
Intent intent = new Intent(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
}
/**
* Update AppOps monitoring for a single location request and op type.
*
* @param allowMonitoring True if monitoring is allowed for this request/op.
* @param currentlyMonitoring True if AppOps is currently monitoring this request/op.
* @param op AppOps code for the op to update.
* @return True if monitoring is on for this request/op after updating.
*/
private boolean updateMonitoring(boolean allowMonitoring, boolean currentlyMonitoring,
int op) {
if (!currentlyMonitoring) {
if (allowMonitoring) {
return mAppOps.startOpNoThrow(op, mCallerIdentity.mUid,
mCallerIdentity.mPackageName, false, mCallerIdentity.mFeatureId, null)
== AppOpsManager.MODE_ALLOWED;
}
} else {
if (!allowMonitoring
|| mAppOps.checkOpNoThrow(op, mCallerIdentity.mUid,
mCallerIdentity.mPackageName) != AppOpsManager.MODE_ALLOWED) {
mAppOps.finishOp(op, mCallerIdentity.mUid, mCallerIdentity.mPackageName);
return false;
}
}
return currentlyMonitoring;
}
public boolean isListener() {
return mListener != null;
}
public boolean isPendingIntent() {
return mPendingIntent != null;
}
public ILocationListener getListener() {
if (mListener != null) {
return mListener;
}
throw new IllegalStateException("Request for non-existent listener");
}
public boolean callLocationChangedLocked(Location location) {
if (mListener != null) {
try {
mListener.onLocationChanged(new Location(location));
// call this after broadcasting so we do not increment
// if we throw an exception.
incrementPendingBroadcastsLocked();
} catch (RemoteException e) {
return false;
}
} else {
Intent locationChanged = new Intent();
locationChanged.putExtra(LocationManager.KEY_LOCATION_CHANGED,
new Location(location));
try {
mPendingIntent.send(mContext, 0, locationChanged, this, mHandler,
getResolutionPermission(mAllowedResolutionLevel),
PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
// call this after broadcasting so we do not increment
// if we throw an exception.
incrementPendingBroadcastsLocked();
} catch (PendingIntent.CanceledException e) {
return false;
}
}
return true;
}
private boolean callProviderEnabledLocked(String provider, boolean enabled) {
// First update AppOp monitoring.
// An app may get/lose location access as providers are enabled/disabled.
updateMonitoring(true);
if (mListener != null) {
try {
if (enabled) {
mListener.onProviderEnabled(provider);
} else {
mListener.onProviderDisabled(provider);
}
// call this after broadcasting so we do not increment
// if we throw an exception.
incrementPendingBroadcastsLocked();
} catch (RemoteException e) {
return false;
}
} else {
Intent providerIntent = new Intent();
providerIntent.putExtra(LocationManager.KEY_PROVIDER_ENABLED, enabled);
try {
mPendingIntent.send(mContext, 0, providerIntent, this, mHandler,
getResolutionPermission(mAllowedResolutionLevel),
PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
// call this after broadcasting so we do not increment
// if we throw an exception.
incrementPendingBroadcastsLocked();
} catch (PendingIntent.CanceledException e) {
return false;
}
}
return true;
}
public void callRemovedLocked() {
if (mListener != null) {
try {
mListener.onRemoved();
} catch (RemoteException e) {
// doesn't matter
}
}
}
@Override
public void binderDied() {
if (D) Log.d(TAG, "Remote " + mListenerName + " died.");
synchronized (mLock) {
removeUpdatesLocked(this);
clearPendingBroadcastsLocked();
}
}
@Override
public void onSendFinished(PendingIntent pendingIntent, Intent intent,
int resultCode, String resultData, Bundle resultExtras) {
synchronized (mLock) {
decrementPendingBroadcastsLocked();
}
}
// this must be called while synchronized by caller in a synchronized block
// containing the sending of the broadcaset
private void incrementPendingBroadcastsLocked() {
mPendingBroadcasts++;
// so wakelock calls will succeed
long identity = Binder.clearCallingIdentity();
try {
mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
private void decrementPendingBroadcastsLocked() {
if (--mPendingBroadcasts == 0) {
// so wakelock calls will succeed
long identity = Binder.clearCallingIdentity();
try {
if (mWakeLock.isHeld()) {
mWakeLock.release();
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
public void clearPendingBroadcastsLocked() {
if (mPendingBroadcasts > 0) {
mPendingBroadcasts = 0;
// so wakelock calls will succeed
long identity = Binder.clearCallingIdentity();
try {
if (mWakeLock.isHeld()) {
mWakeLock.release();
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
}
@Override
public void locationCallbackFinished(ILocationListener listener) {
//Do not use getReceiverLocked here as that will add the ILocationListener to
//the receiver list if it is not found. If it is not found then the
//LocationListener was removed when it had a pending broadcast and should
//not be added back.
synchronized (mLock) {
Receiver receiver = mReceivers.get(listener.asBinder());
if (receiver != null) {
receiver.decrementPendingBroadcastsLocked();
}
}
}
@Override
public int getGnssYearOfHardware() {
return mGnssManagerService == null ? 0 : mGnssManagerService.getGnssYearOfHardware();
}
@Override
@Nullable
public String getGnssHardwareModelName() {
return mGnssManagerService == null ? "" : mGnssManagerService.getGnssHardwareModelName();
}
@Override
public int getGnssBatchSize(String packageName) {
return mGnssManagerService == null ? 0 : mGnssManagerService.getGnssBatchSize(packageName);
}
@Override
public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
String featureId, String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
return mGnssManagerService != null && mGnssManagerService.addGnssBatchingCallback(
callback, packageName, featureId, listenerIdentifier);
}
@Override
public void removeGnssBatchingCallback() {
if (mGnssManagerService != null) mGnssManagerService.removeGnssBatchingCallback();
}
@Override
public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) {
return mGnssManagerService != null && mGnssManagerService.startGnssBatch(periodNanos,
wakeOnFifoFull, packageName);
}
@Override
public void flushGnssBatch(String packageName) {
if (mGnssManagerService != null) mGnssManagerService.flushGnssBatch(packageName);
}
@Override
public boolean stopGnssBatch() {
return mGnssManagerService != null && mGnssManagerService.stopGnssBatch();
}
@Nullable
private LocationProviderManager getLocationProviderManager(String providerName) {
for (LocationProviderManager manager : mProviderManagers) {
if (providerName.equals(manager.getName())) {
return manager;
}
}
return null;
}
private String getResolutionPermission(int resolutionLevel) {
switch (resolutionLevel) {
case RESOLUTION_LEVEL_FINE:
return android.Manifest.permission.ACCESS_FINE_LOCATION;
case RESOLUTION_LEVEL_COARSE:
return android.Manifest.permission.ACCESS_COARSE_LOCATION;
default:
return null;
}
}
private int getAllowedResolutionLevel(int pid, int uid) {
if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
pid, uid) == PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_FINE;
} else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
pid, uid) == PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_COARSE;
} else {
return RESOLUTION_LEVEL_NONE;
}
}
private int getCallerAllowedResolutionLevel() {
return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
}
private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) {
if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission");
}
}
@GuardedBy("mLock")
private int getMinimumResolutionLevelForProviderUseLocked(String provider) {
if (GPS_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) {
// gps and passive providers require FINE permission
return RESOLUTION_LEVEL_FINE;
} else if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) {
// network and fused providers are ok with COARSE or FINE
return RESOLUTION_LEVEL_COARSE;
} else {
for (LocationProviderManager lp : mProviderManagers) {
if (!lp.getName().equals(provider)) {
continue;
}
ProviderProperties properties = lp.getProperties();
if (properties != null) {
if (properties.mRequiresSatellite) {
// provider requiring satellites require FINE permission
return RESOLUTION_LEVEL_FINE;
} else if (properties.mRequiresNetwork || properties.mRequiresCell) {
// provider requiring network and or cell require COARSE or FINE
return RESOLUTION_LEVEL_COARSE;
}
}
}
}
return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE
}
@GuardedBy("mLock")
private void checkResolutionLevelIsSufficientForProviderUseLocked(int allowedResolutionLevel,
String providerName) {
int requiredResolutionLevel = getMinimumResolutionLevelForProviderUseLocked(providerName);
if (allowedResolutionLevel < requiredResolutionLevel) {
switch (requiredResolutionLevel) {
case RESOLUTION_LEVEL_FINE:
throw new SecurityException("\"" + providerName + "\" location provider " +
"requires ACCESS_FINE_LOCATION permission.");
case RESOLUTION_LEVEL_COARSE:
throw new SecurityException("\"" + providerName + "\" location provider " +
"requires ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission.");
default:
throw new SecurityException("Insufficient permission for \"" + providerName +
"\" location provider.");
}
}
}
public static int resolutionLevelToOp(int allowedResolutionLevel) {
if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) {
if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) {
return AppOpsManager.OP_COARSE_LOCATION;
} else {
return AppOpsManager.OP_FINE_LOCATION;
}
}
return -1;
}
private static String resolutionLevelToOpStr(int allowedResolutionLevel) {
switch (allowedResolutionLevel) {
case RESOLUTION_LEVEL_COARSE:
return AppOpsManager.OPSTR_COARSE_LOCATION;
case RESOLUTION_LEVEL_FINE:
// fall through
case RESOLUTION_LEVEL_NONE:
// fall through
default:
// Use the most restrictive ops if not sure.
return AppOpsManager.OPSTR_FINE_LOCATION;
}
}
private boolean reportLocationAccessNoThrow(int pid, int uid, String packageName,
@Nullable String featureId, int allowedResolutionLevel, @Nullable String message) {
int op = resolutionLevelToOp(allowedResolutionLevel);
if (op >= 0) {
if (mAppOps.noteOpNoThrow(op, uid, packageName, featureId, message)
!= AppOpsManager.MODE_ALLOWED) {
return false;
}
}
return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
}
private boolean checkLocationAccess(int pid, int uid, String packageName,
int allowedResolutionLevel) {
int op = resolutionLevelToOp(allowedResolutionLevel);
if (op >= 0) {
if (mAppOps.checkOp(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
return false;
}
}
return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
}
/**
* Returns all providers by name, including passive and the ones that are not permitted to
* be accessed by the calling activity or are currently disabled, but excluding fused.
*/
@Override
public List<String> getAllProviders() {
ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
for (LocationProviderManager manager : mProviderManagers) {
if (FUSED_PROVIDER.equals(manager.getName())) {
continue;
}
providers.add(manager.getName());
}
return providers;
}
/**
* Return all providers by name, that match criteria and are optionally
* enabled.
* Can return passive provider, but never returns fused provider.
*/
@Override
public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
synchronized (mLock) {
ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
for (LocationProviderManager manager : mProviderManagers) {
String name = manager.getName();
if (FUSED_PROVIDER.equals(name)) {
continue;
}
if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) {
continue;
}
if (enabledOnly && !manager.isUseable()) {
continue;
}
if (criteria != null
&& !android.location.LocationProvider.propertiesMeetCriteria(
name, manager.getProperties(), criteria)) {
continue;
}
providers.add(name);
}
return providers;
}
}
/**
* Return the name of the best provider given a Criteria object.
* This method has been deprecated from the public API,
* and the whole LocationProvider (including #meetsCriteria)
* has been deprecated as well. So this method now uses
* some simplified logic.
*/
@Override
public String getBestProvider(Criteria criteria, boolean enabledOnly) {
List<String> providers = getProviders(criteria, enabledOnly);
if (providers.isEmpty()) {
providers = getProviders(null, enabledOnly);
}
if (!providers.isEmpty()) {
if (providers.contains(GPS_PROVIDER)) {
return GPS_PROVIDER;
} else if (providers.contains(NETWORK_PROVIDER)) {
return NETWORK_PROVIDER;
} else {
return providers.get(0);
}
}
return null;
}
@GuardedBy("mLock")
private void updateProviderUseableLocked(LocationProviderManager manager) {
boolean useable = manager.isUseable();
ArrayList<Receiver> deadReceivers = null;
ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
if (records != null) {
for (UpdateRecord record : records) {
if (!mUserInfoStore.isCurrentUserOrProfile(
UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
continue;
}
// requests that ignore location settings will never provide notifications
if (isSettingsExemptLocked(record)) {
continue;
}
// Sends a notification message to the receiver
if (!record.mReceiver.callProviderEnabledLocked(manager.getName(), useable)) {
if (deadReceivers == null) {
deadReceivers = new ArrayList<>();
}
deadReceivers.add(record.mReceiver);
}
}
}
if (deadReceivers != null) {
for (int i = deadReceivers.size() - 1; i >= 0; i--) {
removeUpdatesLocked(deadReceivers.get(i));
}
}
applyRequirementsLocked(manager);
}
@GuardedBy("mLock")
private void applyRequirementsLocked(String providerName) {
LocationProviderManager manager = getLocationProviderManager(providerName);
if (manager != null) {
applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
private void applyRequirementsLocked(LocationProviderManager manager) {
ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
ProviderRequest.Builder providerRequest = new ProviderRequest.Builder();
// if provider is not active, it should not respond to requests
if (mProviderManagers.contains(manager) && records != null && !records.isEmpty()) {
long backgroundThrottleInterval;
long identity = Binder.clearCallingIdentity();
try {
backgroundThrottleInterval = mSettingsStore.getBackgroundThrottleIntervalMs();
} finally {
Binder.restoreCallingIdentity(identity);
}
ArrayList<LocationRequest> requests = new ArrayList<>(records.size());
final boolean isForegroundOnlyMode =
mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
final boolean shouldThrottleRequests =
mBatterySaverMode
== PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF
&& !mPowerManager.isInteractive();
// initialize the low power mode to true and set to false if any of the records requires
providerRequest.setLowPowerMode(true);
for (UpdateRecord record : records) {
if (!mUserInfoStore.isCurrentUserOrProfile(
UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
continue;
}
if (!checkLocationAccess(
record.mReceiver.mCallerIdentity.mPid,
record.mReceiver.mCallerIdentity.mUid,
record.mReceiver.mCallerIdentity.mPackageName,
record.mReceiver.mAllowedResolutionLevel)) {
continue;
}
final boolean isBatterySaverDisablingLocation = shouldThrottleRequests
|| (isForegroundOnlyMode && !record.mIsForegroundUid);
if (!manager.isUseable() || isBatterySaverDisablingLocation) {
if (isSettingsExemptLocked(record)) {
providerRequest.setLocationSettingsIgnored(true);
providerRequest.setLowPowerMode(false);
} else {
continue;
}
}
LocationRequest locationRequest = record.mRealRequest;
long interval = locationRequest.getInterval();
// if we're forcing location, don't apply any throttling
if (!providerRequest.isLocationSettingsIgnored() && !isThrottlingExemptLocked(
record.mReceiver.mCallerIdentity)) {
if (!record.mIsForegroundUid) {
interval = Math.max(interval, backgroundThrottleInterval);
}
if (interval != locationRequest.getInterval()) {
locationRequest = new LocationRequest(locationRequest);
locationRequest.setInterval(interval);
}
}
record.mRequest = locationRequest;
requests.add(locationRequest);
if (!locationRequest.isLowPowerMode()) {
providerRequest.setLowPowerMode(false);
}
if (interval < providerRequest.getInterval()) {
providerRequest.setInterval(interval);
}
}
providerRequest.setLocationRequests(requests);
if (providerRequest.getInterval() < Long.MAX_VALUE) {
// calculate who to blame for power
// This is somewhat arbitrary. We pick a threshold interval
// that is slightly higher that the minimum interval, and
// spread the blame across all applications with a request
// under that threshold.
// TODO: overflow
long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2;
for (UpdateRecord record : records) {
if (mUserInfoStore.isCurrentUserOrProfile(
UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
LocationRequest locationRequest = record.mRequest;
// Don't assign battery blame for update records whose
// client has no permission to receive location data.
if (!providerRequest.getLocationRequests().contains(locationRequest)) {
continue;
}
if (locationRequest.getInterval() <= thresholdInterval) {
if (record.mReceiver.mWorkSource != null
&& isValidWorkSource(record.mReceiver.mWorkSource)) {
providerRequest.getWorkSource().add(record.mReceiver.mWorkSource);
} else {
// Assign blame to caller if there's no WorkSource associated with
// the request or if it's invalid.
providerRequest.getWorkSource().add(
record.mReceiver.mCallerIdentity.mUid,
record.mReceiver.mCallerIdentity.mPackageName);
}
}
}
}
}
}
manager.setRequest(providerRequest.build());
}
/**
* Whether a given {@code WorkSource} associated with a Location request is valid.
*/
private static boolean isValidWorkSource(WorkSource workSource) {
if (workSource.size() > 0) {
// If the WorkSource has one or more non-chained UIDs, make sure they're accompanied
// by tags.
return workSource.getPackageName(0) != null;
} else {
// For now, make sure callers have supplied an attribution tag for use with
// AppOpsManager. This might be relaxed in the future.
final List<WorkChain> workChains = workSource.getWorkChains();
return workChains != null && !workChains.isEmpty() &&
workChains.get(0).getAttributionTag() != null;
}
}
@Override
public String[] getBackgroundThrottlingWhitelist() {
return mSettingsStore.getBackgroundThrottlePackageWhitelist().toArray(new String[0]);
}
@Override
public String[] getIgnoreSettingsWhitelist() {
return mSettingsStore.getIgnoreSettingsPackageWhitelist().toArray(new String[0]);
}
@GuardedBy("mLock")
public boolean isThrottlingExemptLocked(CallerIdentity callerIdentity) {
if (callerIdentity.mUid == Process.SYSTEM_UID) {
return true;
}
if (mSettingsStore.getBackgroundThrottlePackageWhitelist().contains(
callerIdentity.mPackageName)) {
return true;
}
return isProviderPackage(callerIdentity.mPackageName);
}
@GuardedBy("mLock")
private boolean isSettingsExemptLocked(UpdateRecord record) {
if (!record.mRealRequest.isLocationSettingsIgnored()) {
return false;
}
if (mSettingsStore.getIgnoreSettingsPackageWhitelist().contains(
record.mReceiver.mCallerIdentity.mPackageName)) {
return true;
}
return isProviderPackage(record.mReceiver.mCallerIdentity.mPackageName);
}
private class UpdateRecord {
final String mProvider;
private final LocationRequest mRealRequest; // original request from client
LocationRequest mRequest; // possibly throttled version of the request
private final Receiver mReceiver;
private boolean mIsForegroundUid;
private Location mLastFixBroadcast;
private Throwable mStackTrace; // for debugging only
private long mExpirationRealtimeMs;
/**
* Note: must be constructed with lock held.
*/
private UpdateRecord(String provider, LocationRequest request, Receiver receiver) {
mExpirationRealtimeMs = request.getExpirationRealtimeMs(SystemClock.elapsedRealtime());
mProvider = provider;
mRealRequest = request;
mRequest = request;
mReceiver = receiver;
mIsForegroundUid =
LocationManagerServiceUtils.isImportanceForeground(
mActivityManager.getPackageImportance(
mReceiver.mCallerIdentity.mPackageName));
if (D && receiver.mCallerIdentity.mPid == Process.myPid()) {
mStackTrace = new Throwable();
}
ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
if (records == null) {
records = new ArrayList<>();
mRecordsByProvider.put(provider, records);
}
if (!records.contains(this)) {
records.add(this);
}
// Update statistics for historical location requests by package/provider
mRequestStatistics.startRequesting(
mReceiver.mCallerIdentity.mPackageName, provider, request.getInterval(),
mIsForegroundUid);
}
/**
* Method to be called when record changes foreground/background
*/
private void updateForeground(boolean isForeground) {
mIsForegroundUid = isForeground;
mRequestStatistics.updateForeground(
mReceiver.mCallerIdentity.mPackageName, mProvider, isForeground);
}
/**
* Method to be called when a record will no longer be used.
*/
private void disposeLocked(boolean removeReceiver) {
String packageName = mReceiver.mCallerIdentity.mPackageName;
mRequestStatistics.stopRequesting(packageName, mProvider);
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
packageName,
mRealRequest,
mReceiver.isListener(),
mReceiver.isPendingIntent(),
/* geofence= */ null,
mActivityManager.getPackageImportance(packageName));
// remove from mRecordsByProvider
ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider);
if (globalRecords != null) {
globalRecords.remove(this);
}
if (!removeReceiver) return; // the caller will handle the rest
// remove from Receiver#mUpdateRecords
HashMap<String, UpdateRecord> receiverRecords = mReceiver.mUpdateRecords;
receiverRecords.remove(this.mProvider);
// and also remove the Receiver if it has no more update records
if (receiverRecords.size() == 0) {
removeUpdatesLocked(mReceiver);
}
}
@Override
public String toString() {
StringBuilder b = new StringBuilder("UpdateRecord[");
b.append(mProvider).append(" ");
b.append(mReceiver.mCallerIdentity.mPackageName);
b.append("(").append(mReceiver.mCallerIdentity.mUid);
if (mIsForegroundUid) {
b.append(" foreground");
} else {
b.append(" background");
}
b.append(") ");
b.append(mRealRequest).append(" ").append(mReceiver.mWorkSource);
if (mStackTrace != null) {
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
mStackTrace.printStackTrace(new PrintStream(tmp));
b.append("\n\n").append(tmp.toString()).append("\n");
}
b.append("]");
return b.toString();
}
}
@GuardedBy("mLock")
private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
String packageName, @Nullable String featureId, WorkSource workSource,
boolean hideFromAppOps, @NonNull String listenerIdentifier) {
IBinder binder = listener.asBinder();
Receiver receiver = mReceivers.get(binder);
if (receiver == null) {
receiver = new Receiver(listener, null, pid, uid, packageName, featureId, workSource,
hideFromAppOps, listenerIdentifier);
if (!receiver.linkToListenerDeathNotificationLocked(
receiver.getListener().asBinder())) {
return null;
}
mReceivers.put(binder, receiver);
}
return receiver;
}
@GuardedBy("mLock")
private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
@Nullable String featureId, WorkSource workSource, boolean hideFromAppOps,
@NonNull String listenerIdentifier) {
Receiver receiver = mReceivers.get(intent);
if (receiver == null) {
receiver = new Receiver(null, intent, pid, uid, packageName, featureId, workSource,
hideFromAppOps, listenerIdentifier);
mReceivers.put(intent, receiver);
}
return receiver;
}
/**
* Creates a LocationRequest based upon the supplied LocationRequest that to meets resolution
* and consistency requirements.
*
* @param request the LocationRequest from which to create a sanitized version
* @return a version of request that meets the given resolution and consistency requirements
* @hide
*/
private LocationRequest createSanitizedRequest(LocationRequest request, int resolutionLevel,
boolean callerHasLocationHardwarePermission) {
LocationRequest sanitizedRequest = new LocationRequest(request);
if (!callerHasLocationHardwarePermission) {
// allow setting low power mode only for callers with location hardware permission
sanitizedRequest.setLowPowerMode(false);
}
if (resolutionLevel < RESOLUTION_LEVEL_FINE) {
switch (sanitizedRequest.getQuality()) {
case LocationRequest.ACCURACY_FINE:
sanitizedRequest.setQuality(LocationRequest.ACCURACY_BLOCK);
break;
case LocationRequest.POWER_HIGH:
sanitizedRequest.setQuality(LocationRequest.POWER_LOW);
break;
}
// throttle
if (sanitizedRequest.getInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
sanitizedRequest.setInterval(LocationFudger.FASTEST_INTERVAL_MS);
}
if (sanitizedRequest.getFastestInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
sanitizedRequest.setFastestInterval(LocationFudger.FASTEST_INTERVAL_MS);
}
}
// make getFastestInterval() the minimum of interval and fastest interval
if (sanitizedRequest.getFastestInterval() > sanitizedRequest.getInterval()) {
sanitizedRequest.setFastestInterval(request.getInterval());
}
return sanitizedRequest;
}
private void checkPackageName(String packageName) {
if (packageName == null) {
throw new SecurityException("invalid package name: " + null);
}
int uid = Binder.getCallingUid();
String[] packages = mPackageManager.getPackagesForUid(uid);
if (packages == null) {
throw new SecurityException("invalid UID " + uid);
}
for (String pkg : packages) {
if (packageName.equals(pkg)) return;
}
throw new SecurityException("invalid package name: " + packageName);
}
@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
PendingIntent intent, String packageName, String featureId,
String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
synchronized (mLock) {
if (request == null) request = DEFAULT_LOCATION_REQUEST;
checkPackageName(packageName);
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
request.getProvider());
WorkSource workSource = request.getWorkSource();
if (workSource != null && !workSource.isEmpty()) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.UPDATE_DEVICE_STATS, null);
}
boolean hideFromAppOps = request.getHideFromAppOps();
if (hideFromAppOps) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, null);
}
if (request.isLocationSettingsIgnored()) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.WRITE_SECURE_SETTINGS, null);
}
boolean callerHasLocationHardwarePermission =
mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
== PERMISSION_GRANTED;
LocationRequest sanitizedRequest = createSanitizedRequest(request,
allowedResolutionLevel,
callerHasLocationHardwarePermission);
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
long identity = Binder.clearCallingIdentity();
try {
// We don't check for MODE_IGNORED here; we will do that when we go to deliver
// a location.
checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
if (intent == null && listener == null) {
throw new IllegalArgumentException("need either listener or intent");
} else if (intent != null && listener != null) {
throw new IllegalArgumentException(
"cannot register both listener and intent");
}
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_STARTED,
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
packageName, request, listener != null, intent != null,
/* geofence= */ null,
mActivityManager.getPackageImportance(packageName));
Receiver receiver;
if (intent != null) {
receiver = getReceiverLocked(intent, pid, uid, packageName, featureId,
workSource, hideFromAppOps, listenerIdentifier);
} else {
receiver = getReceiverLocked(listener, pid, uid, packageName, featureId,
workSource, hideFromAppOps, listenerIdentifier);
}
requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@GuardedBy("mLock")
private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver,
int uid, String packageName) {
// Figure out the provider. Either its explicitly request (legacy use cases), or
// use the fused provider
if (request == null) request = DEFAULT_LOCATION_REQUEST;
String name = request.getProvider();
if (name == null) {
throw new IllegalArgumentException("provider name must not be null");
}
LocationProviderManager manager = getLocationProviderManager(name);
if (manager == null) {
throw new IllegalArgumentException("provider doesn't exist: " + name);
}
UpdateRecord record = new UpdateRecord(name, request, receiver);
if (D) {
Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
+ " " + name + " " + request + " from " + packageName + "(" + uid + " "
+ (record.mIsForegroundUid ? "foreground" : "background")
+ (isThrottlingExemptLocked(receiver.mCallerIdentity)
? " [whitelisted]" : "") + ")");
}
UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
if (oldRecord != null) {
oldRecord.disposeLocked(false);
}
if (!manager.isUseable() && !isSettingsExemptLocked(record)) {
// Notify the listener that updates are currently disabled - but only if the request
// does not ignore location settings
receiver.callProviderEnabledLocked(name, false);
}
applyRequirementsLocked(name);
// Update the monitoring here just in case multiple location requests were added to the
// same receiver (this request may be high power and the initial might not have been).
receiver.updateMonitoring(true);
}
@Override
public void removeUpdates(ILocationListener listener, PendingIntent intent,
String packageName) {
checkPackageName(packageName);
int pid = Binder.getCallingPid();
int uid = Binder.getCallingUid();
if (intent == null && listener == null) {
throw new IllegalArgumentException("need either listener or intent");
} else if (intent != null && listener != null) {
throw new IllegalArgumentException("cannot register both listener and intent");
}
synchronized (mLock) {
Receiver receiver;
if (intent != null) {
receiver = getReceiverLocked(intent, pid, uid, packageName, null, null, false, "");
} else {
receiver = getReceiverLocked(listener, pid, uid, packageName, null, null, false,
"");
}
long identity = Binder.clearCallingIdentity();
try {
removeUpdatesLocked(receiver);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@GuardedBy("mLock")
private void removeUpdatesLocked(Receiver receiver) {
if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver)));
if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) {
receiver.unlinkFromListenerDeathNotificationLocked(
receiver.getListener().asBinder());
receiver.clearPendingBroadcastsLocked();
}
receiver.updateMonitoring(false);
// Record which providers were associated with this listener
HashSet<String> providers = new HashSet<>();
HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords;
if (oldRecords != null) {
// Call dispose() on the obsolete update records.
for (UpdateRecord record : oldRecords.values()) {
// Update statistics for historical location requests by package/provider
record.disposeLocked(false);
}
// Accumulate providers
providers.addAll(oldRecords.keySet());
}
// update provider
for (String provider : providers) {
applyRequirementsLocked(provider);
}
}
@Override
public Location getLastLocation(LocationRequest r, String packageName, String featureId) {
synchronized (mLock) {
LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
checkPackageName(packageName);
checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
request.getProvider());
// no need to sanitize this request, as only the provider name is used
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
if (mSettingsStore.isLocationPackageBlacklisted(UserHandle.getUserId(uid),
packageName)) {
if (D) {
Log.d(TAG, "not returning last loc for blacklisted app: "
+ packageName);
}
return null;
}
// Figure out the provider. Either its explicitly request (deprecated API's),
// or use the fused provider
String name = request.getProvider();
if (name == null) name = LocationManager.FUSED_PROVIDER;
LocationProviderManager manager = getLocationProviderManager(name);
if (manager == null) return null;
// only the current user or location providers may get location this way
if (!mUserInfoStore.isCurrentUserOrProfile(UserHandle.getUserId(uid))
&& !isProviderPackage(packageName)) {
return null;
}
if (!manager.isUseable()) {
return null;
}
Location location;
if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
// Make sure that an app with coarse permissions can't get frequent location
// updates by calling LocationManager.getLastKnownLocation repeatedly.
location = mLastLocationCoarseInterval.get(name);
} else {
location = mLastLocation.get(name);
}
if (location == null) {
return null;
}
// Don't return stale location to apps with foreground-only location permission.
String op = resolutionLevelToOpStr(allowedResolutionLevel);
long locationAgeMs = TimeUnit.NANOSECONDS.toMillis(
SystemClock.elapsedRealtime() - location.getElapsedRealtimeNanos());
if (locationAgeMs > mSettingsStore.getMaxLastLocationAgeMs()
&& (mAppOps.unsafeCheckOp(op, uid, packageName)
== AppOpsManager.MODE_FOREGROUND)) {
return null;
}
Location lastLocation = null;
if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
Location noGPSLocation = location.getExtraLocation(
Location.EXTRA_NO_GPS_LOCATION);
if (noGPSLocation != null) {
lastLocation = new Location(mLocationFudger.getOrCreate(noGPSLocation));
}
} else {
lastLocation = new Location(location);
}
// Don't report location access if there is no last location to deliver.
if (lastLocation != null) {
if (!reportLocationAccessNoThrow(pid, uid, packageName, featureId,
allowedResolutionLevel, null)) {
if (D) {
Log.d(TAG, "not returning last loc for no op app: " + packageName);
}
lastLocation = null;
}
}
return lastLocation;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@Override
public boolean getCurrentLocation(LocationRequest locationRequest,
ICancellationSignal remoteCancellationSignal, ILocationListener listener,
String packageName, String featureId, String listenerIdentifier) {
// side effect of validating locationRequest and packageName
Location lastLocation = getLastLocation(locationRequest, packageName, featureId);
if (lastLocation != null) {
long locationAgeMs = TimeUnit.NANOSECONDS.toMillis(
SystemClock.elapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos());
long identity = Binder.clearCallingIdentity();
try {
if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
try {
listener.onLocationChanged(lastLocation);
return true;
} catch (RemoteException e) {
Log.w(TAG, e);
return false;
}
}
// packageName already validated by getLastLocation() call above
boolean foreground = LocationManagerServiceUtils.isImportanceForeground(
mActivityManager.getPackageImportance(packageName));
if (!foreground) {
if (locationAgeMs < mSettingsStore.getBackgroundThrottleIntervalMs()) {
// not allowed to request new locations, so we can't return anything
return false;
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
requestLocationUpdates(locationRequest, listener, null, packageName, featureId,
listenerIdentifier);
CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
remoteCancellationSignal);
if (cancellationSignal != null) {
cancellationSignal.setOnCancelListener(
() -> removeUpdates(listener, null, packageName));
}
return true;
}
@Override
public LocationTime getGnssTimeMillis() {
synchronized (mLock) {
Location location = mLastLocation.get(LocationManager.GPS_PROVIDER);
if (location == null) {
return null;
}
long currentNanos = SystemClock.elapsedRealtimeNanos();
long deltaMs = (currentNanos - location.getElapsedRealtimeNanos()) / 1000000L;
return new LocationTime(location.getTime() + deltaMs, currentNanos);
}
}
@Override
public boolean injectLocation(Location location) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
"Location Hardware permission not granted to inject location");
mContext.enforceCallingPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
"Access Fine Location permission not granted to inject Location");
synchronized (mLock) {
LocationProviderManager manager = getLocationProviderManager(location.getProvider());
if (manager == null || !manager.isUseable()) {
return false;
}
// NOTE: If last location is already available, location is not injected. If
// provider's normal source (like a GPS chipset) have already provided an output
// there is no need to inject this location.
if (mLastLocation.get(manager.getName()) != null) {
return false;
}
updateLastLocationLocked(location, manager.getName());
return true;
}
}
@Override
public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent,
String packageName, String featureId, String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
if (request == null) request = DEFAULT_LOCATION_REQUEST;
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
if (intent == null) {
throw new IllegalArgumentException("invalid pending intent: " + null);
}
checkPackageName(packageName);
synchronized (mLock) {
checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
request.getProvider());
}
// Require that caller can manage given document
boolean callerHasLocationHardwarePermission =
mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
== PERMISSION_GRANTED;
LocationRequest sanitizedRequest = createSanitizedRequest(request,
allowedResolutionLevel,
callerHasLocationHardwarePermission);
if (D) {
Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent);
}
// geo-fence manager uses the public location API, need to clear identity
int uid = Binder.getCallingUid();
if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
// temporary measure until geofences work for secondary users
Log.w(TAG, "proximity alerts are currently available only to the primary user");
return;
}
long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_STARTED,
LocationStatsEnums.API_REQUEST_GEOFENCE,
packageName,
request,
/* hasListener= */ false,
true,
geofence,
mActivityManager.getPackageImportance(packageName));
}
mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
uid, packageName, featureId, listenerIdentifier);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) {
if (intent == null) {
throw new IllegalArgumentException("invalid pending intent: " + null);
}
checkPackageName(packageName);
if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent);
// geo-fence manager uses the public location API, need to clear identity
long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_REQUEST_GEOFENCE,
packageName,
/* LocationRequest= */ null,
/* hasListener= */ false,
true,
geofence,
mActivityManager.getPackageImportance(packageName));
}
mGeofenceManager.removeFence(geofence, intent);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
String featureId) {
return mGnssManagerService != null && mGnssManagerService.registerGnssStatusCallback(
listener, packageName, featureId);
}
@Override
public void unregisterGnssStatusCallback(IGnssStatusListener listener) {
if (mGnssManagerService != null) mGnssManagerService.unregisterGnssStatusCallback(listener);
}
@Override
public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener,
String packageName, String featureId, String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener(
listener, packageName, featureId, listenerIdentifier);
}
@Override
public void removeGnssMeasurementsListener(IGnssMeasurementsListener listener) {
if (mGnssManagerService != null) {
mGnssManagerService.removeGnssMeasurementsListener(
listener);
}
}
@Override
public void injectGnssMeasurementCorrections(
GnssMeasurementCorrections measurementCorrections, String packageName) {
if (mGnssManagerService != null) {
mGnssManagerService.injectGnssMeasurementCorrections(measurementCorrections,
packageName);
}
}
@Override
public long getGnssCapabilities(String packageName) {
return mGnssManagerService == null ? 0L : mGnssManagerService.getGnssCapabilities(
packageName);
}
@Override
public boolean addGnssNavigationMessageListener(IGnssNavigationMessageListener listener,
String packageName, String featureId, String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
return mGnssManagerService != null && mGnssManagerService.addGnssNavigationMessageListener(
listener, packageName, featureId, listenerIdentifier);
}
@Override
public void removeGnssNavigationMessageListener(IGnssNavigationMessageListener listener) {
if (mGnssManagerService != null) {
mGnssManagerService.removeGnssNavigationMessageListener(
listener);
}
}
@Override
public boolean sendExtraCommand(String providerName, String command, Bundle extras) {
if (providerName == null) {
// throw NullPointerException to remain compatible with previous implementation
throw new NullPointerException();
}
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, null);
synchronized (mLock) {
checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
providerName);
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_STARTED,
LocationStatsEnums.API_SEND_EXTRA_COMMAND,
providerName);
LocationProviderManager manager = getLocationProviderManager(providerName);
if (manager != null) {
manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command,
extras);
}
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_SEND_EXTRA_COMMAND,
providerName);
return true;
}
}
@Override
public boolean sendNiResponse(int notifId, int userResponse) {
return mGnssManagerService != null && mGnssManagerService.sendNiResponse(notifId,
userResponse);
}
@Override
public ProviderProperties getProviderProperties(String providerName) {
LocationProviderManager manager = getLocationProviderManager(providerName);
if (manager == null) {
return null;
}
return manager.getProperties();
}
@Override
public boolean isProviderPackage(String packageName) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
Manifest.permission.READ_DEVICE_CONFIG + " permission required");
for (LocationProviderManager manager : mProviderManagers) {
if (manager.getPackages().contains(packageName)) {
return true;
}
}
return false;
}
@Override
public List<String> getProviderPackages(String providerName) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
Manifest.permission.READ_DEVICE_CONFIG + " permission required");
LocationProviderManager manager = getLocationProviderManager(providerName);
return manager == null ? Collections.emptyList() : new ArrayList<>(manager.getPackages());
}
@Override
public void setExtraLocationControllerPackage(String packageName) {
mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
Manifest.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(Manifest.permission.LOCATION_HARDWARE,
Manifest.permission.LOCATION_HARDWARE + " permission required");
synchronized (mLock) {
mExtraLocationControllerPackageEnabled = enabled;
}
}
@Override
public boolean isExtraLocationControllerPackageEnabled() {
synchronized (mLock) {
return mExtraLocationControllerPackageEnabled
&& (mExtraLocationControllerPackage != null);
}
}
@Override
public boolean isLocationEnabledForUser(int userId) {
// Check INTERACT_ACROSS_USERS permission if userId is not current user id.
if (UserHandle.getCallingUserId() != userId) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS,
"Requires INTERACT_ACROSS_USERS permission");
}
long identity = Binder.clearCallingIdentity();
try {
return mSettingsStore.isLocationEnabled(userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public boolean isProviderEnabledForUser(String providerName, int userId) {
// Check INTERACT_ACROSS_USERS permission if userId is not current user id.
if (UserHandle.getCallingUserId() != userId) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS,
"Requires INTERACT_ACROSS_USERS permission");
}
// Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
// so we discourage its use
if (FUSED_PROVIDER.equals(providerName)) return false;
synchronized (mLock) {
LocationProviderManager manager = getLocationProviderManager(providerName);
return manager != null && manager.isUseable(userId);
}
}
@GuardedBy("mLock")
private static boolean shouldBroadcastSafeLocked(
Location loc, Location lastLoc, UpdateRecord record, long now) {
// Always broadcast the first update
if (lastLoc == null) {
return true;
}
// Check whether sufficient time has passed
long minTime = record.mRealRequest.getFastestInterval();
long deltaMs = TimeUnit.NANOSECONDS.toMillis(
loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos());
if (deltaMs < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) {
return false;
}
// Check whether sufficient distance has been traveled
double minDistance = record.mRealRequest.getSmallestDisplacement();
if (minDistance > 0.0) {
if (loc.distanceTo(lastLoc) <= minDistance) {
return false;
}
}
// Check whether sufficient number of udpates is left
if (record.mRealRequest.getNumUpdates() <= 0) {
return false;
}
// Check whether the expiry date has passed
return record.mExpirationRealtimeMs >= now;
}
@GuardedBy("mLock")
private void handleLocationChangedLocked(Location location, LocationProviderManager manager) {
if (!mProviderManagers.contains(manager)) {
Log.w(TAG, "received location from unknown provider: " + manager.getName());
return;
}
if (!location.isComplete()) {
Log.w(TAG, "dropping incomplete location from " + manager.getName() + " provider: "
+ location);
return;
}
// notify passive provider
if (manager != mPassiveManager) {
mPassiveManager.updateLocation(new Location(location));
}
if (D) Log.d(TAG, "incoming location: " + location);
long now = SystemClock.elapsedRealtime();
// only update last location for locations that come from useable providers
if (manager.isUseable()) {
updateLastLocationLocked(location, manager.getName());
}
// Update last known coarse interval location if enough time has passed.
Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(
manager.getName());
if (lastLocationCoarseInterval == null) {
lastLocationCoarseInterval = new Location(location);
if (manager.isUseable()) {
mLastLocationCoarseInterval.put(manager.getName(), lastLocationCoarseInterval);
}
}
long timeDeltaMs = TimeUnit.NANOSECONDS.toMillis(location.getElapsedRealtimeNanos()
- lastLocationCoarseInterval.getElapsedRealtimeNanos());
if (timeDeltaMs > LocationFudger.FASTEST_INTERVAL_MS) {
lastLocationCoarseInterval.set(location);
}
// Don't ever return a coarse location that is more recent than the allowed update
// interval (i.e. don't allow an app to keep registering and unregistering for
// location updates to overcome the minimum interval).
Location noGPSLocation =
lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
// Skip if there are no UpdateRecords for this provider.
ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
if (records == null || records.size() == 0) return;
// Fetch coarse location
Location coarseLocation = null;
if (noGPSLocation != null) {
coarseLocation = mLocationFudger.getOrCreate(noGPSLocation);
}
ArrayList<Receiver> deadReceivers = null;
ArrayList<UpdateRecord> deadUpdateRecords = null;
// Broadcast location to all listeners
for (UpdateRecord r : records) {
Receiver receiver = r.mReceiver;
boolean receiverDead = false;
if (!manager.isUseable() && !isSettingsExemptLocked(r)) {
continue;
}
int receiverUserId = UserHandle.getUserId(receiver.mCallerIdentity.mUid);
if (!mUserInfoStore.isCurrentUserOrProfile(receiverUserId)
&& !isProviderPackage(receiver.mCallerIdentity.mPackageName)) {
if (D) {
Log.d(TAG, "skipping loc update for background user " + receiverUserId +
" (app: " + receiver.mCallerIdentity.mPackageName + ")");
}
continue;
}
if (mSettingsStore.isLocationPackageBlacklisted(receiverUserId,
receiver.mCallerIdentity.mPackageName)) {
if (D) {
Log.d(TAG, "skipping loc update for blacklisted app: " +
receiver.mCallerIdentity.mPackageName);
}
continue;
}
Location notifyLocation;
if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
notifyLocation = coarseLocation; // use coarse location
} else {
notifyLocation = location; // use fine location
}
if (notifyLocation != null) {
Location lastLoc = r.mLastFixBroadcast;
if ((lastLoc == null)
|| shouldBroadcastSafeLocked(notifyLocation, lastLoc, r, now)) {
if (lastLoc == null) {
lastLoc = new Location(notifyLocation);
r.mLastFixBroadcast = lastLoc;
} else {
lastLoc.set(notifyLocation);
}
// Report location access before delivering location to the client. This will
// note location delivery to appOps, so it should be called only when a
// location is really being delivered to the client.
if (!reportLocationAccessNoThrow(
receiver.mCallerIdentity.mPid,
receiver.mCallerIdentity.mUid,
receiver.mCallerIdentity.mPackageName,
receiver.mCallerIdentity.mFeatureId,
receiver.mAllowedResolutionLevel,
"Location sent to " + receiver.mCallerIdentity.mListenerIdentifier)) {
if (D) {
Log.d(TAG, "skipping loc update for no op app: "
+ receiver.mCallerIdentity.mPackageName);
}
continue;
}
if (!receiver.callLocationChangedLocked(notifyLocation)) {
Slog.w(TAG, "RemoteException calling onLocationChanged on "
+ receiver);
receiverDead = true;
}
r.mRealRequest.decrementNumUpdates();
}
}
// track expired records
if (r.mRealRequest.getNumUpdates() <= 0 || r.mExpirationRealtimeMs < now) {
// notify the client it can remove this listener
r.mReceiver.callRemovedLocked();
if (deadUpdateRecords == null) {
deadUpdateRecords = new ArrayList<>();
}
deadUpdateRecords.add(r);
}
// track dead receivers
if (receiverDead) {
if (deadReceivers == null) {
deadReceivers = new ArrayList<>();
}
if (!deadReceivers.contains(receiver)) {
deadReceivers.add(receiver);
}
}
}
// remove dead records and receivers outside the loop
if (deadReceivers != null) {
for (Receiver receiver : deadReceivers) {
removeUpdatesLocked(receiver);
}
}
if (deadUpdateRecords != null) {
for (UpdateRecord r : deadUpdateRecords) {
r.disposeLocked(true);
}
applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
private void updateLastLocationLocked(Location location, String provider) {
Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
Location lastNoGPSLocation;
Location lastLocation = mLastLocation.get(provider);
if (lastLocation == null) {
lastLocation = new Location(provider);
mLastLocation.put(provider, lastLocation);
} else {
lastNoGPSLocation = lastLocation.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
if (noGPSLocation == null && lastNoGPSLocation != null) {
// New location has no no-GPS location: adopt last no-GPS location. This is set
// directly into location because we do not want to notify COARSE clients.
location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation);
}
}
lastLocation.set(location);
}
// Geocoder
@Override
public boolean geocoderIsPresent() {
return mGeocodeProvider != null;
}
@Override
public String getFromLocation(double latitude, double longitude, int maxResults,
GeocoderParams params, List<Address> addrs) {
if (mGeocodeProvider != null) {
return mGeocodeProvider.getFromLocation(latitude, longitude, maxResults,
params, addrs);
}
return null;
}
@Override
public String getFromLocationName(String locationName,
double lowerLeftLatitude, double lowerLeftLongitude,
double upperRightLatitude, double upperRightLongitude, int maxResults,
GeocoderParams params, List<Address> addrs) {
if (mGeocodeProvider != null) {
return mGeocodeProvider.getFromLocationName(locationName, lowerLeftLatitude,
lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
maxResults, params, addrs);
}
return null;
}
// Mock Providers
@Override
public void addTestProvider(String provider, ProviderProperties properties,
String packageName) {
if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
synchronized (mLock) {
LocationProviderManager manager = getLocationProviderManager(provider);
if (manager == null) {
manager = new LocationProviderManager(provider);
mProviderManagers.add(manager);
}
manager.setMockProvider(new MockProvider(mContext, properties));
}
}
@Override
public void removeTestProvider(String provider, String packageName) {
if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
synchronized (mLock) {
LocationProviderManager manager = getLocationProviderManager(provider);
if (manager == null) {
return;
}
manager.setMockProvider(null);
if (!manager.hasProvider()) {
mProviderManagers.remove(manager);
mLastLocation.remove(manager.getName());
mLastLocationCoarseInterval.remove(manager.getName());
}
}
}
@Override
public void setTestProviderLocation(String provider, Location location, String packageName) {
if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
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) {
if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
LocationProviderManager manager = getLocationProviderManager(provider);
if (manager == null) {
throw new IllegalArgumentException("provider doesn't exist: " + provider);
}
manager.setMockProviderEnabled(enabled);
}
@Override
@NonNull
public List<LocationRequest> getTestProviderCurrentRequests(String provider,
String packageName) {
if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
!= AppOpsManager.MODE_ALLOWED) {
return Collections.emptyList();
}
LocationProviderManager manager = getLocationProviderManager(provider);
if (manager == null) {
throw new IllegalArgumentException("provider doesn't exist: " + provider);
}
return manager.getMockProviderRequests();
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
return;
}
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
synchronized (mLock) {
if (mGnssManagerService != null && args.length > 0 && args[0].equals("--gnssmetrics")) {
mGnssManagerService.dump(fd, pw, args);
return;
}
ipw.println("Location Manager State:");
ipw.increaseIndent();
ipw.print("Current System Time: "
+ TimeUtils.logTimeOfDay(System.currentTimeMillis()));
ipw.println(", Current Elapsed Time: "
+ TimeUtils.formatDuration(SystemClock.elapsedRealtime()));
ipw.println("User Info:");
ipw.increaseIndent();
mUserInfoStore.dump(fd, ipw, args);
ipw.decreaseIndent();
ipw.println("Location Settings:");
ipw.increaseIndent();
mSettingsStore.dump(fd, ipw, args);
ipw.decreaseIndent();
ipw.println("Battery Saver Location Mode: "
+ locationPowerSaveModeToString(mBatterySaverMode));
ipw.println("Location Listeners:");
ipw.increaseIndent();
for (Receiver receiver : mReceivers.values()) {
ipw.println(receiver);
}
ipw.decreaseIndent();
ipw.println("Active Records by Provider:");
ipw.increaseIndent();
for (Map.Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
ipw.println(entry.getKey() + ":");
ipw.increaseIndent();
for (UpdateRecord record : entry.getValue()) {
ipw.println(record);
}
ipw.decreaseIndent();
}
ipw.decreaseIndent();
ipw.println("Historical Records by Provider:");
ipw.increaseIndent();
for (Map.Entry<PackageProviderKey, PackageStatistics> entry
: mRequestStatistics.statistics.entrySet()) {
PackageProviderKey key = entry.getKey();
ipw.println(key.packageName + ": " + key.providerName + ": " + entry.getValue());
}
ipw.decreaseIndent();
mRequestStatistics.history.dump(ipw);
ipw.println("Last Known Locations:");
ipw.increaseIndent();
for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) {
ipw.println(entry.getKey() + ": " + entry.getValue());
}
ipw.decreaseIndent();
ipw.println("Last Known Coarse Locations:");
ipw.increaseIndent();
for (Map.Entry<String, Location> entry : mLastLocationCoarseInterval.entrySet()) {
ipw.println(entry.getKey() + ": " + entry.getValue());
}
ipw.decreaseIndent();
if (mGeofenceManager != null) {
ipw.println("Geofences:");
ipw.increaseIndent();
mGeofenceManager.dump(ipw);
ipw.decreaseIndent();
}
if (mExtraLocationControllerPackage != null) {
ipw.println("Location Controller Extra Package: " + mExtraLocationControllerPackage
+ (mExtraLocationControllerPackageEnabled ? " [enabled]" : "[disabled]"));
}
if (mLocationFudger != null) {
ipw.println("Location Fudger:");
ipw.increaseIndent();
mLocationFudger.dump(fd, ipw, args);
ipw.decreaseIndent();
}
ipw.println("Location Providers:");
ipw.increaseIndent();
for (LocationProviderManager manager : mProviderManagers) {
manager.dump(fd, ipw, args);
}
ipw.decreaseIndent();
if (mGnssManagerService != null) {
ipw.println("GNSS:");
ipw.increaseIndent();
mGnssManagerService.dump(fd, ipw, args);
ipw.decreaseIndent();
}
}
}
}