Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 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 | |
Christopher Tate | 7060b04 | 2014-06-09 19:50:00 -0700 | [diff] [blame] | 17 | package com.android.server.job.controllers; |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 18 | |
Matthew Williams | effacfa | 2014-06-05 20:56:40 -0700 | [diff] [blame] | 19 | import java.io.PrintWriter; |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 20 | import java.util.ArrayList; |
| 21 | |
| 22 | import android.app.AlarmManager; |
| 23 | import android.app.PendingIntent; |
| 24 | import android.content.BroadcastReceiver; |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 25 | import android.content.Context; |
| 26 | import android.content.Intent; |
| 27 | import android.content.IntentFilter; |
| 28 | import android.os.SystemClock; |
| 29 | import android.util.Slog; |
| 30 | |
Christopher Tate | 7060b04 | 2014-06-09 19:50:00 -0700 | [diff] [blame] | 31 | import com.android.server.job.JobSchedulerService; |
| 32 | import com.android.server.job.StateChangedListener; |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 33 | |
| 34 | public class IdleController extends StateController { |
| 35 | private static final String TAG = "IdleController"; |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 36 | |
| 37 | // Policy: we decide that we're "idle" if the device has been unused / |
| 38 | // screen off or dreaming for at least this long |
| 39 | private static final long INACTIVITY_IDLE_THRESHOLD = 71 * 60 * 1000; // millis; 71 min |
| 40 | private static final long IDLE_WINDOW_SLOP = 5 * 60 * 1000; // 5 minute window, to be nice |
| 41 | |
| 42 | private static final String ACTION_TRIGGER_IDLE = |
| 43 | "com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE"; |
| 44 | |
Christopher Tate | 7060b04 | 2014-06-09 19:50:00 -0700 | [diff] [blame] | 45 | final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>(); |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 46 | IdlenessTracker mIdleTracker; |
| 47 | |
| 48 | // Singleton factory |
| 49 | private static Object sCreationLock = new Object(); |
| 50 | private static volatile IdleController sController; |
| 51 | |
Christopher Tate | 7060b04 | 2014-06-09 19:50:00 -0700 | [diff] [blame] | 52 | public static IdleController get(JobSchedulerService service) { |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 53 | synchronized (sCreationLock) { |
| 54 | if (sController == null) { |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 55 | sController = new IdleController(service, service.getContext()); |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 56 | } |
| 57 | return sController; |
| 58 | } |
| 59 | } |
| 60 | |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 61 | private IdleController(StateChangedListener stateChangedListener, Context context) { |
| 62 | super(stateChangedListener, context); |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 63 | initIdleStateTracking(); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * StateController interface |
| 68 | */ |
| 69 | @Override |
Christopher Tate | 7060b04 | 2014-06-09 19:50:00 -0700 | [diff] [blame] | 70 | public void maybeStartTrackingJob(JobStatus taskStatus) { |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 71 | if (taskStatus.hasIdleConstraint()) { |
| 72 | synchronized (mTrackedTasks) { |
| 73 | mTrackedTasks.add(taskStatus); |
| 74 | taskStatus.idleConstraintSatisfied.set(mIdleTracker.isIdle()); |
| 75 | } |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | @Override |
Christopher Tate | 7060b04 | 2014-06-09 19:50:00 -0700 | [diff] [blame] | 80 | public void maybeStopTrackingJob(JobStatus taskStatus) { |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 81 | synchronized (mTrackedTasks) { |
| 82 | mTrackedTasks.remove(taskStatus); |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * Interaction with the task manager service |
| 88 | */ |
| 89 | void reportNewIdleState(boolean isIdle) { |
| 90 | synchronized (mTrackedTasks) { |
Christopher Tate | 7060b04 | 2014-06-09 19:50:00 -0700 | [diff] [blame] | 91 | for (JobStatus task : mTrackedTasks) { |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 92 | task.idleConstraintSatisfied.set(isIdle); |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 93 | } |
| 94 | } |
Matthew Williams | 9b9244b6 | 2014-05-14 11:06:04 -0700 | [diff] [blame] | 95 | mStateChangedListener.onControllerStateChanged(); |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Idle state tracking, and messaging with the task manager when |
| 100 | * significant state changes occur |
| 101 | */ |
| 102 | private void initIdleStateTracking() { |
| 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 | |
| 112 | public IdlenessTracker() { |
| 113 | mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); |
| 114 | |
Matthew Williams | be0c417 | 2014-08-06 18:14:16 -0700 | [diff] [blame] | 115 | Intent intent = new Intent(ACTION_TRIGGER_IDLE) |
| 116 | .setPackage("android") |
| 117 | .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 118 | mIdleTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); |
| 119 | |
Matthew Williams | be0c417 | 2014-08-06 18:14:16 -0700 | [diff] [blame] | 120 | // At boot we presume that the user has just "interacted" with the |
| 121 | // device in some meaningful way. |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 122 | mIdle = false; |
| 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 | |
Christopher Tate | b9583c9 | 2014-07-24 17:03:22 -0700 | [diff] [blame] | 140 | // Debugging/instrumentation |
| 141 | filter.addAction(ACTION_TRIGGER_IDLE); |
| 142 | |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 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 | // possible transition to not-idle |
| 153 | if (mIdle) { |
| 154 | if (DEBUG) { |
| 155 | Slog.v(TAG, "exiting idle : " + action); |
| 156 | } |
| 157 | mAlarm.cancel(mIdleTriggerIntent); |
| 158 | mIdle = false; |
| 159 | reportNewIdleState(mIdle); |
| 160 | } |
| 161 | } else if (action.equals(Intent.ACTION_SCREEN_OFF) |
| 162 | || action.equals(Intent.ACTION_DREAMING_STARTED)) { |
| 163 | // when the screen goes off or dreaming starts, we schedule the |
| 164 | // alarm that will tell us when we have decided the device is |
| 165 | // truly idle. |
Matthew Williams | be0c417 | 2014-08-06 18:14:16 -0700 | [diff] [blame] | 166 | final long nowElapsed = SystemClock.elapsedRealtime(); |
| 167 | final long when = nowElapsed + INACTIVITY_IDLE_THRESHOLD; |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 168 | if (DEBUG) { |
Matthew Williams | be0c417 | 2014-08-06 18:14:16 -0700 | [diff] [blame] | 169 | Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when=" |
| 170 | + when); |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 171 | } |
| 172 | mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| 173 | when, IDLE_WINDOW_SLOP, mIdleTriggerIntent); |
| 174 | } else if (action.equals(ACTION_TRIGGER_IDLE)) { |
| 175 | // idle time starts now |
| 176 | if (!mIdle) { |
| 177 | if (DEBUG) { |
| 178 | Slog.v(TAG, "Idle trigger fired @ " + SystemClock.elapsedRealtime()); |
| 179 | } |
| 180 | mIdle = true; |
| 181 | reportNewIdleState(mIdle); |
| 182 | } |
| 183 | } |
| 184 | } |
| 185 | } |
Matthew Williams | effacfa | 2014-06-05 20:56:40 -0700 | [diff] [blame] | 186 | |
| 187 | @Override |
| 188 | public void dumpControllerState(PrintWriter pw) { |
Christopher Tate | 5eeb59c | 2014-07-22 10:50:22 -0700 | [diff] [blame] | 189 | synchronized (mTrackedTasks) { |
| 190 | pw.print("Idle: "); |
| 191 | pw.println(mIdleTracker.isIdle() ? "true" : "false"); |
| 192 | pw.println(mTrackedTasks.size()); |
| 193 | for (int i = 0; i < mTrackedTasks.size(); i++) { |
| 194 | final JobStatus js = mTrackedTasks.get(i); |
| 195 | pw.print(" "); |
| 196 | pw.print(String.valueOf(js.hashCode()).substring(0, 3)); |
| 197 | pw.println(".."); |
| 198 | } |
| 199 | } |
Matthew Williams | effacfa | 2014-06-05 20:56:40 -0700 | [diff] [blame] | 200 | } |
Christopher Tate | 851f3d51 | 2014-05-14 17:01:58 -0700 | [diff] [blame] | 201 | } |