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