Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5ca5cb6c authored by Michael Wachenschwanz's avatar Michael Wachenschwanz
Browse files

Add Stable Charging Threshold for AppStandby

Delay parole when charging to ensure the device has some time to charge
and avoid a spike is activity on plug in.

Change-Id: If85f097249aeed6b64f43a22f4d25ff0a070febb
Fixes: 78040839
Test: atest AppStandbyControllerTests
parent 46e58e1b
Loading
Loading
Loading
Loading
+17 −5
Original line number Diff line number Diff line
@@ -10601,18 +10601,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_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>
+100 −10
Original line number Diff line number Diff line
@@ -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);
        assertFalse(mController.isAppIdleFilteredOrParoled(PACKAGE_1,USER_ID,
                mInjector.mElapsedRealtime, false));
        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);
        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) {
+55 −9
Original line number Diff line number Diff line
@@ -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();
    }

    /**
@@ -1547,7 +1577,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:
@@ -1559,6 +1589,10 @@ public class AppStandbyController {
                    reportExemptedSyncStart((String) msg.obj, msg.arg1);
                    break;

                case MSG_UPDATE_STABLE_CHARGING:
                    updateChargingStableState();
                    break;

                default:
                    super.handleMessage(msg);
                    break;
@@ -1570,11 +1604,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)) {
            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;
            }
        }
    }
@@ -1618,9 +1657,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";
@@ -1636,12 +1677,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(',');

@@ -1727,6 +1770,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);
            }
        }