Merge "Implement stationary location throttling" into sc-dev

This commit is contained in:
Soonil Nagarkar
2021-02-16 19:04:08 +00:00
committed by Android (Google) Code Review
18 changed files with 1013 additions and 5 deletions

View File

@@ -10779,6 +10779,13 @@ public final class Settings {
public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST =
"location_ignore_settings_package_whitelist";
/**
* Whether to throttle location when the device is in doze and still.
* @hide
*/
public static final String LOCATION_ENABLE_STATIONARY_THROTTLE =
"location_enable_stationary_throttle";
/**
* Whether TV will switch to MHL port when a mobile device is plugged in.
* (0 = false, 1 = true)

View File

@@ -569,7 +569,12 @@ public class Location implements Parcelable {
/** @hide */
public long getElapsedRealtimeAgeMillis() {
return NANOSECONDS.toMillis(getElapsedRealtimeAgeNanos());
return getElapsedRealtimeAgeMillis(SystemClock.elapsedRealtime());
}
/** @hide */
public long getElapsedRealtimeAgeMillis(long referenceRealtimeMs) {
return referenceRealtimeMs - NANOSECONDS.toMillis(mElapsedRealtimeNanos);
}
/**

View File

@@ -323,6 +323,7 @@ public class SettingsBackupTest {
Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE,
Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
Settings.Global.LOCK_SOUND,

View File

@@ -78,6 +78,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
import android.provider.Settings;
import android.stats.location.LocationStatsEnums;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
@@ -97,6 +98,8 @@ import com.android.server.location.gnss.hal.GnssNative;
import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.AppOpsHelper;
import com.android.server.location.injector.DeviceIdleHelper;
import com.android.server.location.injector.DeviceStationaryHelper;
import com.android.server.location.injector.EmergencyHelper;
import com.android.server.location.injector.Injector;
import com.android.server.location.injector.LocationAttributionHelper;
@@ -108,6 +111,8 @@ import com.android.server.location.injector.SettingsHelper;
import com.android.server.location.injector.SystemAlarmHelper;
import com.android.server.location.injector.SystemAppForegroundHelper;
import com.android.server.location.injector.SystemAppOpsHelper;
import com.android.server.location.injector.SystemDeviceIdleHelper;
import com.android.server.location.injector.SystemDeviceStationaryHelper;
import com.android.server.location.injector.SystemEmergencyHelper;
import com.android.server.location.injector.SystemLocationPermissionsHelper;
import com.android.server.location.injector.SystemLocationPowerSaveModeHelper;
@@ -120,6 +125,7 @@ import com.android.server.location.provider.LocationProviderManager;
import com.android.server.location.provider.MockLocationProvider;
import com.android.server.location.provider.PassiveLocationProvider;
import com.android.server.location.provider.PassiveLocationProviderManager;
import com.android.server.location.provider.StationaryThrottlingLocationProvider;
import com.android.server.location.provider.proxy.ProxyLocationProvider;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
@@ -313,6 +319,18 @@ public class LocationManagerService extends ILocationManager.Stub {
manager.startManager();
if (realProvider != null) {
// custom logic wrapping all non-passive providers
if (manager != mPassiveManager) {
boolean enableStationaryThrottling = Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, 1) != 0;
if (enableStationaryThrottling) {
realProvider = new StationaryThrottlingLocationProvider(manager.getName(),
mInjector, realProvider, mEventLog);
}
}
manager.setRealProvider(realProvider);
}
mProviderManagers.add(manager);
@@ -1368,6 +1386,8 @@ public class LocationManagerService extends ILocationManager.Stub {
private final SystemAppForegroundHelper mAppForegroundHelper;
private final SystemLocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
private final SystemScreenInteractiveHelper mScreenInteractiveHelper;
private final SystemDeviceStationaryHelper mDeviceStationaryHelper;
private final SystemDeviceIdleHelper mDeviceIdleHelper;
private final LocationAttributionHelper mLocationAttributionHelper;
private final LocationUsageLogger mLocationUsageLogger;
@@ -1391,6 +1411,8 @@ public class LocationManagerService extends ILocationManager.Stub {
mAppForegroundHelper = new SystemAppForegroundHelper(context);
mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog);
mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
mLocationUsageLogger = new LocationUsageLogger();
}
@@ -1450,6 +1472,16 @@ public class LocationManagerService extends ILocationManager.Stub {
return mScreenInteractiveHelper;
}
@Override
public DeviceStationaryHelper getDeviceStationaryHelper() {
return mDeviceStationaryHelper;
}
@Override
public DeviceIdleHelper getDeviceIdleHelper() {
return mDeviceIdleHelper;
}
@Override
public LocationAttributionHelper getLocationAttributionHelper() {
return mLocationAttributionHelper;

View File

@@ -60,7 +60,8 @@ public class LocationEventLog extends LocalEventLog {
private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 9;
private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 9;
private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 10;
@GuardedBy("mAggregateStats")
private final ArrayMap<String, ArrayMap<String, AggregateStats>> mAggregateStats;
@@ -167,6 +168,11 @@ public class LocationEventLog extends LocalEventLog {
getAggregateStats(provider, identity.getPackageName()).markLocationDelivered();
}
/** Logs that a provider has entered or exited stationary throttling. */
public void logProviderStationaryThrottled(String provider, boolean throttled) {
addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled);
}
/** Logs that the location power save mode has changed. */
public void logLocationPowerSaveMode(
@LocationPowerSaveMode int locationPowerSaveMode) {
@@ -198,6 +204,9 @@ public class LocationEventLog extends LocalEventLog {
case EVENT_PROVIDER_DELIVER_LOCATION:
return new ProviderDeliverLocationEvent(timeDelta, (String) args[0],
(Integer) args[1], (CallerIdentity) args[2]);
case EVENT_PROVIDER_STATIONARY_THROTTLED:
return new ProviderStationaryThrottledEvent(timeDelta, (String) args[0],
(Boolean) args[1]);
case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
default:
@@ -332,6 +341,23 @@ public class LocationEventLog extends LocalEventLog {
}
}
private static final class ProviderStationaryThrottledEvent extends ProviderEvent {
private final boolean mStationaryThrottled;
private ProviderStationaryThrottledEvent(long timeDelta, String provider,
boolean stationaryThrottled) {
super(timeDelta, provider);
mStationaryThrottled = stationaryThrottled;
}
@Override
public String getLogString() {
return mProvider + " provider stationary/idle " + (mStationaryThrottled ? "throttled"
: "unthrottled");
}
}
private static final class LocationPowerSaveModeEvent extends LogEvent {
@LocationPowerSaveMode

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2021 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.injector;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Provides accessors and listeners for device idle status.
*/
public abstract class DeviceIdleHelper {
/**
* Listener for device stationary status.
*/
public interface DeviceIdleListener {
/**
* Called when device idle state has changed.
*/
void onDeviceIdleChanged(boolean deviceIdle);
}
private final CopyOnWriteArrayList<DeviceIdleListener> mListeners;
protected DeviceIdleHelper() {
mListeners = new CopyOnWriteArrayList<>();
}
/**
* Adds a listener for device idle status.
*/
public final synchronized void addListener(DeviceIdleListener listener) {
if (mListeners.add(listener) && mListeners.size() == 1) {
registerInternal();
}
}
/**
* Removes a listener for device idle status.
*/
public final synchronized void removeListener(DeviceIdleListener listener) {
if (mListeners.remove(listener) && mListeners.isEmpty()) {
unregisterInternal();
}
}
protected final void notifyDeviceIdleChanged() {
boolean deviceIdle = isDeviceIdle();
for (DeviceIdleListener listener : mListeners) {
listener.onDeviceIdleChanged(deviceIdle);
}
}
protected abstract void registerInternal();
protected abstract void unregisterInternal();
/**
* Returns true if the device is currently idle.
*/
public abstract boolean isDeviceIdle();
}

View File

@@ -0,0 +1,36 @@
/*
* 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.injector;
import com.android.server.DeviceIdleInternal;
/**
* Provides accessors and listeners for device stationary status.
*/
public abstract class DeviceStationaryHelper {
/**
* Adds a listener for stationary status. The listener will be immediately invoked with the
* current stationary status.
*/
public abstract void addListener(DeviceIdleInternal.StationaryListener listener);
/**
* Removes a listener for stationary status.
*/
public abstract void removeListener(DeviceIdleInternal.StationaryListener listener);
}

View File

@@ -48,6 +48,12 @@ public interface Injector {
/** Returns a ScreenInteractiveHelper. */
ScreenInteractiveHelper getScreenInteractiveHelper();
/** Returns a DeviceStationaryHelper. */
DeviceStationaryHelper getDeviceStationaryHelper();
/** Returns a DeviceIdleHelper. */
DeviceIdleHelper getDeviceIdleHelper();
/** Returns a LocationAttributionHelper. */
LocationAttributionHelper getLocationAttributionHelper();

View File

@@ -0,0 +1,70 @@
/*
* 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.injector;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.PowerManager;
import com.android.server.FgThread;
/**
* Provides accessors and listeners for device stationary state.
*/
public class SystemDeviceIdleHelper extends DeviceIdleHelper {
private final Context mContext;
private final PowerManager mPowerManager;
private @Nullable BroadcastReceiver mReceiver;
public SystemDeviceIdleHelper(Context context) {
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
}
@Override
protected void registerInternal() {
if (mReceiver == null) {
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
notifyDeviceIdleChanged();
}
};
mContext.registerReceiver(mReceiver,
new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
FgThread.getHandler());
}
}
@Override
protected void unregisterInternal() {
if (mReceiver != null) {
mContext.unregisterReceiver(mReceiver);
}
}
@Override
public boolean isDeviceIdle() {
return mPowerManager.isDeviceIdleMode();
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.injector;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import java.util.Objects;
/**
* Provides accessors and listeners for device stationary state.
*/
public class SystemDeviceStationaryHelper extends DeviceStationaryHelper {
private final DeviceIdleInternal mDeviceIdle;
public SystemDeviceStationaryHelper() {
mDeviceIdle = Objects.requireNonNull(LocalServices.getService(DeviceIdleInternal.class));
}
@Override
public void addListener(DeviceIdleInternal.StationaryListener listener) {
mDeviceIdle.registerStationaryListener(listener);
}
@Override
public void removeListener(DeviceIdleInternal.StationaryListener listener) {
mDeviceIdle.unregisterStationaryListener(listener);
}
}

View File

@@ -337,8 +337,10 @@ public abstract class AbstractLocationProvider {
@Override
public State setListener(@Nullable Listener listener) {
return mInternalState.updateAndGet(
internalState -> internalState.withListener(listener)).state;
InternalState oldInternalState = mInternalState.getAndUpdate(
internalState -> internalState.withListener(listener));
Preconditions.checkState(listener == null || oldInternalState.listener == null);
return oldInternalState.state;
}
@Override

View File

@@ -0,0 +1,119 @@
/*
* 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 android.location.LocationResult;
import android.location.provider.ProviderRequest;
import android.os.Bundle;
import com.android.internal.util.Preconditions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
/**
* Helper class for wrapping location providers. Subclasses MUST ensure that
* {@link #initializeDelegate()} is invoked before the delegate is used.
*/
class DelegateLocationProvider extends AbstractLocationProvider
implements AbstractLocationProvider.Listener {
private final Object mInitializationLock = new Object();
protected final AbstractLocationProvider mDelegate;
private boolean mInitialized = false;
DelegateLocationProvider(Executor executor, AbstractLocationProvider delegate) {
super(executor, null, null);
mDelegate = delegate;
}
/**
* This function initializes state from the delegate and allows all other callbacks to be
* immediately invoked. If this method is invoked from a subclass constructor, it should be the
* last statement in the constructor since it allows the subclass' reference to escape.
*/
protected void initializeDelegate() {
synchronized (mInitializationLock) {
Preconditions.checkState(!mInitialized);
setState(previousState -> mDelegate.getController().setListener(this));
mInitialized = true;
}
}
// must be invoked in every listener callback to ensure they don't run until initialized
protected final void waitForInitialization() {
// callbacks can start coming as soon as the setListener() call in initializeDelegate
// completes - but we can't allow any to proceed until the setState call afterwards
// completes. acquiring the initialization lock here blocks until initialization is
// complete, and we verify this wasn't called before initializeDelegate for some reason.
synchronized (mInitializationLock) {
Preconditions.checkState(mInitialized);
}
}
@Override
public void onStateChanged(State oldState, State newState) {
waitForInitialization();
setState(previousState -> newState);
}
@Override
public void onReportLocation(LocationResult locationResult) {
waitForInitialization();
reportLocation(locationResult);
}
@Override
protected void onStart() {
Preconditions.checkState(mInitialized);
mDelegate.getController().start();
}
@Override
protected void onStop() {
Preconditions.checkState(mInitialized);
mDelegate.getController().stop();
}
@Override
protected void onSetRequest(ProviderRequest request) {
Preconditions.checkState(mInitialized);
mDelegate.getController().setRequest(request);
}
@Override
protected void onFlush(Runnable callback) {
Preconditions.checkState(mInitialized);
mDelegate.getController().flush(callback);
}
@Override
protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
Preconditions.checkState(mInitialized);
mDelegate.getController().sendExtraCommand(uid, pid, command, extras);
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Preconditions.checkState(mInitialized);
mDelegate.dump(fd, pw, args);
}
}

View File

@@ -0,0 +1,294 @@
/*
* 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 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.eventlog.LocationEventLog;
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;
private final Object mLock = new Object();
private final String mName;
private final DeviceIdleHelper mDeviceIdleHelper;
private final DeviceStationaryHelper mDeviceStationaryHelper;
private final LocationEventLog mEventLog;
@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")
private long mThrottlingIntervalMs = INTERVAL_DISABLED;
@GuardedBy("mLock")
private @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
@GuardedBy("mLock")
private @Nullable Location mLastLocation;
public StationaryThrottlingLocationProvider(String name, Injector injector,
AbstractLocationProvider delegate, LocationEventLog eventLog) {
super(DIRECT_EXECUTOR, delegate);
mName = name;
mDeviceIdleHelper = injector.getDeviceIdleHelper();
mDeviceStationaryHelper = injector.getDeviceStationaryHelper();
mEventLog = eventLog;
// 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");
}
mEventLog.logProviderStationaryThrottled(mName, true);
}
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) {
mEventLog.logProviderStationaryThrottled(mName, false);
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 {
@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));
}
}
}

View File

@@ -140,7 +140,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider {
}
@Override
public void onSetRequest(ProviderRequest request) {
protected void onSetRequest(ProviderRequest request) {
mRequest = request;
mServiceWatcher.runOnBinder(binder -> {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2021 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.injector;
public class FakeDeviceIdleHelper extends DeviceIdleHelper {
private boolean mDeviceIdle = false;
public void setIdle(boolean deviceIdle) {
mDeviceIdle = deviceIdle;
notifyDeviceIdleChanged();
}
@Override
protected void registerInternal() {}
@Override
protected void unregisterInternal() {}
@Override
public boolean isDeviceIdle() {
return mDeviceIdle;
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.injector;
import com.android.server.DeviceIdleInternal;
import java.util.concurrent.CopyOnWriteArrayList;
public class FakeDeviceStationaryHelper extends DeviceStationaryHelper {
private final CopyOnWriteArrayList<DeviceIdleInternal.StationaryListener> mListeners =
new CopyOnWriteArrayList<>();
private boolean mStationary = false;
@Override
public void addListener(DeviceIdleInternal.StationaryListener listener) {
synchronized (mListeners) {
mListeners.add(listener);
listener.onDeviceStationaryChanged(mStationary);
}
}
@Override
public void removeListener(DeviceIdleInternal.StationaryListener listener) {
mListeners.remove(listener);
}
public void setStationary(boolean stationary) {
synchronized (mListeners) {
mStationary = stationary;
}
for (DeviceIdleInternal.StationaryListener listener : mListeners) {
listener.onDeviceStationaryChanged(stationary);
}
}
}

View File

@@ -28,6 +28,8 @@ public class TestInjector implements Injector {
private final FakeAppForegroundHelper mAppForegroundHelper;
private final FakeLocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
private final FakeScreenInteractiveHelper mScreenInteractiveHelper;
private final FakeDeviceStationaryHelper mDeviceStationaryHelper;
private final FakeDeviceIdleHelper mDeviceIdleHelper;
private final LocationAttributionHelper mLocationAttributionHelper;
private final FakeEmergencyHelper mEmergencyHelper;
private final LocationUsageLogger mLocationUsageLogger;
@@ -45,6 +47,8 @@ public class TestInjector implements Injector {
mAppForegroundHelper = new FakeAppForegroundHelper();
mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog);
mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
mDeviceStationaryHelper = new FakeDeviceStationaryHelper();
mDeviceIdleHelper = new FakeDeviceIdleHelper();
mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
mEmergencyHelper = new FakeEmergencyHelper();
mLocationUsageLogger = new LocationUsageLogger();
@@ -90,6 +94,16 @@ public class TestInjector implements Injector {
return mScreenInteractiveHelper;
}
@Override
public FakeDeviceStationaryHelper getDeviceStationaryHelper() {
return mDeviceStationaryHelper;
}
@Override
public FakeDeviceIdleHelper getDeviceIdleHelper() {
return mDeviceIdleHelper;
}
@Override
public LocationAttributionHelper getLocationAttributionHelper() {
return mLocationAttributionHelper;

View File

@@ -0,0 +1,186 @@
/*
* 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 com.android.server.location.LocationUtils.createLocation;
import static com.android.server.location.LocationUtils.createLocationResult;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
import android.location.Location;
import android.location.LocationResult;
import android.location.provider.ProviderRequest;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.TestInjector;
import com.android.server.location.test.FakeProvider;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import java.util.Random;
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class StationaryThrottlingLocationProviderTest {
private static final String TAG = "StationaryThrottlingLocationProviderTest";
private Random mRandom;
private TestInjector mInjector;
private FakeProvider mDelegateProvider;
private @Mock AbstractLocationProvider.Listener mListener;
private @Mock FakeProvider.FakeProviderInterface mDelegate;
private StationaryThrottlingLocationProvider mProvider;
@Before
public void setUp() {
initMocks(this);
long seed = System.currentTimeMillis();
Log.i(TAG, "location random seed: " + seed);
mRandom = new Random(seed);
mInjector = new TestInjector();
mDelegateProvider = new FakeProvider(mDelegate);
mProvider = new StationaryThrottlingLocationProvider("test_provider", mInjector,
mDelegateProvider, new LocationEventLog());
mProvider.getController().setListener(mListener);
mProvider.getController().start();
}
@After
public void tearDown() {
mProvider.getController().setRequest(ProviderRequest.EMPTY_REQUEST);
mProvider.getController().stop();
}
@Test
public void testThrottle() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
mProvider.getController().setRequest(request);
verify(mDelegate).onSetRequest(request);
mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
mInjector.getDeviceStationaryHelper().setStationary(true);
verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
mInjector.getDeviceIdleHelper().setIdle(true);
verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
mInjector.getDeviceStationaryHelper().setStationary(false);
verify(mDelegate, times(2)).onSetRequest(request);
verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
}
@Test
public void testThrottle_NoInitialLocation() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
mProvider.getController().setRequest(request);
verify(mDelegate).onSetRequest(request);
mInjector.getDeviceStationaryHelper().setStationary(true);
mInjector.getDeviceIdleHelper().setIdle(true);
verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
verify(mDelegate, times(1)).onSetRequest(ProviderRequest.EMPTY_REQUEST);
verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
mInjector.getDeviceStationaryHelper().setStationary(false);
verify(mDelegate, times(2)).onSetRequest(request);
verify(mListener, after(75).times(2)).onReportLocation(any(LocationResult.class));
}
@Test
public void testNoThrottle_noLocation() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
mProvider.getController().setRequest(request);
verify(mDelegate).onSetRequest(request);
verify(mListener, never()).onReportLocation(any(LocationResult.class));
mInjector.getDeviceStationaryHelper().setStationary(true);
mInjector.getDeviceIdleHelper().setIdle(true);
verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
verify(mListener, after(75).times(0)).onReportLocation(any(LocationResult.class));
}
@Test
public void testNoThrottle_oldLocation() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
mProvider.getController().setRequest(request);
verify(mDelegate).onSetRequest(request);
Location l = createLocation("test_provider", mRandom);
l.setElapsedRealtimeNanos(0);
LocationResult loc = LocationResult.wrap(l);
mDelegateProvider.reportLocation(loc);
verify(mListener, times(1)).onReportLocation(loc);
mInjector.getDeviceStationaryHelper().setStationary(true);
mInjector.getDeviceIdleHelper().setIdle(true);
verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
}
@Test
public void testNoThrottle_locationSettingsIgnored() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(
50).setLocationSettingsIgnored(true).build();
mProvider.getController().setRequest(request);
verify(mDelegate).onSetRequest(request);
LocationResult loc = createLocationResult("test_provider", mRandom);
mDelegateProvider.reportLocation(loc);
verify(mListener, times(1)).onReportLocation(loc);
mInjector.getDeviceStationaryHelper().setStationary(true);
mInjector.getDeviceIdleHelper().setIdle(true);
verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
}
}