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

Commit a81d7074 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[JobScheduler] Adding car policy for device idleness that can be redefined in the future"

parents 9af59ee5 2ac4f06f
Loading
Loading
Loading
Loading
+20 −146
Original line number Diff line number Diff line
@@ -16,41 +16,33 @@

package com.android.server.job.controllers;

import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.util.IndentingPrintWriter;
import com.android.server.am.ActivityManagerService;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
import com.android.server.job.controllers.idle.CarIdlenessTracker;
import com.android.server.job.controllers.idle.DeviceIdlenessTracker;
import com.android.server.job.controllers.idle.IdlenessListener;
import com.android.server.job.controllers.idle.IdlenessTracker;

import java.util.function.Predicate;

public final class IdleController extends StateController {
    private static final String TAG = "JobScheduler.Idle";
    private static final boolean DEBUG = JobSchedulerService.DEBUG
            || Log.isLoggable(TAG, Log.DEBUG);

public final class IdleController extends StateController implements IdlenessListener {
    private static final String TAG = "JobScheduler.IdleController";
    // Policy: we decide that we're "idle" if the device has been unused /
    // 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<>();
    IdlenessTracker mIdleTracker;

    public IdleController(JobSchedulerService service) {
        super(service);
        initIdleStateTracking();
        initIdleStateTracking(mContext);
    }

