IdleController.java revision 0529dac587b1361eac359737a5f10972bce5fd0d
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.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.os.SystemClock;
29import android.util.Slog;
30
31import com.android.server.job.JobSchedulerService;
32import com.android.server.job.StateChangedListener;
33
34public class IdleController extends StateController {
35    private static final String TAG = "IdleController";
36
37    private static final String ACTION_TRIGGER_IDLE =
38            "com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE";
39
40    // Policy: we decide that we're "idle" if the device has been unused /
41    // screen off or dreaming for at least this long
42    private long mInactivityIdleThreshold;
43    private long mIdleWindowSlop;
44    final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
45    IdlenessTracker mIdleTracker;
46
47    // Singleton factory
48    private static Object sCreationLock = new Object();
49    private static volatile IdleController sController;
50
51    public static IdleController get(JobSchedulerService service) {
52        synchronized (sCreationLock) {
53            if (sController == null) {
54                sController = new IdleController(service, service.getContext(), service.getLock());
55            }
56            return sController;
57        }
58    }
59
60    private IdleController(StateChangedListener stateChangedListener, Context context,
61                Object lock) {
62        super(stateChangedListener, context, lock);
63        initIdleStateTracking();
64    }
65
66    /**
67     * StateController interface
68     */
69    @Override
70    public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
71        if (taskStatus.hasIdleConstraint()) {
72            mTrackedTasks.add(taskStatus);
73            taskStatus.setIdleConstraintSatisfied(mIdleTracker.isIdle());
74        }
75    }
76
77    @Override
78    public void maybeStopTrackingJobLocked(JobStatus taskStatus, boolean forUpdate) {
79        mTrackedTasks.remove(taskStatus);
80    }
81
82    /**
83     * Interaction with the task manager service
84     */
85    void reportNewIdleState(boolean isIdle) {
86        synchronized (mLock) {
87            for (JobStatus task : mTrackedTasks) {
88                task.setIdleConstraintSatisfied(isIdle);
89            }
90        }
91        mStateChangedListener.onControllerStateChanged();
92    }
93
94    /**
95     * Idle state tracking, and messaging with the task manager when
96     * significant state changes occur
97     */
98    private void initIdleStateTracking() {
99        mInactivityIdleThreshold = mContext.getResources().getInteger(
100                com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold);
101        mIdleWindowSlop = mContext.getResources().getInteger(
102                com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop);
103        mIdleTracker = new IdlenessTracker();
104        mIdleTracker.startTracking();
105    }
106
107    class IdlenessTracker extends BroadcastReceiver {
108        private AlarmManager mAlarm;
109        private PendingIntent mIdleTriggerIntent;
110        boolean mIdle;
111        boolean mScreenOn;
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            mScreenOn = true;
125        }
126
127        public boolean isIdle() {
128            return mIdle;
129        }
130
131        public void startTracking() {
132            IntentFilter filter = new IntentFilter();
133
134            // Screen state
135            filter.addAction(Intent.ACTION_SCREEN_ON);
136            filter.addAction(Intent.ACTION_SCREEN_OFF);
137
138            // Dreaming state
139            filter.addAction(Intent.ACTION_DREAMING_STARTED);
140            filter.addAction(Intent.ACTION_DREAMING_STOPPED);
141
142            // Debugging/instrumentation
143            filter.addAction(ACTION_TRIGGER_IDLE);
144
145            mContext.registerReceiver(this, filter);
146        }
147
148        @Override
149        public void onReceive(Context context, Intent intent) {
150            final String action = intent.getAction();
151
152            if (action.equals(Intent.ACTION_SCREEN_ON)
153                    || action.equals(Intent.ACTION_DREAMING_STOPPED)) {
154                if (DEBUG) {
155                    Slog.v(TAG,"exiting idle : " + action);
156                }
157                mScreenOn = true;
158                //cancel the alarm
159                mAlarm.cancel(mIdleTriggerIntent);
160                if (mIdle) {
161                // possible transition to not-idle
162                    mIdle = false;
163                    reportNewIdleState(mIdle);
164                }
165            } else if (action.equals(Intent.ACTION_SCREEN_OFF)
166                    || action.equals(Intent.ACTION_DREAMING_STARTED)) {
167                // when the screen goes off or dreaming starts, we schedule the
168                // alarm that will tell us when we have decided the device is
169                // truly idle.
170                final long nowElapsed = SystemClock.elapsedRealtime();
171                final long when = nowElapsed + mInactivityIdleThreshold;
172                if (DEBUG) {
173                    Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when="
174                            + when);
175                }
176                mScreenOn = false;
177                mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
178                        when, mIdleWindowSlop, mIdleTriggerIntent);
179            } else if (action.equals(ACTION_TRIGGER_IDLE)) {
180                // idle time starts now. Do not set mIdle if screen is on.
181                if (!mIdle && !mScreenOn) {
182                    if (DEBUG) {
183                        Slog.v(TAG, "Idle trigger fired @ " + SystemClock.elapsedRealtime());
184                    }
185                    mIdle = true;
186                    reportNewIdleState(mIdle);
187                }
188            }
189        }
190    }
191
192    @Override
193    public void dumpControllerStateLocked(PrintWriter pw) {
194        pw.print("Idle: ");
195        pw.println(mIdleTracker.isIdle() ? "true" : "false");
196        pw.println(mTrackedTasks.size());
197        for (int i = 0; i < mTrackedTasks.size(); i++) {
198            final JobStatus js = mTrackedTasks.get(i);
199            pw.print("  ");
200            pw.print(String.valueOf(js.getJobId() + "," + js.getUid()));
201            pw.println("..");
202        }
203    }
204}
205