IdleController.java revision be0c4175398ff5d7e13209e833b3037cdd0207d7
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.job.controllers;
18
19import java.io.PrintWriter;
20import java.util.ArrayList;
21
22import android.app.AlarmManager;
23import android.app.PendingIntent;
24import android.content.BroadcastReceiver;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.os.SystemClock;
30import android.util.Slog;
31
32import com.android.server.job.JobSchedulerService;
33import com.android.server.job.StateChangedListener;
34
35public class IdleController extends StateController {
36    private static final String TAG = "IdleController";
37
38    // Policy: we decide that we're "idle" if the device has been unused /
39    // screen off or dreaming for at least this long
40    private static final long INACTIVITY_IDLE_THRESHOLD = 71 * 60 * 1000; // millis; 71 min
41    private static final long IDLE_WINDOW_SLOP = 5 * 60 * 1000; // 5 minute window, to be nice
42
43    private static final String ACTION_TRIGGER_IDLE =
44            "com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE";
45
46    final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
47    IdlenessTracker mIdleTracker;
48
49    // Singleton factory
50    private static Object sCreationLock = new Object();
51    private static volatile IdleController sController;
52
53    public static IdleController get(JobSchedulerService service) {
54        synchronized (sCreationLock) {
55            if (sController == null) {
56                sController = new IdleController(service, service.getContext());
57            }
58            return sController;
59        }
60    }
61
62    private IdleController(StateChangedListener stateChangedListener, Context context) {
63        super(stateChangedListener, context);
64        initIdleStateTracking();
65    }
66
67    /**
68     * StateController interface
69     */
70    @Override
71    public void maybeStartTrackingJob(JobStatus taskStatus) {
72        if (taskStatus.hasIdleConstraint()) {
73            synchronized (mTrackedTasks) {
74                mTrackedTasks.add(taskStatus);
75                taskStatus.idleConstraintSatisfied.set(mIdleTracker.isIdle());
76            }
77        }
78    }
79
80    @Override
81    public void maybeStopTrackingJob(JobStatus taskStatus) {
82        synchronized (mTrackedTasks) {
83            mTrackedTasks.remove(taskStatus);
84        }
85    }
86
87    /**
88     * Interaction with the task manager service
89     */
90    void reportNewIdleState(boolean isIdle) {
91        synchronized (mTrackedTasks) {
92            for (JobStatus task : mTrackedTasks) {
93                task.idleConstraintSatisfied.set(isIdle);
94            }
95        }
96        mStateChangedListener.onControllerStateChanged();
97    }
98
99    /**
100     * Idle state tracking, and messaging with the task manager when
101     * significant state changes occur
102     */
103    private void initIdleStateTracking() {
104        mIdleTracker = new IdlenessTracker();
105        mIdleTracker.startTracking();
106    }
107
108    class IdlenessTracker extends BroadcastReceiver {
109        private AlarmManager mAlarm;
110        private PendingIntent mIdleTriggerIntent;
111        boolean mIdle;
112
113        public IdlenessTracker() {
114            mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
115
116            Intent intent = new Intent(ACTION_TRIGGER_IDLE)
117                    .setPackage("android")
118                    .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
119            mIdleTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
120
121            // At boot we presume that the user has just "interacted" with the
122            // device in some meaningful way.
123            mIdle = false;
124        }
125
126        public boolean isIdle() {
127            return mIdle;
128        }
129
130        public void startTracking() {
131            IntentFilter filter = new IntentFilter();
132
133            // Screen state
134            filter.addAction(Intent.ACTION_SCREEN_ON);
135            filter.addAction(Intent.ACTION_SCREEN_OFF);
136
137            // Dreaming state
138            filter.addAction(Intent.ACTION_DREAMING_STARTED);
139            filter.addAction(Intent.ACTION_DREAMING_STOPPED);
140
141            // Debugging/instrumentation
142            filter.addAction(ACTION_TRIGGER_IDLE);
143
144            mContext.registerReceiver(this, filter);
145        }
146
147        @Override
148        public void onReceive(Context context, Intent intent) {
149            final String action = intent.getAction();
150
151            if (action.equals(Intent.ACTION_SCREEN_ON)
152                    || action.equals(Intent.ACTION_DREAMING_STOPPED)) {
153                // possible transition to not-idle
154                if (mIdle) {
155                    if (DEBUG) {
156                        Slog.v(TAG, "exiting idle : " + action);
157                    }
158                    mAlarm.cancel(mIdleTriggerIntent);
159                    mIdle = false;
160                    reportNewIdleState(mIdle);
161                }
162            } else if (action.equals(Intent.ACTION_SCREEN_OFF)
163                    || action.equals(Intent.ACTION_DREAMING_STARTED)) {
164                // when the screen goes off or dreaming starts, we schedule the
165                // alarm that will tell us when we have decided the device is
166                // truly idle.
167                final long nowElapsed = SystemClock.elapsedRealtime();
168                final long when = nowElapsed + INACTIVITY_IDLE_THRESHOLD;
169                if (DEBUG) {
170                    Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when="
171                            + when);
172                }
173                mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
174                        when, IDLE_WINDOW_SLOP, mIdleTriggerIntent);
175            } else if (action.equals(ACTION_TRIGGER_IDLE)) {
176                // idle time starts now
177                if (!mIdle) {
178                    if (DEBUG) {
179                        Slog.v(TAG, "Idle trigger fired @ " + SystemClock.elapsedRealtime());
180                    }
181                    mIdle = true;
182                    reportNewIdleState(mIdle);
183                }
184            }
185        }
186    }
187
188    @Override
189    public void dumpControllerState(PrintWriter pw) {
190        synchronized (mTrackedTasks) {
191            pw.print("Idle: ");
192            pw.println(mIdleTracker.isIdle() ? "true" : "false");
193            pw.println(mTrackedTasks.size());
194            for (int i = 0; i < mTrackedTasks.size(); i++) {
195                final JobStatus js = mTrackedTasks.get(i);
196                pw.print("  ");
197                pw.print(String.valueOf(js.hashCode()).substring(0, 3));
198                pw.println("..");
199            }
200        }
201    }
202}
203