    /**
@@ -74,9 +66,10 @@ public final class IdleController extends StateController {
    }

    /**
     * Interaction with the task manager service
     * State-change notifications from the idleness tracker
     */
    void reportNewIdleState(boolean isIdle) {
    @Override
    public void reportNewIdleState(boolean isIdle) {
        synchronized (mLock) {
            for (int i = mTrackedTasks.size()-1; i >= 0; i--) {
                mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(isIdle);
@@ -89,141 +82,22 @@ public final class IdleController extends StateController {
     * Idle state tracking, and messaging with the task manager when
     * significant state changes occur
     */
    private void initIdleStateTracking() {
        mInactivityIdleThreshold = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold);
        mIdleWindowSlop = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop);
        mIdleTracker = new IdlenessTracker();
        mIdleTracker.startTracking();
    }

    final class IdlenessTracker extends BroadcastReceiver {
        private AlarmManager mAlarm;

        // After construction, mutations of idle/screen-on state will only happen
        // 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();
        };

        public IdlenessTracker() {
            mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);

            // At boot we presume that the user has just "interacted" with the
            // device in some meaningful way.
            mIdle = false;
            mScreenOn = true;
            mDockIdle = false;
        }

        public boolean isIdle() {
            return mIdle;
        }

        public void startTracking() {
            IntentFilter filter = new IntentFilter();

            // Screen state
            filter.addAction(Intent.ACTION_SCREEN_ON);
            filter.addAction(Intent.ACTION_SCREEN_OFF);

            // Dreaming state
            filter.addAction(Intent.ACTION_DREAMING_STARTED);
            filter.addAction(Intent.ACTION_DREAMING_STOPPED);

            // 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);
        }

        @Override
        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_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);
                }
                //cancel the alarm
                mAlarm.cancel(mIdleAlarmListener);
                if (mIdle) {
                // possible transition to not-idle
                    mIdle = false;
                    reportNewIdleState(mIdle);
                }
            } else if (action.equals(Intent.ACTION_SCREEN_OFF)
                    || 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;
                    }
    private void initIdleStateTracking(Context ctx) {
        final boolean isCar = mContext.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_AUTOMOTIVE);
        if (isCar) {
            mIdleTracker = new CarIdlenessTracker();
        } 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);
                }
                mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                        when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null);
            } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) {
                handleIdleTrigger();
            }
        }

        private void handleIdleTrigger() {
            // idle time starts now. Do not set mIdle if screen is on.
            if (!mIdle && (!mScreenOn || mDockIdle)) {
                if (DEBUG) {
                    Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis());
                }
                mIdle = true;
                reportNewIdleState(mIdle);
            } else {
                if (DEBUG) {
                    Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle="
                            + mIdle + " screen=" + mScreenOn);
                }
            }
            mIdleTracker = new DeviceIdlenessTracker();
        }
        mIdleTracker.startTracking(ctx, this);
    }

    @Override
    public void dumpControllerStateLocked(IndentingPrintWriter pw,
            Predicate<JobStatus> predicate) {
        pw.println("Currently idle: " + mIdleTracker.isIdle());
        pw.println("Idleness tracker:"); mIdleTracker.dump(pw);
        pw.println();

        for (int i = 0; i < mTrackedTasks.size(); i++) {
+139 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.server.job.controllers.idle;

import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

import android.util.Log;
import android.util.Slog;
import com.android.server.am.ActivityManagerService;
import com.android.server.job.JobSchedulerService;

import java.io.PrintWriter;

public final class CarIdlenessTracker extends BroadcastReceiver implements IdlenessTracker {
    private static final String TAG = "JobScheduler.CarIdlenessTracker";
    private static final boolean DEBUG = JobSchedulerService.DEBUG
            || Log.isLoggable(TAG, Log.DEBUG);

    public static final String ACTION_FORCE_IDLE = "com.android.server.ACTION_FORCE_IDLE";
    public static final String ACTION_UNFORCE_IDLE = "com.android.server.ACTION_UNFORCE_IDLE";

    // After construction, mutations of idle/screen-on state will only happen
    // on the main looper thread, either in onReceive() or in an alarm callback.
    private boolean mIdle;
    private boolean mScreenOn;
    private IdlenessListener mIdleListener;

    public CarIdlenessTracker() {
        // At boot we presume that the user has just "interacted" with the
        // device in some meaningful way.
        mIdle = false;
        mScreenOn = true;
    }

    @Override
    public boolean isIdle() {
        return mIdle;
    }

    @Override
    public void startTracking(Context context, IdlenessListener listener) {
        mIdleListener = listener;

        IntentFilter filter = new IntentFilter();

        // Screen state
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);

        // Debugging/instrumentation
        filter.addAction(ACTION_FORCE_IDLE);
        filter.addAction(ACTION_UNFORCE_IDLE);
        filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE);

        context.registerReceiver(this, filter);
    }

    @Override
    public void dump(PrintWriter pw) {
        pw.print("  mIdle: "); pw.println(mIdle);
        pw.print("  mScreenOn: "); pw.println(mScreenOn);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        logIfDebug("Received action: " + action);

        // Check for forced actions
        if (action.equals(ACTION_FORCE_IDLE)) {
            logIfDebug("Forcing idle...");
            enterIdleState(true);
        } else if (action.equals(ACTION_UNFORCE_IDLE)) {
            logIfDebug("Unforcing idle...");
            exitIdleState(true);
        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
            logIfDebug("Going idle...");
            mScreenOn = false;
            enterIdleState(false);
        } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
            logIfDebug("exiting idle...");
            mScreenOn = true;
            exitIdleState(true);
        } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) {
            if (!mScreenOn) {
                logIfDebug("Idle trigger fired...");
                enterIdleState(false);
            } else {
                logIfDebug("TRIGGER_IDLE received but not changing state; idle="
                        + mIdle + " screen=" + mScreenOn);
            }
        }
    }

    private void enterIdleState(boolean forced) {
        if (!forced && mIdle) {
            // Already idle and don't need to trigger callbacks since not forced
            logIfDebug("Device is already considered idle");
            return;
        }
        mIdle = true;
        mIdleListener.reportNewIdleState(mIdle);
    }

    private void exitIdleState(boolean forced) {
        if (!forced && !mIdle) {
            // Already out of idle and don't need to trigger callbacks since not forced
            logIfDebug("Device is already considered not idle");
            return;
        }
        mIdle = false;
        mIdleListener.reportNewIdleState(mIdle);
    }

    private void logIfDebug(String msg) {
        if (DEBUG) {
            Slog.v(TAG, msg);
        }
    }
}
 No newline at end of file
+175 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.server.job.controllers.idle;

import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

import android.util.Log;
import android.util.Slog;
import com.android.server.am.ActivityManagerService;
import com.android.server.job.JobSchedulerService;

import java.io.PrintWriter;

