blob: 22a675ad39ab61ea8a52010322c22a1cd6e3751d [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.location.provider.ProviderRequest.INTERVAL_DISABLED;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import android.annotation.Nullable;
import android.location.Location;
import android.location.LocationResult;
import android.location.provider.ProviderRequest;
import android.os.SystemClock;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.DeviceIdleInternal;
import com.android.server.FgThread;
import com.android.server.location.injector.DeviceIdleHelper;
import com.android.server.location.injector.DeviceStationaryHelper;
import com.android.server.location.injector.Injector;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* Throttles location providers completely while the device is in doze and stationary, and returns
* the last known location as a new location at the appropriate interval instead. Hypothetically,
* this throttling could be applied only when the device is stationary - however we don't trust the
* accuracy of the on-device SMD (which could allow for 100s of feet of movement without triggering
* in some use cases) enough to rely just on this. Instead we require the device to be in doze mode
* and stationary to narrow down the effect of false positives/negatives.
*/
public final class StationaryThrottlingLocationProvider extends DelegateLocationProvider
implements DeviceIdleHelper.DeviceIdleListener, DeviceIdleInternal.StationaryListener {
private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000;
final Object mLock = new Object();
private final String mName;
private final DeviceIdleHelper mDeviceIdleHelper;
private final DeviceStationaryHelper mDeviceStationaryHelper;
@GuardedBy("mLock")
private boolean mDeviceIdle = false;
@GuardedBy("mLock")
private boolean mDeviceStationary = false;
@GuardedBy("mLock")
private long mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
@GuardedBy("mLock")
private ProviderRequest mIncomingRequest = ProviderRequest.EMPTY_REQUEST;
@GuardedBy("mLock")
private ProviderRequest mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
@GuardedBy("mLock")
long mThrottlingIntervalMs = INTERVAL_DISABLED;
@GuardedBy("mLock")
@Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
@GuardedBy("mLock")
@Nullable Location mLastLocation;
public StationaryThrottlingLocationProvider(String name, Injector injector,
AbstractLocationProvider delegate) {
super(DIRECT_EXECUTOR, delegate);
mName = name;
mDeviceIdleHelper = injector.getDeviceIdleHelper();
mDeviceStationaryHelper = injector.getDeviceStationaryHelper();
// must be last statement in the constructor because reference is escaping
initializeDelegate();
}
@Override
public void onReportLocation(LocationResult locationResult) {
super.onReportLocation(locationResult);
synchronized (mLock) {
mLastLocation = locationResult.getLastLocation();
onThrottlingChangedLocked(false);
}
}
@Override
protected void onStart() {
mDelegate.getController().start();
synchronized (mLock) {
mDeviceIdleHelper.addListener(this);
mDeviceIdle = mDeviceIdleHelper.isDeviceIdle();
mDeviceStationaryHelper.addListener(this);
mDeviceStationary = false;
mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
onThrottlingChangedLocked(false);
}
}
@Override
protected void onStop() {
synchronized (mLock) {
mDeviceStationaryHelper.removeListener(this);
mDeviceIdleHelper.removeListener(this);
mIncomingRequest = ProviderRequest.EMPTY_REQUEST;
mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
mThrottlingIntervalMs = INTERVAL_DISABLED;
if (mDeliverLastLocationCallback != null) {
FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
mDeliverLastLocationCallback = null;
}
mLastLocation = null;
}
mDelegate.getController().stop();
}
@Override
protected void onSetRequest(ProviderRequest request) {
synchronized (mLock) {
mIncomingRequest = request;
onThrottlingChangedLocked(true);
}
}
@Override
public void onDeviceIdleChanged(boolean deviceIdle) {
synchronized (mLock) {
if (deviceIdle == mDeviceIdle) {
return;
}
mDeviceIdle = deviceIdle;
onThrottlingChangedLocked(false);
}
}
@Override
public void onDeviceStationaryChanged(boolean deviceStationary) {
synchronized (mLock) {
if (mDeviceStationary == deviceStationary) {
return;
}
mDeviceStationary = deviceStationary;
if (mDeviceStationary) {
mDeviceStationaryRealtimeMs = SystemClock.elapsedRealtime();
} else {
mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
}
onThrottlingChangedLocked(false);
}
}
@GuardedBy("mLock")
private void onThrottlingChangedLocked(boolean deliverImmediate) {
long throttlingIntervalMs = INTERVAL_DISABLED;
if (mDeviceStationary && mDeviceIdle && !mIncomingRequest.isLocationSettingsIgnored()
&& mLastLocation != null
&& mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs)
<= MAX_STATIONARY_LOCATION_AGE_MS) {
throttlingIntervalMs = mIncomingRequest.getIntervalMillis();
}
ProviderRequest newRequest;
if (throttlingIntervalMs != INTERVAL_DISABLED) {
newRequest = ProviderRequest.EMPTY_REQUEST;
} else {
newRequest = mIncomingRequest;
}
if (!newRequest.equals(mOutgoingRequest)) {
mOutgoingRequest = newRequest;
mDelegate.getController().setRequest(mOutgoingRequest);
}
if (throttlingIntervalMs == mThrottlingIntervalMs) {
return;
}
long oldThrottlingIntervalMs = mThrottlingIntervalMs;
mThrottlingIntervalMs = throttlingIntervalMs;
if (mThrottlingIntervalMs != INTERVAL_DISABLED) {
if (oldThrottlingIntervalMs == INTERVAL_DISABLED) {
if (D) {
Log.d(TAG, mName + " provider stationary throttled");
}
EVENT_LOG.logProviderStationaryThrottled(mName, true, mOutgoingRequest);
}
if (mDeliverLastLocationCallback != null) {
FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
}
mDeliverLastLocationCallback = new DeliverLastLocationRunnable();
Preconditions.checkState(mLastLocation != null);
if (deliverImmediate) {
FgThread.getHandler().post(mDeliverLastLocationCallback);
} else {
long delayMs = mThrottlingIntervalMs - mLastLocation.getElapsedRealtimeAgeMillis();
FgThread.getHandler().postDelayed(mDeliverLastLocationCallback, delayMs);
}
} else {
if (oldThrottlingIntervalMs != INTERVAL_DISABLED) {
EVENT_LOG.logProviderStationaryThrottled(mName, false, mOutgoingRequest);
if (D) {
Log.d(TAG, mName + " provider stationary unthrottled");
}
}
FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
mDeliverLastLocationCallback = null;
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mThrottlingIntervalMs != INTERVAL_DISABLED) {
pw.println("stationary throttled=" + mLastLocation);
} else {
pw.print("stationary throttled=false");
if (!mDeviceIdle) {
pw.print(" (not idle)");
}
if (!mDeviceStationary) {
pw.print(" (not stationary)");
}
pw.println();
}
mDelegate.dump(fd, pw, args);
}
private class DeliverLastLocationRunnable implements Runnable {
DeliverLastLocationRunnable() {}
@Override
public void run() {
Location location;
synchronized (mLock) {
if (mDeliverLastLocationCallback != this) {
return;
}
if (mLastLocation == null) {
return;
}
location = new Location(mLastLocation);
location.setTime(System.currentTimeMillis());
location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
if (location.hasSpeed()) {
location.removeSpeed();
if (location.hasSpeedAccuracy()) {
location.removeSpeedAccuracy();
}
}
if (location.hasBearing()) {
location.removeBearing();
if (location.hasBearingAccuracy()) {
location.removeBearingAccuracy();
}
}
mLastLocation = location;
FgThread.getHandler().postDelayed(this, mThrottlingIntervalMs);
}
reportLocation(LocationResult.wrap(location));
}
}
}