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

Commit 318abc93 authored by lpeter's avatar lpeter Committed by Peter Li
Browse files

[IdleController] Support dock scenario on idle or active judgement

In the current design, IdleController of Job service considers devices as ‘idle’ after 71 minutes of screen off. But under docking use scenario, devices screen might remain on for a very long time and it’s not necessary implying the device being interactive with users.
So create a mechanism for device to enter the ‘idle’ state that JobScheduler can kick off idle tasks.

Bug: 79183658
Test: atest DeviceStatesTest
Change-Id: I5b307ca51e28ffca63f79a9c43984c3b76e51629
parent 8b49b9f7
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -3986,6 +3986,26 @@ public class Intent implements Parcelable, Cloneable {
     */
    public static final int EXTRA_THERMAL_STATE_EXCEEDED = 2;

    /**
     * Broadcast Action: Indicates the dock in idle state while device is docked.
     *
     * <p class="note">This is a protected intent that can only be sent
     * by the system.
     *
     * @hide
     */
    public static final String ACTION_DOCK_IDLE = "android.intent.action.DOCK_IDLE";

    /**
     * Broadcast Action: Indicates the dock in active state while device is docked.
     *
     * <p class="note">This is a protected intent that can only be sent
     * by the system.
     *
     * @hide
     */
    public static final String ACTION_DOCK_ACTIVE = "android.intent.action.DOCK_ACTIVE";


    // ---------------------------------------------------------------------
    // ---------------------------------------------------------------------
+4 −0
Original line number Diff line number Diff line
@@ -599,6 +599,10 @@
    <protected-broadcast android:name="android.app.action.DATA_SHARING_RESTRICTION_CHANGED" />
    <protected-broadcast android:name="android.app.action.STATSD_STARTED" />

    <!-- For IdleController -->
    <protected-broadcast android:name="android.intent.action.DOCK_IDLE" />
    <protected-broadcast android:name="android.intent.action.DOCK_ACTIVE" />

    <!-- ====================================================================== -->
    <!--                          RUNTIME PERMISSIONS                           -->
    <!-- ====================================================================== -->
+12 −0
Original line number Diff line number Diff line
@@ -2961,6 +2961,18 @@ public class JobSchedulerService extends com.android.server.SystemService
        return 0;
    }

    void triggerDockState(boolean idleState) {
        final Intent dockIntent;
        if (idleState) {
            dockIntent = new Intent(Intent.ACTION_DOCK_IDLE);
        } else {
            dockIntent = new Intent(Intent.ACTION_DOCK_ACTIVE);
        }
        dockIntent.setPackage("android");
        dockIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
        getContext().sendBroadcastAsUser(dockIntent, UserHandle.ALL);
    }

    private String printContextIdToJobMap(JobStatus[] map, String initial) {
        StringBuilder s = new StringBuilder(initial + ": ");
        for (int i=0; i<map.length; i++) {
+27 −0
Original line number Diff line number Diff line
@@ -66,6 +66,8 @@ public final class JobSchedulerShellCommand extends ShellCommand {
                    return getJobState(pw);
                case "heartbeat":
                    return doHeartbeat(pw);
                case "trigger-dock-state":
                    return triggerDockState(pw);
                default:
                    return handleDefaultCommands(cmd);
            }
@@ -349,6 +351,29 @@ public final class JobSchedulerShellCommand extends ShellCommand {
        }
    }

    private int triggerDockState(PrintWriter pw) throws Exception {
        checkPermission("trigger wireless charging dock state");

        final String opt = getNextArgRequired();
        boolean idleState;
        if ("idle".equals(opt)) {
            idleState = true;
        } else if ("active".equals(opt)) {
            idleState = false;
        } else {
            getErrPrintWriter().println("Error: unknown option " + opt);
            return 1;
        }

        final long ident = Binder.clearCallingIdentity();
        try {
            mInternal.triggerDockState(idleState);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
        return 0;
    }

    @Override
    public void onHelp() {
        final PrintWriter pw = getOutPrintWriter();
@@ -403,6 +428,8 @@ public final class JobSchedulerShellCommand extends ShellCommand {
        pw.println("    Options:");
        pw.println("      -u or --user: specify which user's job is to be run; the default is");
        pw.println("         the primary or system user");
        pw.println("  trigger-dock-state [idle|active]");
        pw.println("    Trigger wireless charging dock state.  Active by default.");
        pw.println();
    }

+36 −8
Original line number Diff line number Diff line
@@ -42,7 +42,7 @@ public final class IdleController extends StateController {
            || Log.isLoggable(TAG, Log.DEBUG);

    // Policy: we decide that we're "idle" if the device has been unused /
    // screen off or dreaming for at least this long
    // screen off or dreaming or wireless charging dock idle for at least this long
    private long mInactivityIdleThreshold;
    private long mIdleWindowSlop;
    final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
@@ -105,6 +105,7 @@ public final class IdleController extends StateController {
        // on the main looper thread, either in onReceive() or in an alarm callback.
        private boolean mIdle;
        private boolean mScreenOn;
        private boolean mDockIdle;

        private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> {
            handleIdleTrigger();
@@ -117,6 +118,7 @@ public final class IdleController extends StateController {
            // device in some meaningful way.
            mIdle = false;
            mScreenOn = true;
            mDockIdle = false;
        }

        public boolean isIdle() {
@@ -137,6 +139,10 @@ public final class IdleController extends StateController {
            // Debugging/instrumentation
            filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE);

            // Wireless charging dock state
            filter.addAction(Intent.ACTION_DOCK_IDLE);
            filter.addAction(Intent.ACTION_DOCK_ACTIVE);

            mContext.registerReceiver(this, filter);
        }

@@ -144,11 +150,22 @@ public final class IdleController extends StateController {
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (action.equals(Intent.ACTION_SCREEN_ON)
                    || action.equals(Intent.ACTION_DREAMING_STOPPED)) {
                    || action.equals(Intent.ACTION_DREAMING_STOPPED)
                    || action.equals(Intent.ACTION_DOCK_ACTIVE)) {
                if (action.equals(Intent.ACTION_DOCK_ACTIVE)) {
                    if (!mScreenOn) {
                        // Ignore this intent during screen off
                        return;
                    } else {
                        mDockIdle = false;
                    }
                } else {
                    mScreenOn = true;
                    mDockIdle = false;
                }
                if (DEBUG) {
                    Slog.v(TAG,"exiting idle : " + action);
                }
                mScreenOn = true;
                //cancel the alarm
                mAlarm.cancel(mIdleAlarmListener);
                if (mIdle) {
@@ -157,17 +174,28 @@ public final class IdleController extends StateController {
                    reportNewIdleState(mIdle);
                }
            } else if (action.equals(Intent.ACTION_SCREEN_OFF)
                    || action.equals(Intent.ACTION_DREAMING_STARTED)) {
                // when the screen goes off or dreaming starts, we schedule the
                // alarm that will tell us when we have decided the device is
                    || action.equals(Intent.ACTION_DREAMING_STARTED)
                    || action.equals(Intent.ACTION_DOCK_IDLE)) {
                // when the screen goes off or dreaming starts or wireless charging dock in idle,
                // we schedule the alarm that will tell us when we have decided the device is
                // truly idle.
                if (action.equals(Intent.ACTION_DOCK_IDLE)) {
                    if (!mScreenOn) {
                        // Ignore this intent during screen off
                        return;
                    } else {
                        mDockIdle = true;
                    }
                } else {
                    mScreenOn = false;
                    mDockIdle = false;
                }
                final long nowElapsed = sElapsedRealtimeClock.millis();
                final long when = nowElapsed + mInactivityIdleThreshold;
                if (DEBUG) {
                    Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when="
                            + when);
                }
                mScreenOn = false;
                mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                        when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null);
            } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) {
@@ -177,7 +205,7 @@ public final class IdleController extends StateController {

        private void handleIdleTrigger() {
            // idle time starts now. Do not set mIdle if screen is on.
            if (!mIdle && !mScreenOn) {
            if (!mIdle && (!mScreenOn || mDockIdle)) {
                if (DEBUG) {
                    Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis());
                }