From f35ed37018c58d1eb0d8c49cc5c6f7538a373c07 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Tue, 11 Feb 2020 17:32:54 -0800 Subject: [PATCH] Make apps active when charging. This undoes part of the work to remove parole windows from AppStandbyController. In the past, UsageStatsManager.isAppInactive would return false when the device was charging. This wasn't documented behavior, but was intentional, so removing it was the wrong thing to do. I've updated the documentation to explicitly call out the behavior and added it back so that the behavior is consistent with previous versions. Bug: 148988544 Test: atest CtsUsageStatsTestCases:UsageStatsTest Test: atest AppIdleHistoryTests Test: atest AppStandbyControllerTests Change-Id: If5cad80b693a1e043ae64b8206e28abb60f83853 --- .../server/usage/AppStandbyInternal.java | 1 + .../server/usage/AppStandbyController.java | 53 ++++++++++++++---- .../android/app/usage/UsageStatsManager.java | 3 +- .../usage/AppStandbyControllerTests.java | 54 +++++++++++++++++++ 4 files changed, 101 insertions(+), 10 deletions(-) diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index d2d942a4a7e53..dc72d6d9c4b3a 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -85,6 +85,7 @@ public interface AppStandbyInternal { /** * Checks if an app has been idle for a while and filters out apps that are excluded. * It returns false if the current system state allows all apps to be considered active. + * This happens if the device is plugged in or otherwise temporarily allowed to make exceptions. * Called by interface impls. */ boolean isAppIdleFiltered(String packageName, int appId, int userId, diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index f1bfa0411978e..e343478ec61fc 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -48,6 +48,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; +import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import android.annotation.NonNull; @@ -71,9 +72,8 @@ import android.content.pm.ParceledListSlice; import android.database.ContentObserver; import android.hardware.display.DisplayManager; import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkRequest; import android.net.NetworkScoreManager; +import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Build; import android.os.Environment; @@ -285,6 +285,7 @@ public class AppStandbyController implements AppStandbyInternal { long mInitialForegroundServiceStartTimeoutMillis; private volatile boolean mAppIdleEnabled; + private boolean mIsCharging; private boolean mSystemServicesReady = false; // There was a system update, defaults need to be initialized after services are ready private boolean mPendingInitializeDefaults; @@ -360,6 +361,11 @@ public class AppStandbyController implements AppStandbyInternal { mHandler = new AppStandbyHandler(mInjector.getLooper()); mPackageManager = mContext.getPackageManager(); + DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver(); + IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING); + deviceStates.addAction(BatteryManager.ACTION_DISCHARGING); + mContext.registerReceiver(deviceStateReceiver, deviceStates); + synchronized (mAppIdleLock) { mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(), mInjector.elapsedRealtime()); @@ -417,6 +423,8 @@ public class AppStandbyController implements AppStandbyInternal { if (mPendingOneTimeCheckIdleStates) { postOneTimeCheckIdleStates(); } + } else if (phase == PHASE_BOOT_COMPLETED) { + setChargingState(mInjector.isCharging()); } } @@ -515,6 +523,16 @@ public class AppStandbyController implements AppStandbyInternal { appUsage.bucketingReason, false); } + @VisibleForTesting + void setChargingState(boolean isCharging) { + synchronized (mAppIdleLock) { + if (mIsCharging != isCharging) { + if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging); + mIsCharging = isCharging; + } + } + } + @Override public void postCheckIdleStates(int userId) { mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0)); @@ -977,6 +995,11 @@ public class AppStandbyController implements AppStandbyInternal { if (isAppSpecial(packageName, appId, userId)) { return false; } else { + synchronized (mAppIdleLock) { + if (!mAppIdleEnabled || mIsCharging) { + return false; + } + } return isAppIdleUnfiltered(packageName, userId, elapsedRealtime); } } @@ -1543,6 +1566,8 @@ public class AppStandbyController implements AppStandbyInternal { pw.println(); pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled); + pw.print(" mIsCharging="); + pw.print(mIsCharging); pw.println(); pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds)); pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds)); @@ -1560,6 +1585,7 @@ public class AppStandbyController implements AppStandbyInternal { private final Looper mLooper; private IDeviceIdleController mDeviceIdleController; private IBatteryStats mBatteryStats; + private BatteryManager mBatteryManager; private PackageManagerInternal mPackageManagerInternal; private DisplayManager mDisplayManager; private PowerManager mPowerManager; @@ -1593,6 +1619,7 @@ public class AppStandbyController implements AppStandbyInternal { mDisplayManager = (DisplayManager) mContext.getSystemService( Context.DISPLAY_SERVICE); mPowerManager = mContext.getSystemService(PowerManager.class); + mBatteryManager = mContext.getSystemService(BatteryManager.class); final ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); @@ -1630,6 +1657,10 @@ public class AppStandbyController implements AppStandbyInternal { return buildFlag && runtimeFlag; } + boolean isCharging() { + return mBatteryManager.isCharging(); + } + boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException { return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName); } @@ -1766,15 +1797,19 @@ public class AppStandbyController implements AppStandbyInternal { } }; - private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder().build(); - - private final ConnectivityManager.NetworkCallback mNetworkCallback - = new ConnectivityManager.NetworkCallback() { + private class DeviceStateReceiver extends BroadcastReceiver { @Override - public void onAvailable(Network network) { - mConnectivityManager.unregisterNetworkCallback(this); + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case BatteryManager.ACTION_CHARGING: + setChargingState(true); + break; + case BatteryManager.ACTION_DISCHARGING: + setChargingState(false); + break; + } } - }; + } private final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() { diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 5668944dfd4e5..2c701b48455cf 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -599,7 +599,8 @@ public final class UsageStatsManager { /** * Returns whether the specified app is currently considered inactive. This will be true if the * app hasn't been used directly or indirectly for a period of time defined by the system. This - * could be of the order of several hours or days. + * could be of the order of several hours or days. Apps are not considered inactive when the + * device is charging. * @param packageName The package name of the app to query * @return whether the app is currently considered inactive */ 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 e768205c2cf49..9e577636c1b36 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -145,6 +145,7 @@ public class AppStandbyControllerTests { static class MyInjector extends AppStandbyController.Injector { long mElapsedRealtime; boolean mIsAppIdleEnabled = true; + boolean mIsCharging; List mPowerSaveWhitelistExceptIdle = new ArrayList<>(); boolean mDisplayOn; DisplayManager.DisplayListener mDisplayListener; @@ -178,6 +179,11 @@ public class AppStandbyControllerTests { return mIsAppIdleEnabled; } + @Override + boolean isCharging() { + return mIsCharging; + } + @Override boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException { return mPowerSaveWhitelistExceptIdle.contains(packageName); @@ -281,6 +287,13 @@ public class AppStandbyControllerTests { } catch (PackageManager.NameNotFoundException nnfe) {} } + private void setChargingState(AppStandbyController controller, boolean charging) { + mInjector.mIsCharging = charging; + if (controller != null) { + controller.setChargingState(charging); + } + } + private void setAppIdleEnabled(AppStandbyController controller, boolean enabled) { mInjector.mIsAppIdleEnabled = enabled; if (controller != null) { @@ -297,6 +310,7 @@ public class AppStandbyControllerTests { controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); mInjector.setDisplayOn(false); mInjector.setDisplayOn(true); + setChargingState(controller, false); controller.checkIdleStates(USER_ID); assertNotEquals(STANDBY_BUCKET_EXEMPTED, controller.getAppStandbyBucket(PACKAGE_1, USER_ID, @@ -324,6 +338,46 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime, false)); } + @Test + public void testIsAppIdle_Charging() throws Exception { + setChargingState(mController, false); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, + REASON_MAIN_FORCED_BY_SYSTEM); + assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); + assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + + setChargingState(mController, true); + assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); + assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + + setChargingState(mController, false); + assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); + assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + } + + @Test + public void testIsAppIdle_Enabled() throws Exception { + setChargingState(mController, false); + setAppIdleEnabled(mController, true); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, + REASON_MAIN_FORCED_BY_SYSTEM); + assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); + assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + + setAppIdleEnabled(mController, false); + assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); + assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + + setAppIdleEnabled(mController, true); + assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); + assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + } + private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) { mInjector.mElapsedRealtime = elapsedTime; controller.checkIdleStates(USER_ID);