blob: 8955c288391f85f265bd0be7d3f60fb4abf6d7e5 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.location.provider;
import static android.app.compat.CompatChanges.isChangeEnabled;
import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS;
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationManager.KEY_FLUSH_COMPLETE;
import static android.location.LocationManager.KEY_LOCATIONS;
import static android.location.LocationManager.KEY_LOCATION_CHANGED;
import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
import static android.location.LocationManager.PASSIVE_PROVIDER;
import static android.os.IPowerManager.LOCATION_MODE_NO_CHANGE;
import static android.os.PowerExemptionManager.REASON_LOCATION_PROVIDER;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static java.lang.Math.max;
import static java.lang.Math.min;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.AlarmManager.OnAlarmListener;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.location.ILocationCallback;
import android.location.ILocationListener;
import android.location.LastLocationRequest;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationManagerInternal;
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.IRemoteCallback;
import android.os.PowerManager;
import android.os.PowerManager.LocationPowerSaveMode;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.stats.location.LocationStatsEnums;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.location.LocationPermissions;
import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.fudger.LocationFudger;
import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.AppForegroundHelper.AppForegroundListener;
import com.android.server.location.injector.AppOpsHelper;
import com.android.server.location.injector.Injector;
import com.android.server.location.injector.LocationAttributionHelper;
import com.android.server.location.injector.LocationPermissionsHelper;
import com.android.server.location.injector.LocationPermissionsHelper.LocationPermissionsListener;
import com.android.server.location.injector.LocationPowerSaveModeHelper;
import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
import com.android.server.location.injector.LocationUsageLogger;
import com.android.server.location.injector.ScreenInteractiveHelper;
import com.android.server.location.injector.ScreenInteractiveHelper.ScreenInteractiveChangedListener;
import com.android.server.location.injector.SettingsHelper;
import com.android.server.location.injector.SettingsHelper.GlobalSettingChangedListener;
import com.android.server.location.injector.SettingsHelper.UserSettingChangedListener;
import com.android.server.location.injector.UserInfoHelper;
import com.android.server.location.injector.UserInfoHelper.UserListener;
import com.android.server.location.listeners.ListenerMultiplexer;
import com.android.server.location.listeners.RemoteListenerRegistration;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
/**
* Manages all aspects of a single location provider.
*/
public class LocationProviderManager extends
ListenerMultiplexer<Object, LocationProviderManager.LocationTransport,
LocationProviderManager.Registration, ProviderRequest> implements
AbstractLocationProvider.Listener {
private static final String WAKELOCK_TAG = "*location*";
private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000;
// duration PI location clients are put on the allowlist to start a fg service
private static final long TEMPORARY_APP_ALLOWLIST_DURATION_MS = 10 * 1000;
// fastest interval at which clients may receive coarse locations
private static final long MIN_COARSE_INTERVAL_MS = 10 * 60 * 1000;
// max interval to be considered "high power" request
private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
// max age of a location before it is no longer considered "current"
private static final long MAX_CURRENT_LOCATION_AGE_MS = 30 * 1000;
// max timeout allowed for getting the current location
private static final long MAX_GET_CURRENT_LOCATION_TIMEOUT_MS = 30 * 1000;
// max jitter allowed for min update interval as a percentage of the interval
private static final float FASTEST_INTERVAL_JITTER_PERCENTAGE = .10f;
// max absolute jitter allowed for min update interval evaluation
private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 30 * 1000;
// minimum amount of request delay in order to respect the delay, below this value the request
// will just be scheduled immediately
private static final long MIN_REQUEST_DELAY_MS = 30 * 1000;
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_STARTED, STATE_STOPPING, STATE_STOPPED})
private @interface State {}
private static final int STATE_STARTED = 0;
private static final int STATE_STOPPING = 1;
private static final int STATE_STOPPED = 2;
public interface StateChangedListener {
void onStateChanged(String provider, AbstractLocationProvider.State oldState,
AbstractLocationProvider.State newState);
}
protected interface LocationTransport {
void deliverOnLocationChanged(LocationResult locationResult,
@Nullable Runnable onCompleteCallback) throws Exception;
void deliverOnFlushComplete(int requestCode) throws Exception;
}
protected interface ProviderTransport {
void deliverOnProviderEnabledChanged(String provider, boolean enabled) throws Exception;
}
protected static final class LocationListenerTransport implements LocationTransport,
ProviderTransport {
private final ILocationListener mListener;
protected LocationListenerTransport(ILocationListener listener) {
mListener = Objects.requireNonNull(listener);
}
@Override
public void deliverOnLocationChanged(LocationResult locationResult,
@Nullable Runnable onCompleteCallback) throws RemoteException {
mListener.onLocationChanged(locationResult.asList(),
SingleUseCallback.wrap(onCompleteCallback));
}
@Override
public void deliverOnFlushComplete(int requestCode) throws RemoteException {
mListener.onFlushComplete(requestCode);
}
@Override
public void deliverOnProviderEnabledChanged(String provider, boolean enabled)
throws RemoteException {
mListener.onProviderEnabledChanged(provider, enabled);
}
}
protected static final class LocationPendingIntentTransport implements LocationTransport,
ProviderTransport {
private final Context mContext;
private final PendingIntent mPendingIntent;
public LocationPendingIntentTransport(Context context, PendingIntent pendingIntent) {
mContext = context;
mPendingIntent = pendingIntent;
}
@Override
public void deliverOnLocationChanged(LocationResult locationResult,
@Nullable Runnable onCompleteCallback)
throws PendingIntent.CanceledException {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setDontSendToRestrictedApps(true);
// allows apps to start a fg service in response to a location PI
options.setTemporaryAppAllowlist(TEMPORARY_APP_ALLOWLIST_DURATION_MS,
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
REASON_LOCATION_PROVIDER,
"");
Intent intent = new Intent().putExtra(KEY_LOCATION_CHANGED,
locationResult.getLastLocation());
if (locationResult.size() > 1) {
intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0]));
}
mPendingIntent.send(
mContext,
0,
intent,
onCompleteCallback != null ? (pI, i, rC, rD, rE) -> onCompleteCallback.run()
: null,
null,
null,
options.toBundle());
}
@Override
public void deliverOnFlushComplete(int requestCode) throws PendingIntent.CanceledException {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setDontSendToRestrictedApps(true);
mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_FLUSH_COMPLETE, requestCode),
null, null, null, options.toBundle());
}
@Override
public void deliverOnProviderEnabledChanged(String provider, boolean enabled)
throws PendingIntent.CanceledException {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setDontSendToRestrictedApps(true);
mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_PROVIDER_ENABLED, enabled),
null, null, null, options.toBundle());
}
}
protected static final class GetCurrentLocationTransport implements LocationTransport {
private final ILocationCallback mCallback;
protected GetCurrentLocationTransport(ILocationCallback callback) {
mCallback = Objects.requireNonNull(callback);
}
@Override
public void deliverOnLocationChanged(@Nullable LocationResult locationResult,
@Nullable Runnable onCompleteCallback)
throws RemoteException {
// ILocationCallback doesn't currently support completion callbacks
Preconditions.checkState(onCompleteCallback == null);
if (locationResult != null) {
mCallback.onLocation(locationResult.getLastLocation());
} else {
mCallback.onLocation(null);
}
}
@Override
public void deliverOnFlushComplete(int requestCode) {}
}
protected abstract class Registration extends RemoteListenerRegistration<LocationRequest,
LocationTransport> {
private final @PermissionLevel int mPermissionLevel;
// we cache these values because checking/calculating on the fly is more expensive
private boolean mPermitted;
private boolean mForeground;
private LocationRequest mProviderLocationRequest;
private boolean mIsUsingHighPower;
private @Nullable Location mLastLocation = null;
protected Registration(LocationRequest request, CallerIdentity identity,
LocationTransport transport, @PermissionLevel int permissionLevel) {
super(Objects.requireNonNull(request), identity, transport);
Preconditions.checkArgument(identity.getListenerId() != null);
Preconditions.checkArgument(permissionLevel > PERMISSION_NONE);
Preconditions.checkArgument(!request.getWorkSource().isEmpty());
mPermissionLevel = permissionLevel;
mProviderLocationRequest = request;
}
@GuardedBy("mLock")
@Override
protected final void onRemovableListenerRegister() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
if (D) {
Log.d(TAG, mName + " provider added registration from " + getIdentity() + " -> "
+ getRequest());
}
EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
// initialization order is important as there are ordering dependencies
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
getIdentity());
mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid());
mProviderLocationRequest = calculateProviderLocationRequest();
mIsUsingHighPower = isUsingHighPower();
onProviderListenerRegister();
if (mForeground) {
EVENT_LOG.logProviderClientForeground(mName, getIdentity());
}
}
@GuardedBy("mLock")
@Override
protected final void onRemovableListenerUnregister() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
onProviderListenerUnregister();
EVENT_LOG.logProviderClientUnregistered(mName, getIdentity());
if (D) {
Log.d(TAG, mName + " provider removed registration from " + getIdentity());
}
}
/**
* Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
*/
@GuardedBy("mLock")
protected void onProviderListenerRegister() {}
/**
* Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
*/
@GuardedBy("mLock")
protected void onProviderListenerUnregister() {}
@Override
protected final void onActive() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
EVENT_LOG.logProviderClientActive(mName, getIdentity());
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
}
onHighPowerUsageChanged();
onProviderListenerActive();
}
@Override
protected final void onInactive() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
onHighPowerUsageChanged();
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
}
onProviderListenerInactive();
EVENT_LOG.logProviderClientInactive(mName, getIdentity());
}
/**
* Subclasses may override this instead of {@link #onActive()}.
*/
@GuardedBy("mLock")
protected void onProviderListenerActive() {}
/**
* Subclasses may override this instead of {@link #onInactive()} ()}.
*/
@GuardedBy("mLock")
protected void onProviderListenerInactive() {}
@Override
public final LocationRequest getRequest() {
return mProviderLocationRequest;
}
@GuardedBy("mLock")
final void setLastDeliveredLocation(@Nullable Location location) {
mLastLocation = location;
}
@GuardedBy("mLock")
public final Location getLastDeliveredLocation() {
return mLastLocation;
}
public @PermissionLevel int getPermissionLevel() {
return mPermissionLevel;
}
public final boolean isForeground() {
return mForeground;
}
public final boolean isPermitted() {
return mPermitted;
}
public final void flush(int requestCode) {
// when the flush callback is invoked, we are guaranteed that locations have been
// queued on our executor, so by running the listener callback on the same executor it
// should be guaranteed that those locations will be delivered before the flush callback
mProvider.getController().flush(() -> executeOperation(
listener -> listener.deliverOnFlushComplete(requestCode)));
}
@Override
protected final LocationProviderManager getOwner() {
return LocationProviderManager.this;
}
@GuardedBy("mLock")
final boolean onProviderPropertiesChanged() {
onHighPowerUsageChanged();
return false;
}
@GuardedBy("mLock")
private void onHighPowerUsageChanged() {
boolean isUsingHighPower = isUsingHighPower();
if (isUsingHighPower != mIsUsingHighPower) {
mIsUsingHighPower = isUsingHighPower;
if (!getRequest().isHiddenFromAppOps()) {
if (mIsUsingHighPower) {
mLocationAttributionHelper.reportHighPowerLocationStart(
getIdentity(), getName(), getKey());
} else {
mLocationAttributionHelper.reportHighPowerLocationStop(
getIdentity(), getName(), getKey());
}
}
}
}
@GuardedBy("mLock")
private boolean isUsingHighPower() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
ProviderProperties properties = getProperties();
if (properties == null) {
return false;
}
return isActive()
&& getRequest().getIntervalMillis() < MAX_HIGH_POWER_INTERVAL_MS
&& properties.getPowerUsage() == ProviderProperties.POWER_USAGE_HIGH;
}
@GuardedBy("mLock")
final boolean onLocationPermissionsChanged(String packageName) {
if (getIdentity().getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
return false;
}
@GuardedBy("mLock")
final boolean onLocationPermissionsChanged(int uid) {
if (getIdentity().getUid() == uid) {
return onLocationPermissionsChanged();
}
return false;
}
@GuardedBy("mLock")
private boolean onLocationPermissionsChanged() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
getIdentity());
if (permitted != mPermitted) {
if (D) {
Log.v(TAG, mName + " provider package " + getIdentity().getPackageName()
+ " permitted = " + permitted);
}
mPermitted = permitted;
if (mPermitted) {
EVENT_LOG.logProviderClientPermitted(mName, getIdentity());
} else {
EVENT_LOG.logProviderClientUnpermitted(mName, getIdentity());
}
return true;
}
return false;
}
@GuardedBy("mLock")
final boolean onAdasGnssLocationEnabledChanged(int userId) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
if (getIdentity().getUserId() == userId) {
return onProviderLocationRequestChanged();
}
return false;
}
@GuardedBy("mLock")
final boolean onForegroundChanged(int uid, boolean foreground) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
if (getIdentity().getUid() == uid && foreground != mForeground) {
if (D) {
Log.v(TAG, mName + " provider uid " + uid + " foreground = " + foreground);
}
mForeground = foreground;
if (mForeground) {
EVENT_LOG.logProviderClientForeground(mName, getIdentity());
} else {
EVENT_LOG.logProviderClientBackground(mName, getIdentity());
}
// note that onProviderLocationRequestChanged() is always called
return onProviderLocationRequestChanged()
|| mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
== LOCATION_MODE_FOREGROUND_ONLY;
}
return false;
}
@GuardedBy("mLock")
final boolean onProviderLocationRequestChanged() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
LocationRequest newRequest = calculateProviderLocationRequest();
if (mProviderLocationRequest.equals(newRequest)) {
return false;
}
LocationRequest oldRequest = mProviderLocationRequest;
mProviderLocationRequest = newRequest;
onHighPowerUsageChanged();
updateService();
// if bypass state has changed then the active state may have changed
return oldRequest.isBypass() != newRequest.isBypass();
}
private LocationRequest calculateProviderLocationRequest() {
LocationRequest baseRequest = super.getRequest();
LocationRequest.Builder builder = new LocationRequest.Builder(baseRequest);
if (mPermissionLevel < PERMISSION_FINE) {
builder.setQuality(LocationRequest.QUALITY_LOW_POWER);
if (baseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
builder.setIntervalMillis(MIN_COARSE_INTERVAL_MS);
}
if (baseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
builder.setMinUpdateIntervalMillis(MIN_COARSE_INTERVAL_MS);
}
}
boolean locationSettingsIgnored = baseRequest.isLocationSettingsIgnored();
if (locationSettingsIgnored) {
// if we are not currently allowed use location settings ignored, disable it
if (!mSettingsHelper.getIgnoreSettingsAllowlist().contains(
getIdentity().getPackageName(), getIdentity().getAttributionTag())
&& !mLocationManagerInternal.isProvider(null, getIdentity())) {
locationSettingsIgnored = false;
}
builder.setLocationSettingsIgnored(locationSettingsIgnored);
}
boolean adasGnssBypass = baseRequest.isAdasGnssBypass();
if (adasGnssBypass) {
// if we are not currently allowed use adas gnss bypass, disable it
if (!GPS_PROVIDER.equals(mName)) {
Log.e(TAG, "adas gnss bypass request received in non-gps provider");
adasGnssBypass = false;
} else if (!mLocationSettings.getUserSettings(
getIdentity().getUserId()).isAdasGnssLocationEnabled()) {
adasGnssBypass = false;
}
builder.setAdasGnssBypass(adasGnssBypass);
}
if (!locationSettingsIgnored && !isThrottlingExempt()) {
// throttle in the background
if (!mForeground) {
builder.setIntervalMillis(max(baseRequest.getIntervalMillis(),
mSettingsHelper.getBackgroundThrottleIntervalMs()));
}
}
return builder.build();
}
private boolean isThrottlingExempt() {
if (mSettingsHelper.getBackgroundThrottlePackageWhitelist().contains(
getIdentity().getPackageName())) {
return true;
}
return mLocationManagerInternal.isProvider(null, getIdentity());
}
@GuardedBy("mLock")
abstract @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
LocationResult fineLocationResult);
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(getIdentity());
ArraySet<String> flags = new ArraySet<>(2);
if (!isForeground()) {
flags.add("bg");
}
if (!isPermitted()) {
flags.add("na");
}
if (!flags.isEmpty()) {
builder.append(" ").append(flags);
}
if (mPermissionLevel == PERMISSION_COARSE) {
builder.append(" (COARSE)");
}
builder.append(" ").append(getRequest());
return builder.toString();
}
}
protected abstract class LocationRegistration extends Registration implements
OnAlarmListener, ProviderEnabledListener {
final PowerManager.WakeLock mWakeLock;
private volatile ProviderTransport mProviderTransport;
private int mNumLocationsDelivered = 0;
private long mExpirationRealtimeMs = Long.MAX_VALUE;
protected <TTransport extends LocationTransport & ProviderTransport> LocationRegistration(
LocationRequest request, CallerIdentity identity, TTransport transport,
@PermissionLevel int permissionLevel) {
super(request, identity, transport, permissionLevel);
mProviderTransport = transport;
mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
mWakeLock.setReferenceCounted(true);
mWakeLock.setWorkSource(request.getWorkSource());
}
@Override
protected void onListenerUnregister() {
mProviderTransport = null;
}
@GuardedBy("mLock")
@Override
protected final void onProviderListenerRegister() {
long registerTimeMs = SystemClock.elapsedRealtime();
mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
// add alarm for expiration
if (mExpirationRealtimeMs <= registerTimeMs) {
onAlarm();
} else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
// Set WorkSource to null in order to ensure the alarm wakes up the device even when
// it is idle. Do this when the cost of waking up the device is less than the power
// cost of not performing the actions set off by the alarm, such as unregistering a
// location request.
mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this,
null);
}
// start listening for provider enabled/disabled events
addEnabledListener(this);
onLocationListenerRegister();
// if the provider is currently disabled, let the client know immediately
int userId = getIdentity().getUserId();
if (!isEnabled(userId)) {
onProviderEnabledChanged(mName, userId, false);
}
}
@GuardedBy("mLock")
@Override
protected final void onProviderListenerUnregister() {
// stop listening for provider enabled/disabled events
removeEnabledListener(this);
// remove alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
mAlarmHelper.cancel(this);
}
onLocationListenerUnregister();
}
/**
* Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
*/
@GuardedBy("mLock")
protected void onLocationListenerRegister() {}
/**
* Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
*/
@GuardedBy("mLock")
protected void onLocationListenerUnregister() {}
@GuardedBy("mLock")
@Override
protected final void onProviderListenerActive() {
// a new registration may not get a location immediately, the provider request may be
// delayed. therefore we deliver a historical location if available. since delivering an
// older location could be considered a breaking change for some applications, we only
// do so for apps targeting S+.
if (isChangeEnabled(DELIVER_HISTORICAL_LOCATIONS, getIdentity().getUid())) {
long maxLocationAgeMs = getRequest().getIntervalMillis();
Location lastDeliveredLocation = getLastDeliveredLocation();
if (lastDeliveredLocation != null) {
// ensure that location is fresher than the last delivered location
maxLocationAgeMs = min(maxLocationAgeMs,
lastDeliveredLocation.getElapsedRealtimeAgeMillis() - 1);
}
// requests are never delayed less than MIN_REQUEST_DELAY_MS, so it only makes sense
// to deliver historical locations to clients with a last location older than that
if (maxLocationAgeMs > MIN_REQUEST_DELAY_MS) {
Location lastLocation = getLastLocationUnsafe(
getIdentity().getUserId(),
getPermissionLevel(),
getRequest().isBypass(),
maxLocationAgeMs);
if (lastLocation != null) {
executeOperation(acceptLocationChange(LocationResult.wrap(lastLocation)));
}
}
}
}
@Override
public void onAlarm() {
if (D) {
Log.d(TAG, mName + " provider registration " + getIdentity()
+ " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
}
synchronized (mLock) {
// no need to remove alarm after it's fired
mExpirationRealtimeMs = Long.MAX_VALUE;
remove();
}
}
@GuardedBy("mLock")
@Override
@Nullable ListenerOperation<LocationTransport> acceptLocationChange(
LocationResult fineLocationResult) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
// check expiration time - alarm is not guaranteed to go off at the right time,
// especially for short intervals
if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
if (D) {
Log.d(TAG, mName + " provider registration " + getIdentity()
+ " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
}
remove();
return null;
}
LocationResult permittedLocationResult = Objects.requireNonNull(
getPermittedLocationResult(fineLocationResult, getPermissionLevel()));
LocationResult locationResult = permittedLocationResult.filter(
new Predicate<Location>() {
private Location mPreviousLocation = getLastDeliveredLocation();
@Override
public boolean test(Location location) {
if (mPreviousLocation != null) {
// check fastest interval
long deltaMs = location.getElapsedRealtimeMillis()
- mPreviousLocation.getElapsedRealtimeMillis();
long maxJitterMs = min((long) (FASTEST_INTERVAL_JITTER_PERCENTAGE
* getRequest().getIntervalMillis()),
MAX_FASTEST_INTERVAL_JITTER_MS);
if (deltaMs
< getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
return false;
}
// check smallest displacement
double smallestDisplacementM =
getRequest().getMinUpdateDistanceMeters();
if (smallestDisplacementM > 0.0 && location.distanceTo(
mPreviousLocation)
<= smallestDisplacementM) {
return false;
}
}
mPreviousLocation = location;
return true;
}
});
if (locationResult == null) {
return null;
}
// note app ops
if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(getPermissionLevel()),
getIdentity())) {
if (D) {
Log.w(TAG, "noteOp denied for " + getIdentity());
}
return null;
}
// deliver location
return new ListenerOperation<LocationTransport>() {
private boolean mUseWakeLock;
@Override
public void onPreExecute() {
mUseWakeLock = false;
final int size = locationResult.size();
for (int i = 0; i < size; ++i) {
if (!locationResult.get(i).isMock()) {
mUseWakeLock = true;
break;
}
}
// update last delivered location
setLastDeliveredLocation(locationResult.getLastLocation());
// don't acquire a wakelock for mock locations to prevent abuse
if (mUseWakeLock) {
mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
}
}
@Override
public void operate(LocationTransport listener) throws Exception {
// if delivering to the same process, make a copy of the location first (since
// location is mutable)
LocationResult deliverLocationResult;
if (getIdentity().getPid() == Process.myPid()) {
deliverLocationResult = locationResult.deepCopy();
} else {
deliverLocationResult = locationResult;
}
listener.deliverOnLocationChanged(deliverLocationResult,
mUseWakeLock ? mWakeLock::release : null);
EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(),
getIdentity());
}
@Override
public void onPostExecute(boolean success) {
if (!success && mUseWakeLock) {
mWakeLock.release();
}
if (success) {
// check num updates - if successful then this function will always be run
// from the same thread, and no additional synchronization is necessary
boolean remove = ++mNumLocationsDelivered >= getRequest().getMaxUpdates();
if (remove) {
if (D) {
Log.d(TAG, mName + " provider registration " + getIdentity()
+ " finished after " + mNumLocationsDelivered + " updates");
}
synchronized (mLock) {
remove();
}
}
}
}
};
}
@Override
public void onProviderEnabledChanged(String provider, int userId, boolean enabled) {
Preconditions.checkState(mName.equals(provider));
if (userId != getIdentity().getUserId()) {
return;
}
// we choose not to hold a wakelock for provider enabled changed events
executeSafely(getExecutor(), () -> mProviderTransport,
listener -> listener.deliverOnProviderEnabledChanged(mName, enabled),
this::onProviderOperationFailure);
}
protected abstract void onProviderOperationFailure(
ListenerOperation<ProviderTransport> operation, Exception exception);
}
protected final class LocationListenerRegistration extends LocationRegistration implements
IBinder.DeathRecipient {
protected LocationListenerRegistration(LocationRequest request, CallerIdentity identity,
LocationListenerTransport transport, @PermissionLevel int permissionLevel) {
super(request, identity, transport, permissionLevel);
}
@GuardedBy("mLock")
@Override
protected void onLocationListenerRegister() {
try {
((IBinder) getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
remove();
}
}
@GuardedBy("mLock")
@Override
protected void onLocationListenerUnregister() {
((IBinder) getKey()).unlinkToDeath(this, 0);
}
@Override
protected void onProviderOperationFailure(ListenerOperation<ProviderTransport> operation,
Exception exception) {
onTransportFailure(exception);
}
@Override
public void onOperationFailure(ListenerOperation<LocationTransport> operation,
Exception exception) {
onTransportFailure(exception);
}
private void onTransportFailure(Exception e) {
if (e instanceof RemoteException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
synchronized (mLock) {
remove();
}
} else {
throw new AssertionError(e);
}
}
@Override
public void binderDied() {
try {
if (D) {
Log.d(TAG, mName + " provider registration " + getIdentity() + " died");
}
synchronized (mLock) {
remove();
}
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
// ensure the crash is seen
throw new AssertionError(e);
}
}
}
protected final class LocationPendingIntentRegistration extends LocationRegistration implements
PendingIntent.CancelListener {
protected LocationPendingIntentRegistration(LocationRequest request,
CallerIdentity identity, LocationPendingIntentTransport transport,
@PermissionLevel int permissionLevel) {
super(request, identity, transport, permissionLevel);
}
@GuardedBy("mLock")
@Override
protected void onLocationListenerRegister() {
((PendingIntent) getKey()).registerCancelListener(this);
}
@GuardedBy("mLock")
@Override
protected void onLocationListenerUnregister() {
((PendingIntent) getKey()).unregisterCancelListener(this);
}
@Override
protected void onProviderOperationFailure(ListenerOperation<ProviderTransport> operation,
Exception exception) {
onTransportFailure(exception);
}
@Override
public void onOperationFailure(ListenerOperation<LocationTransport> operation,
Exception exception) {
onTransportFailure(exception);
}
private void onTransportFailure(Exception e) {
if (e instanceof RemoteException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
synchronized (mLock) {
remove();
}
} else {
throw new AssertionError(e);
}
}
@Override
public void onCancelled(PendingIntent intent) {
if (D) {
Log.d(TAG, mName + " provider registration " + getIdentity() + " cancelled");
}
synchronized (mLock) {
remove();
}
}
}
protected final class GetCurrentLocationListenerRegistration extends Registration implements
IBinder.DeathRecipient, OnAlarmListener {
private long mExpirationRealtimeMs = Long.MAX_VALUE;
protected GetCurrentLocationListenerRegistration(LocationRequest request,
CallerIdentity identity, LocationTransport transport, int permissionLevel) {
super(request, identity, transport, permissionLevel);
}
@GuardedBy("mLock")
@Override
protected void onProviderListenerRegister() {
try {
((IBinder) getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
remove();
}
long registerTimeMs = SystemClock.elapsedRealtime();
mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
// add alarm for expiration
if (mExpirationRealtimeMs <= registerTimeMs) {
onAlarm();
} else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
// Set WorkSource to null in order to ensure the alarm wakes up the device even when
// it is idle. Do this when the cost of waking up the device is less than the power
// cost of not performing the actions set off by the alarm, such as unregistering a
// location request.
mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this,
null);
}
}
@GuardedBy("mLock")
@Override
protected void onProviderListenerUnregister() {
// remove alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
mAlarmHelper.cancel(this);
}
((IBinder) getKey()).unlinkToDeath(this, 0);
}
@GuardedBy("mLock")
@Override
protected void onProviderListenerActive() {
Location lastLocation = getLastLocationUnsafe(
getIdentity().getUserId(),
getPermissionLevel(),
getRequest().isBypass(),
MAX_CURRENT_LOCATION_AGE_MS);
if (lastLocation != null) {
executeOperation(acceptLocationChange(LocationResult.wrap(lastLocation)));
}
}
@GuardedBy("mLock")
@Override
protected void onProviderListenerInactive() {
// if we go inactive for any reason, fail immediately
executeOperation(acceptLocationChange(null));
}
void deliverNull() {
synchronized (mLock) {
executeOperation(acceptLocationChange(null));
}
}
@Override
public void onAlarm() {
if (D) {
Log.d(TAG, mName + " provider registration " + getIdentity()
+ " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
}
synchronized (mLock) {
// no need to remove alarm after it's fired
mExpirationRealtimeMs = Long.MAX_VALUE;
executeOperation(acceptLocationChange(null));
}
}
@GuardedBy("mLock")
@Override
@Nullable ListenerOperation<LocationTransport> acceptLocationChange(
@Nullable LocationResult fineLocationResult) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
// check expiration time - alarm is not guaranteed to go off at the right time,
// especially for short intervals
if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
if (D) {
Log.d(TAG, mName + " provider registration " + getIdentity()
+ " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
}
fineLocationResult = null;
}
// lastly - note app ops
if (fineLocationResult != null && !mAppOpsHelper.noteOpNoThrow(
LocationPermissions.asAppOp(getPermissionLevel()), getIdentity())) {
if (D) {
Log.w(TAG, "noteOp denied for " + getIdentity());
}
fineLocationResult = null;
}
if (fineLocationResult != null) {
fineLocationResult = fineLocationResult.asLastLocationResult();
}
LocationResult locationResult = getPermittedLocationResult(fineLocationResult,
getPermissionLevel());
// deliver location
return new ListenerOperation<LocationTransport>() {
@Override
public void operate(LocationTransport listener) throws Exception {
// if delivering to the same process, make a copy of the location first (since
// location is mutable)
LocationResult deliverLocationResult;
if (getIdentity().getPid() == Process.myPid() && locationResult != null) {
deliverLocationResult = locationResult.deepCopy();
} else {
deliverLocationResult = locationResult;
}
// we currently don't hold a wakelock for getCurrentLocation deliveries
listener.deliverOnLocationChanged(deliverLocationResult, null);
EVENT_LOG.logProviderDeliveredLocations(mName,
locationResult != null ? locationResult.size() : 0, getIdentity());
}
@Override
public void onPostExecute(boolean success) {
// on failure we're automatically removed anyways, no need to attempt removal
// again
if (success) {
synchronized (mLock) {
remove();
}
}
}
};
}
@Override
public void onOperationFailure(ListenerOperation<LocationTransport> operation,
Exception e) {
if (e instanceof RemoteException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
synchronized (mLock) {
remove();
}
} else {
throw new AssertionError(e);
}
}
@Override
public void binderDied() {
try {
if (D) {
Log.d(TAG, mName + " provider registration " + getIdentity() + " died");
}
synchronized (mLock) {
remove();
}
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
// ensure the crash is seen
throw new AssertionError(e);
}
}
}
protected final Object mLock = new Object();
protected final String mName;
private final @Nullable PassiveLocationProviderManager mPassiveManager;
protected final Context mContext;
@GuardedBy("mLock")
private @State int mState;
// maps of user id to value
@GuardedBy("mLock")
private final SparseBooleanArray mEnabled; // null or not present means unknown
@GuardedBy("mLock")
private final SparseArray<LastLocation> mLastLocations;
@GuardedBy("mLock")
private final ArrayList<ProviderEnabledListener> mEnabledListeners;
private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
protected final LocationManagerInternal mLocationManagerInternal;
protected final LocationSettings mLocationSettings;
protected final SettingsHelper mSettingsHelper;
protected final UserInfoHelper mUserHelper;
protected final AlarmHelper mAlarmHelper;
protected final AppOpsHelper mAppOpsHelper;
protected final LocationPermissionsHelper mLocationPermissionsHelper;
protected final AppForegroundHelper mAppForegroundHelper;
protected final LocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
protected final ScreenInteractiveHelper mScreenInteractiveHelper;
protected final LocationAttributionHelper mLocationAttributionHelper;
protected final LocationUsageLogger mLocationUsageLogger;
protected final LocationFudger mLocationFudger;
private final UserListener mUserChangedListener = this::onUserChanged;
private final LocationSettings.LocationUserSettingsListener mLocationUserSettingsListener =
this::onLocationUserSettingsChanged;
private final UserSettingChangedListener mLocationEnabledChangedListener =
this::onLocationEnabledChanged;
private final GlobalSettingChangedListener mBackgroundThrottlePackageWhitelistChangedListener =
this::onBackgroundThrottlePackageWhitelistChanged;
private final UserSettingChangedListener mLocationPackageBlacklistChangedListener =
this::onLocationPackageBlacklistChanged;
private final LocationPermissionsListener mLocationPermissionsListener =
new LocationPermissionsListener() {
@Override
public void onLocationPermissionsChanged(String packageName) {
LocationProviderManager.this.onLocationPermissionsChanged(packageName);
}
@Override
public void onLocationPermissionsChanged(int uid) {
LocationProviderManager.this.onLocationPermissionsChanged(uid);
}
};
private final AppForegroundListener mAppForegroundChangedListener =
this::onAppForegroundChanged;
private final GlobalSettingChangedListener mBackgroundThrottleIntervalChangedListener =
this::onBackgroundThrottleIntervalChanged;
private final GlobalSettingChangedListener mIgnoreSettingsPackageWhitelistChangedListener =
this::onIgnoreSettingsWhitelistChanged;
private final LocationPowerSaveModeChangedListener mLocationPowerSaveModeChangedListener =
this::onLocationPowerSaveModeChanged;
private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener =
this::onScreenInteractiveChanged;
// acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
protected final MockableLocationProvider mProvider;
@GuardedBy("mLock")
private @Nullable OnAlarmListener mDelayedRegister;
@GuardedBy("mLock")
private @Nullable StateChangedListener mStateChangedListener;
public LocationProviderManager(Context context, Injector injector,
String name, @Nullable PassiveLocationProviderManager passiveManager) {
mContext = context;
mName = Objects.requireNonNull(name);
mPassiveManager = passiveManager;
mState = STATE_STOPPED;
mEnabled = new SparseBooleanArray(2);
mLastLocations = new SparseArray<>(2);
mEnabledListeners = new ArrayList<>();
mProviderRequestListeners = new CopyOnWriteArrayList<>();
mLocationManagerInternal = Objects.requireNonNull(
LocalServices.getService(LocationManagerInternal.class));
mLocationSettings = injector.getLocationSettings();
mSettingsHelper = injector.getSettingsHelper();
mUserHelper = injector.getUserInfoHelper();
mAlarmHelper = injector.getAlarmHelper();
mAppOpsHelper = injector.getAppOpsHelper();
mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
mAppForegroundHelper = injector.getAppForegroundHelper();
mLocationPowerSaveModeHelper = injector.getLocationPowerSaveModeHelper();
mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
mLocationAttributionHelper = injector.getLocationAttributionHelper();
mLocationUsageLogger = injector.getLocationUsageLogger();
mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
mProvider = new MockableLocationProvider(mLock);
// set listener last, since this lets our reference escape
mProvider.getController().setListener(this);
}
@Override
public String getTag() {
return TAG;
}
public void startManager(@Nullable StateChangedListener listener) {
synchronized (mLock) {
Preconditions.checkState(mState == STATE_STOPPED);
mState = STATE_STARTED;
mStateChangedListener = listener;
mUserHelper.addListener(mUserChangedListener);
mLocationSettings.registerLocationUserSettingsListener(mLocationUserSettingsListener);
mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
final long identity = Binder.clearCallingIdentity();
try {
mProvider.getController().start();
onUserStarted(UserHandle.USER_ALL);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
public void stopManager() {
synchronized (mLock) {
Preconditions.checkState(mState == STATE_STARTED);
mState = STATE_STOPPING;
final long identity = Binder.clearCallingIdentity();
try {
onEnabledChanged(UserHandle.USER_ALL);
removeRegistrationIf(key -> true);
mProvider.getController().stop();
} finally {
Binder.restoreCallingIdentity(identity);
}
mUserHelper.removeListener(mUserChangedListener);
mLocationSettings.unregisterLocationUserSettingsListener(mLocationUserSettingsListener);
mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
// if external entities are registering listeners it's their responsibility to
// unregister them before stopManager() is called
Preconditions.checkState(mEnabledListeners.isEmpty());
mProviderRequestListeners.clear();
mEnabled.clear();
mLastLocations.clear();
mStateChangedListener = null;
mState = STATE_STOPPED;
}
}
public String getName() {
return mName;
}
public AbstractLocationProvider.State getState() {
return mProvider.getState();
}
public @Nullable CallerIdentity getIdentity() {
return mProvider.getState().identity;
}
public @Nullable ProviderProperties getProperties() {
return mProvider.getState().properties;
}
public boolean hasProvider() {
return mProvider.getProvider() != null;
}
public boolean isEnabled(int userId) {
if (userId == UserHandle.USER_NULL) {
return false;
} else if (userId == UserHandle.USER_CURRENT) {
return isEnabled(mUserHelper.getCurrentUserId());
}
Preconditions.checkArgument(userId >= 0);
synchronized (mLock) {
int index = mEnabled.indexOfKey(userId);
if (index < 0) {
// this generally shouldn't occur, but might be possible due to race conditions
// on when we are notified of new users
Log.w(TAG, mName + " provider saw user " + userId + " unexpectedly");
onEnabledChanged(userId);
index = mEnabled.indexOfKey(userId);
}
return mEnabled.valueAt(index);
}
}
public void addEnabledListener(ProviderEnabledListener listener) {
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
mEnabledListeners.add(listener);
}
}
public void removeEnabledListener(ProviderEnabledListener listener) {
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
mEnabledListeners.remove(listener);
}
}
/** Add a {@link IProviderRequestListener}. */
public void addProviderRequestListener(IProviderRequestListener listener) {
mProviderRequestListeners.add(listener);
}
/** Remove a {@link IProviderRequestListener}. */
public void removeProviderRequestListener(IProviderRequestListener listener) {
mProviderRequestListeners.remove(listener);
}
public void setRealProvider(@Nullable AbstractLocationProvider provider) {
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
mProvider.setRealProvider(provider);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
public void setMockProvider(@Nullable MockLocationProvider provider) {
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
EVENT_LOG.logProviderMocked(mName, provider != null);
final long identity = Binder.clearCallingIdentity();
try {
mProvider.setMockProvider(provider);
} finally {
Binder.restoreCallingIdentity(identity);
}
// when removing a mock provider, also clear any mock last locations and reset the
// location fudger. the mock provider could have been used to infer the current
// location fudger offsets.
if (provider == null) {
final int lastLocationSize = mLastLocations.size();
for (int i = 0; i < lastLocationSize; i++) {
mLastLocations.valueAt(i).clearMock();
}
mLocationFudger.resetOffsets();
}
}
}
public void setMockProviderAllowed(boolean enabled) {
synchronized (mLock) {
if (!mProvider.isMock()) {
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
final long identity = Binder.clearCallingIdentity();
try {
mProvider.setMockProviderAllowed(enabled);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
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);
}
final long identity = Binder.clearCallingIdentity();
try {
mProvider.setMockProviderLocation(location);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
public @Nullable Location getLastLocation(LastLocationRequest request,
CallerIdentity identity, @PermissionLevel int permissionLevel) {
if (!isActive(request.isBypass(), identity)) {
return null;
}
// lastly - note app ops
if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
identity)) {
return null;
}
Location location = getPermittedLocation(
getLastLocationUnsafe(
identity.getUserId(),
permissionLevel,
request.isBypass(),
Long.MAX_VALUE),
permissionLevel);
if (location != null && identity.getPid() == Process.myPid()) {
// if delivering to the same process, make a copy of the location first (since
// location is mutable)
location = new Location(location);
}
return location;
}
/**
* This function does not perform any permissions or safety checks, by calling it you are
* committing to performing all applicable checks yourself. This always returns a "fine"
* location, even if the permissionLevel is coarse. You are responsible for coarsening the
* location if necessary.
*/
public @Nullable Location getLastLocationUnsafe(int userId,
@PermissionLevel int permissionLevel, boolean isBypass,
long maximumAgeMs) {
if (userId == UserHandle.USER_ALL) {
// find the most recent location across all users
Location lastLocation = null;
final int[] runningUserIds = mUserHelper.getRunningUserIds();
for (int i = 0; i < runningUserIds.length; i++) {
Location next = getLastLocationUnsafe(runningUserIds[i], permissionLevel,
isBypass, maximumAgeMs);
if (lastLocation == null || (next != null && next.getElapsedRealtimeNanos()
> lastLocation.getElapsedRealtimeNanos())) {
lastLocation = next;
}
}
return lastLocation;
} else if (userId == UserHandle.USER_CURRENT) {
return getLastLocationUnsafe(mUserHelper.getCurrentUserId(), permissionLevel,
isBypass, maximumAgeMs);
}
Preconditions.checkArgument(userId >= 0);
Location location;
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation == null) {
location = null;
} else {
location = lastLocation.get(permissionLevel, isBypass);
}
}
if (location == null) {
return null;
}
if (location.getElapsedRealtimeAgeMillis() > maximumAgeMs) {
return null;
}
return location;
}
public void injectLastLocation(Location location, int userId) {
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
if (getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE) == null) {
setLastLocation(location, userId);
}
}
}
private void setLastLocation(Location location, int userId) {
if (userId == UserHandle.USER_ALL) {
final int[] runningUserIds = mUserHelper.getRunningUserIds();
for (int i = 0; i < runningUserIds.length; i++) {
setLastLocation(location, runningUserIds[i]);
}
return;
} else if (userId == UserHandle.USER_CURRENT) {
setLastLocation(location, mUserHelper.getCurrentUserId());
return;
}
Preconditions.checkArgument(userId >= 0);
synchronized (mLock) {
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation == null) {
lastLocation = new LastLocation();
mLastLocations.put(userId, lastLocation);
}
if (isEnabled(userId)) {
lastLocation.set(location);
}
lastLocation.setBypass(location);
}
}
public @Nullable ICancellationSignal getCurrentLocation(LocationRequest request,
CallerIdentity identity, int permissionLevel, ILocationCallback callback) {
if (request.getDurationMillis() > MAX_GET_CURRENT_LOCATION_TIMEOUT_MS) {
request = new LocationRequest.Builder(request)
.setDurationMillis(MAX_GET_CURRENT_LOCATION_TIMEOUT_MS)
.build();
}
GetCurrentLocationListenerRegistration registration =
new GetCurrentLocationListenerRegistration(
request,
identity,
new GetCurrentLocationTransport(callback),
permissionLevel);
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long ident = Binder.clearCallingIdentity();
try {
putRegistration(callback.asBinder(), registration);
if (!registration.isActive()) {
// if the registration never activated, fail it immediately
registration.deliverNull();
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
CancellationSignal.fromTransport(cancelTransport)
.setOnCancelListener(SingleUseCallback.wrap(
() -> {
synchronized (mLock) {
removeRegistration(callback.asBinder(), registration);
}
}));
return cancelTransport;
}
public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
final long identity = Binder.clearCallingIdentity();
try {
mProvider.getController().sendExtraCommand(uid, pid, command, extras);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
public void registerLocationRequest(LocationRequest request, CallerIdentity identity,
@PermissionLevel int permissionLevel, ILocationListener listener) {
LocationListenerRegistration registration = new LocationListenerRegistration(
request,
identity,
new LocationListenerTransport(listener),
permissionLevel);
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long ident = Binder.clearCallingIdentity();
try {
putRegistration(listener.asBinder(), registration);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
public void registerLocationRequest(LocationRequest request, CallerIdentity callerIdentity,
@PermissionLevel int permissionLevel, PendingIntent pendingIntent) {
LocationPendingIntentRegistration registration = new LocationPendingIntentRegistration(
request,
callerIdentity,
new LocationPendingIntentTransport(mContext, pendingIntent),
permissionLevel);
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
putRegistration(pendingIntent, registration);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
public void flush(ILocationListener listener, int requestCode) {
synchronized (mLock) {
final long identity = Binder.clearCallingIdentity();
try {
boolean flushed = updateRegistration(listener.asBinder(), registration -> {
registration.flush(requestCode);
return false;
});
if (!flushed) {
throw new IllegalArgumentException("unregistered listener cannot be flushed");
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
public void flush(PendingIntent pendingIntent, int requestCode) {
synchronized (mLock) {
final long identity = Binder.clearCallingIdentity();
try {
boolean flushed = updateRegistration(pendingIntent, registration -> {
registration.flush(requestCode);
return false;
});
if (!flushed) {
throw new IllegalArgumentException(
"unregistered pending intent cannot be flushed");
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
public void unregisterLocationRequest(ILocationListener listener) {
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
removeRegistration(listener.asBinder());
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
public void unregisterLocationRequest(PendingIntent pendingIntent) {
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
removeRegistration(pendingIntent);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@GuardedBy("mLock")
@Override
protected void onRegister() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
mSettingsHelper.addOnBackgroundThrottleIntervalChangedListener(
mBackgroundThrottleIntervalChangedListener);
mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
mLocationPackageBlacklistChangedListener);
mSettingsHelper.addIgnoreSettingsAllowlistChangedListener(
mIgnoreSettingsPackageWhitelistChangedListener);
mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
mAppForegroundHelper.addListener(mAppForegroundChangedListener);
mLocationPowerSaveModeHelper.addListener(mLocationPowerSaveModeChangedListener);
mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
}
@GuardedBy("mLock")
@Override
protected void onUnregister() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
mSettingsHelper.removeOnBackgroundThrottleIntervalChangedListener(
mBackgroundThrottleIntervalChangedListener);
mSettingsHelper.removeOnBackgroundThrottlePackageWhitelistChangedListener(
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
mLocationPackageBlacklistChangedListener);
mSettingsHelper.removeIgnoreSettingsAllowlistChangedListener(
mIgnoreSettingsPackageWhitelistChangedListener);
mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
mLocationPowerSaveModeHelper.removeListener(mLocationPowerSaveModeChangedListener);
mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
}
@GuardedBy("mLock")
@Override
protected void onRegistrationAdded(Object key, Registration registration) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_STARTED,
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
registration.getIdentity().getPackageName(),
registration.getIdentity().getAttributionTag(),
mName,
registration.getRequest(),
key instanceof PendingIntent,
/* geofence= */ key instanceof IBinder,
null, registration.isForeground());
}
@GuardedBy("mLock")
@Override
protected void onRegistrationReplaced(Object key, Registration oldRegistration,
Registration newRegistration) {
// by saving the last delivered location state we are able to potentially delay the
// resulting provider request longer and save additional power
newRegistration.setLastDeliveredLocation(oldRegistration.getLastDeliveredLocation());
super.onRegistrationReplaced(key, oldRegistration, newRegistration);
}
@GuardedBy("mLock")
@Override
protected void onRegistrationRemoved(Object key, Registration registration) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
registration.getIdentity().getPackageName(),
registration.getIdentity().getAttributionTag(),
mName,
registration.getRequest(),
key instanceof PendingIntent,
/* geofence= */ key instanceof IBinder,
null, registration.isForeground());
}
@GuardedBy("mLock")
@Override
protected boolean registerWithService(ProviderRequest request,
Collection<Registration> registrations) {
return reregisterWithService(ProviderRequest.EMPTY_REQUEST, request, registrations);
}
@GuardedBy("mLock")
@Override
protected boolean reregisterWithService(ProviderRequest oldRequest,
ProviderRequest newRequest, Collection<Registration> registrations) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
if (mDelayedRegister != null) {
mAlarmHelper.cancel(mDelayedRegister);
mDelayedRegister = null;
}
// calculate how long the new request should be delayed before sending it off to the
// provider, under the assumption that once we send the request off, the provider will
// immediately attempt to deliver a new location satisfying that request.
long delayMs;
if (!oldRequest.isBypass() && newRequest.isBypass()) {
delayMs = 0;
} else if (newRequest.getIntervalMillis() > oldRequest.getIntervalMillis()) {
// if the interval has increased, tell the provider immediately, so it can save power
// (even though technically this could burn extra power in the short term by producing
// an extra location - the provider itself is free to detect an increasing interval and
// delay its own location)
delayMs = 0;
} else {
delayMs = calculateRequestDelayMillis(newRequest.getIntervalMillis(), registrations);
}
// the delay should never exceed the new interval
Preconditions.checkState(delayMs >= 0 && delayMs <= newRequest.getIntervalMillis());
if (delayMs < MIN_REQUEST_DELAY_MS) {
setProviderRequest(newRequest);
} else {
if (D) {
Log.d(TAG, mName + " provider delaying request update " + newRequest + " by "
+ TimeUtils.formatDuration(delayMs));
}
mDelayedRegister = new OnAlarmListener() {
@Override
public void onAlarm() {
synchronized (mLock) {
if (mDelayedRegister == this) {
setProviderRequest(newRequest);
mDelayedRegister = null;
}
}
}
};
// Set WorkSource to null in order to ensure the alarm wakes up the device even when it
// is idle. Do this when the cost of waking up the device is less than the power cost of
// not performing the actions set off by the alarm, such as unregistering a location
// request.
mAlarmHelper.setDelayedAlarm(delayMs, mDelayedRegister, null);
}
return true;
}
@GuardedBy("mLock")
@Override
protected void unregisterWithService() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
setProviderRequest(ProviderRequest.EMPTY_REQUEST);
}
@GuardedBy("mLock")
void setProviderRequest(ProviderRequest request) {
EVENT_LOG.logProviderUpdateRequest(mName, request);
mProvider.getController().setRequest(request);
FgThread.getHandler().post(() -> {
for (IProviderRequestListener listener : mProviderRequestListeners) {
try {
listener.onProviderRequestChanged(mName, request);
} catch (RemoteException e) {
mProviderRequestListeners.remove(listener);
}
}
});
}
@GuardedBy("mLock")
@Override
protected boolean isActive(Registration registration) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
if (!registration.isPermitted()) {
return false;
}
boolean isBypass = registration.getRequest().isBypass();
if (!isActive(isBypass, registration.getIdentity())) {
return false;
}
if (!isBypass) {
switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
case LOCATION_MODE_FOREGROUND_ONLY:
if (!registration.isForeground()) {
return false;
}
break;
case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
if (!GPS_PROVIDER.equals(mName)) {
break;
}
// fall through
case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
// fall through
case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
if (!mScreenInteractiveHelper.isInteractive()) {
return false;
}
break;
case LOCATION_MODE_NO_CHANGE:
// fall through
default:
break;
}
}
return true;
}
private boolean isActive(boolean isBypass, CallerIdentity identity) {
if (identity.isSystemServer()) {
if (!isBypass) {
if (!isEnabled(mUserHelper.getCurrentUserId())) {
return false;
}
}
} else {
if (!isBypass) {
if (!isEnabled(identity.getUserId())) {
return false;
}
if (!mUserHelper.isCurrentUserId(identity.getUserId())) {
return false;
}
}
if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
identity.getPackageName())) {
return false;
}
}
return true;
}
@GuardedBy("mLock")
@Override
protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
long intervalMs = ProviderRequest.INTERVAL_DISABLED;
int quality = LocationRequest.QUALITY_LOW_POWER;
long maxUpdateDelayMs = Long.MAX_VALUE;
boolean adasGnssBypass = false;
boolean locationSettingsIgnored = false;
boolean lowPower = true;
for (Registration registration : registrations) {
LocationRequest request = registration.getRequest();
// passive requests do not contribute to the provider request, and passive requests
// must handle the batching parameters of non-passive requests
if (request.getIntervalMillis() == LocationRequest.PASSIVE_INTERVAL) {
continue;
}
intervalMs = min(request.getIntervalMillis(), intervalMs);
quality = min(request.getQuality(), quality);
maxUpdateDelayMs = min(request.getMaxUpdateDelayMillis(), maxUpdateDelayMs);
adasGnssBypass |= request.isAdasGnssBypass();
locationSettingsIgnored |= request.isLocationSettingsIgnored();
lowPower &= request.isLowPower();
}
if (intervalMs == ProviderRequest.INTERVAL_DISABLED) {
return ProviderRequest.EMPTY_REQUEST;
}
if (maxUpdateDelayMs / 2 < intervalMs) {
// reduces churn if only the batching parameter has changed
maxUpdateDelayMs = 0;
}
// calculate who to blame for power in a somewhat arbitrary fashion. we pick a threshold
// interval slightly higher that the minimum interval, and spread the blame across all
// contributing registrations under that threshold (since worksource does not allow us to
// represent differing power blame ratios).
long thresholdIntervalMs;
try {
thresholdIntervalMs = Math.multiplyExact(Math.addExact(intervalMs, 1000) / 2, 3);
} catch (ArithmeticException e) {
// check for and handle overflow by setting to one below the passive interval so passive
// requests are automatically skipped
thresholdIntervalMs = LocationRequest.PASSIVE_INTERVAL - 1;
}
WorkSource workSource = new WorkSource();
for (Registration registration : registrations) {
if (registration.getRequest().getIntervalMillis() <= thresholdIntervalMs) {
workSource.add(registration.getRequest().getWorkSource());
}
}
return new ProviderRequest.Builder()
.setIntervalMillis(intervalMs)
.setQuality(quality)
.setMaxUpdateDelayMillis(maxUpdateDelayMs)
.setAdasGnssBypass(adasGnssBypass)
.setLocationSettingsIgnored(locationSettingsIgnored)
.setLowPower(lowPower)
.setWorkSource(workSource)
.build();
}
@GuardedBy("mLock")
protected long calculateRequestDelayMillis(long newIntervalMs,
Collection<Registration> registrations) {
// calculate the minimum delay across all registrations, ensuring that it is not more than
// the requested interval
long delayMs = newIntervalMs;
for (Registration registration : registrations) {
if (delayMs == 0) {
break;
}
LocationRequest locationRequest = registration.getRequest();
Location last = registration.getLastDeliveredLocation();
if (last == null && !locationRequest.isLocationSettingsIgnored()) {
// if this request has never gotten any location and it's not ignoring location
// settings, then we pretend that this request has gotten the last applicable cached
// location for our calculations instead. this prevents spammy add/remove behavior
last = getLastLocationUnsafe(
registration.getIdentity().getUserId(),
registration.getPermissionLevel(),
false,
locationRequest.getIntervalMillis());
}
long registrationDelayMs;
if (last == null) {
// if this request has never gotten any location then there's no delay
registrationDelayMs = 0;
} else {
// otherwise the delay is the amount of time until the next location is expected
registrationDelayMs = max(0,
locationRequest.getIntervalMillis() - last.getElapsedRealtimeAgeMillis());
}
delayMs = min(delayMs, registrationDelayMs);
}
return delayMs;
}
private void onUserChanged(int userId, int change) {
synchronized (mLock) {
if (mState == STATE_STOPPED) {
return;
}
switch (change) {
case UserListener.CURRENT_USER_CHANGED:
updateRegistrations(
registration -> registration.getIdentity().getUserId() == userId);
break;
case UserListener.USER_STARTED:
onUserStarted(userId);
break;
case UserListener.USER_STOPPED:
onUserStopped(userId);
break;
}
}
}
private void onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings,
LocationUserSettings newSettings) {
if (oldSettings.isAdasGnssLocationEnabled() != newSettings.isAdasGnssLocationEnabled()) {
synchronized (mLock) {
updateRegistrations(
registration -> registration.onAdasGnssLocationEnabledChanged(userId));
}
}
}
private void onLocationEnabledChanged(int userId) {
synchronized (mLock) {
if (mState == STATE_STOPPED) {
return;
}
onEnabledChanged(userId);
}
}
private void onScreenInteractiveChanged(boolean screenInteractive) {
synchronized (mLock) {
switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
if (!GPS_PROVIDER.equals(mName)) {
break;
}
// fall through
case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
// fall through
case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
updateRegistrations(registration -> true);
break;
default:
break;
}
}
}
private void onBackgroundThrottlePackageWhitelistChanged() {
synchronized (mLock) {
updateRegistrations(Registration::onProviderLocationRequestChanged);
}
}
private void onBackgroundThrottleIntervalChanged() {
synchronized (mLock) {
updateRegistrations(Registration::onProviderLocationRequestChanged);
}
}
private void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode) {
synchronized (mLock) {
// this is rare, just assume everything has changed to keep it simple
updateRegistrations(registration -> true);
}
}
private void onAppForegroundChanged(int uid, boolean foreground) {
synchronized (mLock) {
updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
}
}
private void onIgnoreSettingsWhitelistChanged() {
synchronized (mLock) {
updateRegistrations(Registration::onProviderLocationRequestChanged);
}
}
private void onLocationPackageBlacklistChanged(int userId) {
synchronized (mLock) {
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
}
private void onLocationPermissionsChanged(String packageName) {
synchronized (mLock) {
updateRegistrations(
registration -> registration.onLocationPermissionsChanged(packageName));
}
}
private void onLocationPermissionsChanged(int uid) {
synchronized (mLock) {
updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
}
}
@GuardedBy("mLock")
@Override
public void onStateChanged(
AbstractLocationProvider.State oldState, AbstractLocationProvider.State newState) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
if (oldState.allowed != newState.allowed) {
onEnabledChanged(UserHandle.USER_ALL);
}
if (!Objects.equals(oldState.properties, newState.properties)) {
updateRegistrations(Registration::onProviderPropertiesChanged);
}
if (mStateChangedListener != null) {
StateChangedListener listener = mStateChangedListener;
FgThread.getExecutor().execute(
() -> listener.onStateChanged(mName, oldState, newState));
}
}
@GuardedBy("mLock")
@Override
public void onReportLocation(LocationResult locationResult) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
LocationResult filtered;
if (mPassiveManager != null) {
filtered = locationResult.filter(location -> {
if (!location.isMock()) {
if (location.getLatitude() == 0 && location.getLongitude() == 0) {
Log.w(TAG, "blocking 0,0 location from " + mName + " provider");
return false;
}
}
if (!location.isComplete()) {
Log.w(TAG, "blocking incomplete location from " + mName + " provider");
return false;
}
return true;
});
if (filtered == null) {
return;
}
// don't log location received for passive provider because it's spammy
EVENT_LOG.logProviderReceivedLocations(mName, filtered.size());
} else {
// passive provider should get already filtered results as input
filtered = locationResult;
}
// update last location
setLastLocation(filtered.getLastLocation(), UserHandle.USER_ALL);
// attempt listener delivery
deliverToListeners(registration -> {
return registration.acceptLocationChange(filtered);
});
// notify passive provider
if (mPassiveManager != null) {
mPassiveManager.updateLocation(filtered);
}
}
@GuardedBy("mLock")
private void onUserStarted(int userId) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
if (userId == UserHandle.USER_NULL) {
return;
}
if (userId == UserHandle.USER_ALL) {
// clear the user's prior enabled state to prevent broadcast of enabled state change
mEnabled.clear();
onEnabledChanged(UserHandle.USER_ALL);
} else {
Preconditions.checkArgument(userId >= 0);
// clear the user's prior enabled state to prevent broadcast of enabled state change
mEnabled.delete(userId);
onEnabledChanged(userId);
}
}
@GuardedBy("mLock")
private void onUserStopped(int userId) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
if (userId == UserHandle.USER_NULL) {
return;
}
if (userId == UserHandle.USER_ALL) {
mEnabled.clear();
mLastLocations.clear();
} else {
Preconditions.checkArgument(userId >= 0);
mEnabled.delete(userId);
mLastLocations.remove(userId);
}
}
@GuardedBy("mLock")
private void onEnabledChanged(int userId) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
if (userId == UserHandle.USER_NULL) {
// used during initialization - ignore since many lower level operations (checking
// settings for instance) do not support the null user
return;
} else if (userId == UserHandle.USER_ALL) {
final int[] runningUserIds = mUserHelper.getRunningUserIds();
for (int i = 0; i < runningUserIds.length; i++) {
onEnabledChanged(runningUserIds[i]);
}
return;
}
Preconditions.checkArgument(userId >= 0);
boolean enabled = mState == STATE_STARTED
&& mProvider.getState().allowed
&& mSettingsHelper.isLocationEnabled(userId);
int index = mEnabled.indexOfKey(userId);
Boolean wasEnabled = index < 0 ? null : mEnabled.valueAt(index);
if (wasEnabled != null && wasEnabled == enabled) {
return;
}
mEnabled.put(userId, enabled);
// don't log unknown -> false transitions for brevity
if (wasEnabled != null || enabled) {
if (D) {
Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
}
EVENT_LOG.logProviderEnabled(mName, userId, enabled);
}
// clear last locations if we become disabled
if (!enabled) {
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation != null) {
lastLocation.clearLocations();
}
}
// do not send change notifications if we just saw this user for the first time
if (wasEnabled != null) {
// passive provider never get public updates for legacy reasons
if (!PASSIVE_PROVIDER.equals(mName)) {
Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION)
.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName)
.putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, enabled)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
}
// send updates to internal listeners - since we expect listener changes to be more
// frequent than enabled changes, we use copy-on-read instead of copy-on-write
if (!mEnabledListeners.isEmpty()) {
ProviderEnabledListener[] listeners = mEnabledListeners.toArray(
new ProviderEnabledListener[0]);
FgThread.getHandler().post(() -> {
for (int i = 0; i < listeners.length; i++) {
listeners[i].onProviderEnabledChanged(mName, userId, enabled);
}
});
}
}
// update active state of affected registrations
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
@Nullable Location getPermittedLocation(@Nullable Location fineLocation,
@PermissionLevel int permissionLevel) {
switch (permissionLevel) {
case PERMISSION_FINE:
return fineLocation;
case PERMISSION_COARSE:
return fineLocation != null ? mLocationFudger.createCoarse(fineLocation) : null;
default:
// shouldn't be possible to have a client added without location permissions
throw new AssertionError();
}
}
@Nullable LocationResult getPermittedLocationResult(
@Nullable LocationResult fineLocationResult, @PermissionLevel int permissionLevel) {
switch (permissionLevel) {
case PERMISSION_FINE:
return fineLocationResult;
case PERMISSION_COARSE:
return fineLocationResult != null ? mLocationFudger.createCoarse(fineLocationResult)
: null;
default:
// shouldn't be possible to have a client added without location permissions
throw new AssertionError();
}
}
public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
synchronized (mLock) {
ipw.print(mName);
ipw.print(" provider");
if (mProvider.isMock()) {
ipw.print(" [mock]");
}
ipw.println(":");
ipw.increaseIndent();
super.dump(fd, ipw, args);
int[] userIds = mUserHelper.getRunningUserIds();
for (int userId : userIds) {
if (userIds.length != 1) {
ipw.print("user ");
ipw.print(userId);
ipw.println(":");
ipw.increaseIndent();
}
ipw.print("last location=");
ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE));
ipw.print("enabled=");
ipw.println(isEnabled(userId));
if (userIds.length != 1) {
ipw.decreaseIndent();
}
}
}
mProvider.dump(fd, ipw, args);
ipw.decreaseIndent();
}
@Override
protected String getServiceState() {
return mProvider.getCurrentRequest().toString();
}
private static class LastLocation {
private @Nullable Location mFineLocation;
private @Nullable Location mCoarseLocation;
private @Nullable Location mFineBypassLocation;
private @Nullable Location mCoarseBypassLocation;
LastLocation() {}
public void clearMock() {
if (mFineLocation != null && mFineLocation.isMock()) {
mFineLocation = null;
}
if (mCoarseLocation != null && mCoarseLocation.isMock()) {
mCoarseLocation = null;
}
if (mFineBypassLocation != null && mFineBypassLocation.isMock()) {
mFineBypassLocation = null;
}
if (mCoarseBypassLocation != null && mCoarseBypassLocation.isMock()) {
mCoarseBypassLocation = null;
}
}
public void clearLocations() {
mFineLocation = null;
mCoarseLocation = null;
}
public @Nullable Location get(@PermissionLevel int permissionLevel,
boolean isBypass) {
switch (permissionLevel) {
case PERMISSION_FINE:
if (isBypass) {
return mFineBypassLocation;
} else {
return mFineLocation;
}
case PERMISSION_COARSE:
if (isBypass) {
return mCoarseBypassLocation;
} else {
return mCoarseLocation;
}
default:
// shouldn't be possible to have a client added without location permissions
throw new AssertionError();
}
}
public void set(Location location) {
mFineLocation = calculateNextFine(mFineLocation, location);
mCoarseLocation = calculateNextCoarse(mCoarseLocation, location);
}
public void setBypass(Location location) {
mFineBypassLocation = calculateNextFine(mFineBypassLocation, location);
mCoarseBypassLocation = calculateNextCoarse(mCoarseBypassLocation, location);
}
private Location calculateNextFine(@Nullable Location oldFine, Location newFine) {
if (oldFine == null) {
return newFine;
}
// update last fine interval only if more recent
if (newFine.getElapsedRealtimeNanos() > oldFine.getElapsedRealtimeNanos()) {
return newFine;
} else {
return oldFine;
}
}
private Location calculateNextCoarse(@Nullable Location oldCoarse, Location newCoarse) {
if (oldCoarse == null) {
return newCoarse;
}
// update last coarse interval only if enough time has passed
if (newCoarse.getElapsedRealtimeMillis() - MIN_COARSE_INTERVAL_MS
> oldCoarse.getElapsedRealtimeMillis()) {
return newCoarse;
} else {
return oldCoarse;
}
}
}
private static class SingleUseCallback extends IRemoteCallback.Stub implements Runnable,
CancellationSignal.OnCancelListener {
public static @Nullable SingleUseCallback wrap(@Nullable Runnable callback) {
return callback == null ? null : new SingleUseCallback(callback);
}
@GuardedBy("this")
private @Nullable Runnable mCallback;
private SingleUseCallback(Runnable callback) {
mCallback = Objects.requireNonNull(callback);
}
@Override
public void sendResult(Bundle data) {
run();
}
@Override
public void onCancel() {
run();
}
@Override
public void run() {
Runnable callback;
synchronized (this) {
callback = mCallback;
mCallback = null;
}
// prevent this callback from being run more than once - otherwise this could provide an
// attack vector for a malicious app to break assumptions on how many times a callback
// may be invoked, and thus crash system server.
if (callback == null) {
return;
}
final long identity = Binder.clearCallingIdentity();
try {
callback.run();
} catch (RuntimeException e) {
// since this is within a oneway binder transaction there is nowhere
// for exceptions to go - move onto another thread to crash system
// server so we find out about it
FgThread.getExecutor().execute(() -> {
throw new AssertionError(e);
});
throw e;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
}