summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/provider/Settings.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java110
-rw-r--r--services/usage/java/com/android/server/usage/AppStandbyController.java64
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);
}
}