public final class DeviceIdlenessTracker extends BroadcastReceiver implements IdlenessTracker {
    private static final String TAG = "JobScheduler.DeviceIdlenessTracker";
    private static final boolean DEBUG = JobSchedulerService.DEBUG
            || Log.isLoggable(TAG, Log.DEBUG);

    private AlarmManager mAlarm;

    // After construction, mutations of idle/screen-on state will only happen
    // on the main looper thread, either in onReceive() or in an alarm callback.
    private long mInactivityIdleThreshold;
    private long mIdleWindowSlop;
    private boolean mIdle;
    private boolean mScreenOn;
    private boolean mDockIdle;
    private IdlenessListener mIdleListener;

    private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> {
        handleIdleTrigger();
    };

    public DeviceIdlenessTracker() {
        // At boot we presume that the user has just "interacted" with the
        // device in some meaningful way.
        mIdle = false;
        mScreenOn = true;
        mDockIdle = false;
    }

    @Override
    public boolean isIdle() {
        return mIdle;
    }

    @Override
    public void startTracking(Context context, IdlenessListener listener) {
        mIdleListener = listener;
        mInactivityIdleThreshold = context.getResources().getInteger(
                com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold);
        mIdleWindowSlop = context.getResources().getInteger(
                com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop);
        mAlarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        IntentFilter filter = new IntentFilter();

        // Screen state
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);

        // Dreaming state
        filter.addAction(Intent.ACTION_DREAMING_STARTED);
        filter.addAction(Intent.ACTION_DREAMING_STOPPED);

        // Debugging/instrumentation
        filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE);

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

        context.registerReceiver(this, filter);
    }

    @Override
    public void dump(PrintWriter pw) {
        pw.print("  mIdle: "); pw.println(mIdle);
        pw.print("  mScreenOn: "); pw.println(mScreenOn);
        pw.print("  mDockIdle: "); pw.println(mDockIdle);
    }

    @Override
    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_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);
            }
            //cancel the alarm
            mAlarm.cancel(mIdleAlarmListener);
            if (mIdle) {
            // possible transition to not-idle
                mIdle = false;
                mIdleListener.reportNewIdleState(mIdle);
            }
        } else if (action.equals(Intent.ACTION_SCREEN_OFF)
                || 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);
            }
            mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null);
        } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) {
            handleIdleTrigger();
        }
    }

    private void handleIdleTrigger() {
        // idle time starts now. Do not set mIdle if screen is on.
        if (!mIdle && (!mScreenOn || mDockIdle)) {
            if (DEBUG) {
                Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis());
            }
            mIdle = true;
            mIdleListener.reportNewIdleState(mIdle);
        } else {
            if (DEBUG) {
                Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle="
                        + mIdle + " screen=" + mScreenOn);
            }
        }
    }
}
 No newline at end of file
+32 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.server.job.controllers.idle;

/**
 * Interface through which an IdlenessTracker informs the job scheduler of
 * changes in the device's inactivity state.
 */
public interface IdlenessListener {
    /**
     * Tell the job scheduler that the device's idle state has changed.
     *
     * @param deviceIsIdle {@code true} to indicate that the device is now considered
     * to be idle; {@code false} to indicate that the device is now being interacted with,
     * so jobs with idle constraints should not be run.
     */
    void reportNewIdleState(boolean deviceIsIdle);
}
+46 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.server.job.controllers.idle;

import android.content.Context;

import java.io.PrintWriter;

public interface IdlenessTracker {
    /**
     * One-time initialization:  this method is called once, after construction of
     * the IdlenessTracker instance.  This is when the tracker should actually begin
     * monitoring whatever signals it consumes in deciding when the device is in a
     * non-interacting state.  When the idle state changes thereafter, the given
     * listener must be called to report the new state.
     */
    void startTracking(Context context, IdlenessListener listener);

    /**
     * Report whether the device is currently considered "idle" for purposes of
     * running scheduled jobs with idleness constraints.
     *
     * @return {@code true} if the job scheduler should consider idleness
     * constraints to be currently satisfied; {@code false} otherwise.
     */
    boolean isIdle();

    /**
     * Dump useful information about tracked idleness-related state in plaintext.
     */
    void dump(PrintWriter pw);
}