diff options
3 files changed, 172 insertions, 24 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 9e56d1425da6..f2d4542e960a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10611,18 +10611,30 @@ public final class Settings { * App standby (app idle) specific settings. * This is encoded as a key=value list, separated by commas. Ex: * <p> - * "idle_duration=5000,parole_interval=4500" + * "idle_duration=5000,parole_interval=4500,screen_thresholds=0/0/60000/120000" * <p> * All durations are in millis. + * Array values are separated by forward slashes * The following keys are supported: * * <pre> - * idle_duration2 (long) - * wallclock_threshold (long) - * parole_interval (long) - * parole_duration (long) + * parole_interval (long) + * parole_window (long) + * parole_duration (long) + * screen_thresholds (long[4]) + * elapsed_thresholds (long[4]) + * strong_usage_duration (long) + * notification_seen_duration (long) + * system_update_usage_duration (long) + * prediction_timeout (long) + * sync_adapter_duration (long) + * exempted_sync_duration (long) + * system_interaction_duration (long) + * stable_charging_threshold (long) * * idle_duration (long) // This is deprecated and used to circumvent b/26355386. + * idle_duration2 (long) // deprecated + * wallclock_threshold (long) // deprecated * </pre> * * <p> diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index c2a0ccfbb810..dee25569705b 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import android.app.usage.UsageEvents; +import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.ContextWrapper; @@ -74,6 +75,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; /** * Unit test for AppStandbyController. @@ -101,6 +104,8 @@ public class AppStandbyControllerTests { private static final long WORKING_SET_THRESHOLD = 12 * HOUR_MS; private static final long FREQUENT_THRESHOLD = 24 * HOUR_MS; private static final long RARE_THRESHOLD = 48 * HOUR_MS; + // Short STABLE_CHARGING_THRESHOLD for testing purposes + private static final long STABLE_CHARGING_THRESHOLD = 2000; private MyInjector mInjector; private AppStandbyController mController; @@ -209,7 +214,8 @@ public class AppStandbyControllerTests { return "screen_thresholds=0/0/0/" + HOUR_MS + ",elapsed_thresholds=0/" + WORKING_SET_THRESHOLD + "/" + FREQUENT_THRESHOLD + "/" - + RARE_THRESHOLD; + + RARE_THRESHOLD + "," + + "stable_charging_threshold=" + STABLE_CHARGING_THRESHOLD; } // Internal methods @@ -276,6 +282,10 @@ public class AppStandbyControllerTests { return controller; } + private long getCurrentTime() { + return TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); + } + @Before public void setUp() throws Exception { MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext()); @@ -284,21 +294,101 @@ public class AppStandbyControllerTests { setChargingState(mController, false); } + private class TestParoleListener extends UsageStatsManagerInternal.AppIdleStateChangeListener { + private boolean mOnParole = false; + private CountDownLatch mLatch; + private long mLastParoleChangeTime; + + public boolean getParoleState() { + synchronized (this) { + return mOnParole; + } + } + + public void rearmLatch() { + synchronized (this) { + mLatch = new CountDownLatch(1); + } + } + + public void awaitOnLatch(long time) throws Exception { + mLatch.await(time, TimeUnit.MILLISECONDS); + } + + public long getLastParoleChangeTime() { + synchronized (this) { + return mLastParoleChangeTime; + } + } + + @Override + public void onAppIdleStateChanged(String packageName, int userId, boolean idle, + int bucket, int reason) { + } + + @Override + public void onParoleStateChanged(boolean isParoleOn) { + synchronized (this) { + // Only record information if it is being looked for + if (mLatch.getCount() > 0) { + mOnParole = isParoleOn; + mLastParoleChangeTime = getCurrentTime(); + mLatch.countDown(); + } + } + } + } + @Test public void testCharging() throws Exception { + long startTime; + TestParoleListener paroleListener = new TestParoleListener(); + long marginOfError = 200; + + // Charging + paroleListener.rearmLatch(); + mController.addListener(paroleListener); + startTime = getCurrentTime(); setChargingState(mController, true); - mInjector.mElapsedRealtime = RARE_THRESHOLD + 1; - assertFalse(mController.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID, - mInjector.mElapsedRealtime, false)); - + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.mOnParole); + // Parole will only be granted after device has been charging for a sufficient amount of + // time. + assertEquals(STABLE_CHARGING_THRESHOLD, + paroleListener.getLastParoleChangeTime() - startTime, + marginOfError); + + // Discharging + paroleListener.rearmLatch(); + startTime = getCurrentTime(); setChargingState(mController, false); - mInjector.mElapsedRealtime = 2 * RARE_THRESHOLD + 2; mController.checkIdleStates(USER_ID); - assertTrue(mController.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID, - mInjector.mElapsedRealtime, false)); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertFalse(paroleListener.getParoleState()); + // Parole should be revoked immediately + assertEquals(0, + paroleListener.getLastParoleChangeTime() - startTime, + marginOfError); + + // Brief Charging + paroleListener.rearmLatch(); + setChargingState(mController, true); + setChargingState(mController, false); + // Device stopped charging before the stable charging threshold. + // Parole should not be granted at the end of the threshold + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertFalse(paroleListener.getParoleState()); + + // Charging Again + paroleListener.rearmLatch(); + startTime = getCurrentTime(); setChargingState(mController, true); - assertFalse(mController.isAppIdleFilteredOrParoled(PACKAGE_1,USER_ID, - mInjector.mElapsedRealtime, false)); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.getParoleState()); + assertTrue(paroleListener.mOnParole); + assertEquals(STABLE_CHARGING_THRESHOLD, + paroleListener.getLastParoleChangeTime() - startTime, + marginOfError); } private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) { diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index 97c5ac911563..08b049669eab 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/services/usage/java/com/android/server/usage/AppStandbyController.java @@ -192,6 +192,7 @@ public class AppStandbyController { /** Check the state of one app: arg1 = userId, arg2 = uid, obj = (String) packageName */ static final int MSG_CHECK_PACKAGE_IDLE_STATE = 11; static final int MSG_REPORT_EXEMPTED_SYNC_START = 12; + static final int MSG_UPDATE_STABLE_CHARGING= 13; long mCheckIdleIntervalMillis; long mAppIdleParoleIntervalMillis; @@ -213,10 +214,13 @@ public class AppStandbyController { long mExemptedSyncAdapterTimeoutMillis; /** Maximum time a system interaction should keep the buckets elevated. */ long mSystemInteractionTimeoutMillis; + /** The length of time phone must be charging before considered stable enough to run jobs */ + long mStableChargingThresholdMillis; volatile boolean mAppIdleEnabled; boolean mAppIdleTempParoled; boolean mCharging; + boolean mChargingStable; private long mLastAppIdleParoledTime; private boolean mSystemServicesReady = false; // There was a system update, defaults need to be initialized after services are ready @@ -297,7 +301,7 @@ public class AppStandbyController { mPackageManager = mContext.getPackageManager(); mDeviceStateReceiver = new DeviceStateReceiver(); - IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING); deviceStates.addAction(BatteryManager.ACTION_DISCHARGING); deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); mContext.registerReceiver(mDeviceStateReceiver, deviceStates); @@ -405,6 +409,27 @@ public class AppStandbyController { synchronized (mAppIdleLock) { if (mCharging != charging) { mCharging = charging; + if (DEBUG) Slog.d(TAG, "Setting mCharging to " + charging); + if (charging) { + if (DEBUG) { + Slog.d(TAG, "Scheduling MSG_UPDATE_STABLE_CHARGING delay = " + + mStableChargingThresholdMillis); + } + mHandler.sendEmptyMessageDelayed(MSG_UPDATE_STABLE_CHARGING, + mStableChargingThresholdMillis); + } else { + mHandler.removeMessages(MSG_UPDATE_STABLE_CHARGING); + updateChargingStableState(); + } + } + } + } + + void updateChargingStableState() { + synchronized (mAppIdleLock) { + if (mChargingStable != mCharging) { + if (DEBUG) Slog.d(TAG, "Setting mChargingStable to " + mCharging); + mChargingStable = mCharging; postParoleStateChanged(); } } @@ -431,7 +456,8 @@ public class AppStandbyController { boolean isParoledOrCharging() { if (!mAppIdleEnabled) return true; synchronized (mAppIdleLock) { - return mAppIdleTempParoled || mCharging; + // Only consider stable charging when determining charge state. + return mAppIdleTempParoled || mChargingStable; } } @@ -1371,11 +1397,15 @@ public class AppStandbyController { pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled); pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled); pw.print(" mCharging="); pw.print(mCharging); + pw.print(" mChargingStable="); pw.print(mChargingStable); pw.print(" mLastAppIdleParoledTime="); TimeUtils.formatDuration(mLastAppIdleParoledTime, pw); pw.println(); pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds)); pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds)); + pw.print("mStableChargingThresholdMillis="); + TimeUtils.formatDuration(mStableChargingThresholdMillis, pw); + pw.println(); } /** @@ -1549,7 +1579,7 @@ public class AppStandbyController { case MSG_PAROLE_STATE_CHANGED: if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled - + ", Charging state:" + mCharging); + + ", Charging state:" + mChargingStable); informParoleStateChanged(); break; case MSG_CHECK_PACKAGE_IDLE_STATE: @@ -1561,6 +1591,10 @@ public class AppStandbyController { reportExemptedSyncStart((String) msg.obj, msg.arg1); break; + case MSG_UPDATE_STABLE_CHARGING: + updateChargingStableState(); + break; + default: super.handleMessage(msg); break; @@ -1572,11 +1606,16 @@ public class AppStandbyController { private class DeviceStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { - setChargingState(intent.getIntExtra("plugged", 0) != 0); - } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) { - onDeviceIdleModeChanged(); + switch (intent.getAction()) { + case BatteryManager.ACTION_CHARGING: + setChargingState(true); + break; + case BatteryManager.ACTION_DISCHARGING: + setChargingState(false); + break; + case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: + onDeviceIdleModeChanged(); + break; } } } @@ -1620,9 +1659,11 @@ public class AppStandbyController { */ @Deprecated private static final String KEY_IDLE_DURATION_OLD = "idle_duration"; - + @Deprecated private static final String KEY_IDLE_DURATION = "idle_duration2"; + @Deprecated private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold"; + private static final String KEY_PAROLE_INTERVAL = "parole_interval"; private static final String KEY_PAROLE_WINDOW = "parole_window"; private static final String KEY_PAROLE_DURATION = "parole_duration"; @@ -1638,12 +1679,14 @@ public class AppStandbyController { private static final String KEY_EXEMPTED_SYNC_HOLD_DURATION = "exempted_sync_duration"; private static final String KEY_SYSTEM_INTERACTION_HOLD_DURATION = "system_interaction_duration"; + private static final String KEY_STABLE_CHARGING_THRESHOLD = "stable_charging_threshold"; public static final long DEFAULT_STRONG_USAGE_TIMEOUT = 1 * ONE_HOUR; public static final long DEFAULT_NOTIFICATION_TIMEOUT = 12 * ONE_HOUR; public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT = 2 * ONE_HOUR; public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT = 10 * ONE_MINUTE; public static final long DEFAULT_SYNC_ADAPTER_TIMEOUT = 10 * ONE_MINUTE; public static final long DEFAULT_EXEMPTED_SYNC_TIMEOUT = 10 * ONE_MINUTE; + public static final long DEFAULT_STABLE_CHARGING_THRESHOLD = 10 * ONE_MINUTE; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -1733,6 +1776,9 @@ public class AppStandbyController { mSystemInteractionTimeoutMillis = mParser.getDurationMillis (KEY_SYSTEM_INTERACTION_HOLD_DURATION, COMPRESS_TIME ? ONE_MINUTE : DEFAULT_SYSTEM_INTERACTION_TIMEOUT); + mStableChargingThresholdMillis = mParser.getDurationMillis + (KEY_STABLE_CHARGING_THRESHOLD, + COMPRESS_TIME ? ONE_MINUTE : DEFAULT_STABLE_CHARGING_THRESHOLD); } } |