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

Commit 851f3d51 authored by Christopher Tate's avatar Christopher Tate
Browse files

Add idle-state controller for the Task Manager

"Idle time" is currently defined to begin ~ an hour (actually slightly
more) after the screen turns off and/or dreaming begins.  Screen-on
and dreams-ending are the triggers for declaring "idle" time to be
over.

Bug 14993295

Change-Id: I0898871d5b76a52d647ae2ebcb1b2f941ec45e79
parent e78e6f92
Loading
Loading
Loading
Loading
+180 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 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.task.controllers;

import java.util.ArrayList;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
import android.util.Slog;

import com.android.server.task.TaskManagerService;

public class IdleController extends StateController {
    private static final String TAG = "IdleController";
    private static final boolean DEBUG = false;

    // Policy: we decide that we're "idle" if the device has been unused /
    // screen off or dreaming for at least this long
    private static final long INACTIVITY_IDLE_THRESHOLD = 71 * 60 * 1000; // millis; 71 min
    private static final long IDLE_WINDOW_SLOP = 5 * 60 * 1000; // 5 minute window, to be nice

    private static final String ACTION_TRIGGER_IDLE =
            "com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE";

    final ArrayList<TaskStatus> mTrackedTasks = new ArrayList<TaskStatus>();
    IdlenessTracker mIdleTracker;

    // Singleton factory
    private static Object sCreationLock = new Object();
    private static volatile IdleController sController;

    public IdleController getController(TaskManagerService service) {
        synchronized (sCreationLock) {
            if (sController == null) {
                sController = new IdleController(service);
            }
            return sController;
        }
    }

    private IdleController(TaskManagerService service) {
        super(service);
        initIdleStateTracking();
    }

    /**
     * StateController interface
     */
    @Override
    public void maybeTrackTaskState(TaskStatus taskStatus) {
        if (taskStatus.hasIdleConstraint()) {
            synchronized (mTrackedTasks) {
                mTrackedTasks.add(taskStatus);
                taskStatus.idleConstraintSatisfied.set(mIdleTracker.isIdle());
            }
        }
    }

    @Override
    public void removeTaskStateIfTracked(TaskStatus taskStatus) {
        synchronized (mTrackedTasks) {
            mTrackedTasks.remove(taskStatus);
        }
    }

    /**
     * Interaction with the task manager service
     */
    void reportNewIdleState(boolean isIdle) {
        synchronized (mTrackedTasks) {
            for (TaskStatus task : mTrackedTasks) {
                task.idleConstraintSatisfied.set(isIdle);
                mStateChangedListener.onTaskStateChanged(task);
            }
        }
    }

    /**
     * Idle state tracking, and messaging with the task manager when
     * significant state changes occur
     */
    private void initIdleStateTracking() {
        mIdleTracker = new IdlenessTracker();
        mIdleTracker.startTracking();
    }

    class IdlenessTracker extends BroadcastReceiver {
        private AlarmManager mAlarm;
        private PendingIntent mIdleTriggerIntent;
        boolean mIdle;

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

            Intent intent = new Intent(ACTION_TRIGGER_IDLE);
            intent.setComponent(new ComponentName(mContext, this.getClass()));
            mIdleTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);

            // at boot we presume that the user has just "interacted" with the
            // device in some meaningful way
            mIdle = 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);

            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)) {
                // possible transition to not-idle
                if (mIdle) {
                    if (DEBUG) {
                        Slog.v(TAG, "exiting idle : " + action);
                    }
                    mAlarm.cancel(mIdleTriggerIntent);
                    mIdle = false;
                    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
                // truly idle.
                long when = SystemClock.elapsedRealtime() + INACTIVITY_IDLE_THRESHOLD;
                if (DEBUG) {
                    Slog.v(TAG, "Scheduling idle : " + action + " when=" + when);
                }
                mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                        when, IDLE_WINDOW_SLOP, mIdleTriggerIntent);
            } else if (action.equals(ACTION_TRIGGER_IDLE)) {
                // idle time starts now
                if (!mIdle) {
                    if (DEBUG) {
                        Slog.v(TAG, "Idle trigger fired @ " + SystemClock.elapsedRealtime());
                    }
                    mIdle = true;
                    reportNewIdleState(mIdle);
                }
            }
        }
    }
}