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