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 17 package com.android.server.job.controllers; 18 19 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 20 21 import android.app.AlarmManager; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.os.UserHandle; 27 import android.util.ArraySet; 28 import android.util.Log; 29 import android.util.Slog; 30 import android.util.proto.ProtoOutputStream; 31 32 import com.android.internal.util.IndentingPrintWriter; 33 import com.android.server.am.ActivityManagerService; 34 import com.android.server.job.JobSchedulerService; 35 import com.android.server.job.StateControllerProto; 36 37 import java.util.function.Predicate; 38 39 public final class IdleController extends StateController { 40 private static final String TAG = "JobScheduler.Idle"; 41 private static final boolean DEBUG = JobSchedulerService.DEBUG 42 || Log.isLoggable(TAG, Log.DEBUG); 43 44 // Policy: we decide that we're "idle" if the device has been unused / 45 // screen off or dreaming or wireless charging dock idle for at least this long 46 private long mInactivityIdleThreshold; 47 private long mIdleWindowSlop; 48 final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>(); 49 IdlenessTracker mIdleTracker; 50 51 public IdleController(JobSchedulerService service) { 52 super(service); 53 initIdleStateTracking(); 54 } 55 56 /** 57 * StateController interface 58 */ 59 @Override 60 public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) { 61 if (taskStatus.hasIdleConstraint()) { 62 mTrackedTasks.add(taskStatus); 63 taskStatus.setTrackingController(JobStatus.TRACKING_IDLE); 64 taskStatus.setIdleConstraintSatisfied(mIdleTracker.isIdle()); 65 } 66 } 67 68 @Override 69 public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, 70 boolean forUpdate) { 71 if (taskStatus.clearTrackingController(JobStatus.TRACKING_IDLE)) { 72 mTrackedTasks.remove(taskStatus); 73 } 74 } 75 76 /** 77 * Interaction with the task manager service 78 */ 79 void reportNewIdleState(boolean isIdle) { 80 synchronized (mLock) { 81 for (int i = mTrackedTasks.size()-1; i >= 0; i--) { 82 mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(isIdle); 83 } 84 } 85 mStateChangedListener.onControllerStateChanged(); 86 } 87 88 /** 89 * Idle state tracking, and messaging with the task manager when 90 * significant state changes occur 91 */ 92 private void initIdleStateTracking() { 93 mInactivityIdleThreshold = mContext.getResources().getInteger( 94 com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold); 95 mIdleWindowSlop = mContext.getResources().getInteger( 96 com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop); 97 mIdleTracker = new IdlenessTracker(); 98 mIdleTracker.startTracking(); 99 } 100 101 final class IdlenessTracker extends BroadcastReceiver { 102 private AlarmManager mAlarm; 103 104 // After construction, mutations of idle/screen-on state will only happen 105 // on the main looper thread, either in onReceive() or in an alarm callback. 106 private boolean mIdle; 107 private boolean mScreenOn; 108 private boolean mDockIdle; 109 110 private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> { 111 handleIdleTrigger(); 112 }; 113 114 public IdlenessTracker() { 115 mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 116 117 // At boot we presume that the user has just "interacted" with the 118 // device in some meaningful way. 119 mIdle = false; 120 mScreenOn = true; 121 mDockIdle = false; 122 } 123 124 public boolean isIdle() { 125 return mIdle; 126 } 127 128 public void startTracking() { 129 IntentFilter filter = new IntentFilter(); 130 131 // Screen state 132 filter.addAction(Intent.ACTION_SCREEN_ON); 133 filter.addAction(Intent.ACTION_SCREEN_OFF); 134 135 // Dreaming state 136 filter.addAction(Intent.ACTION_DREAMING_STARTED); 137 filter.addAction(Intent.ACTION_DREAMING_STOPPED); 138 139 // Debugging/instrumentation 140 filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE); 141 142 // Wireless charging dock state 143 filter.addAction(Intent.ACTION_DOCK_IDLE); 144 filter.addAction(Intent.ACTION_DOCK_ACTIVE); 145 146 mContext.registerReceiver(this, filter); 147 } 148 149 @Override 150 public void onReceive(Context context, Intent intent) { 151 final String action = intent.getAction(); 152 if (action.equals(Intent.ACTION_SCREEN_ON) 153 || action.equals(Intent.ACTION_DREAMING_STOPPED) 154 || action.equals(Intent.ACTION_DOCK_ACTIVE)) { 155 if (action.equals(Intent.ACTION_DOCK_ACTIVE)) { 156 if (!mScreenOn) { 157 // Ignore this intent during screen off 158 return; 159 } else { 160 mDockIdle = false; 161 } 162 } else { 163 mScreenOn = true; 164 mDockIdle = false; 165 } 166 if (DEBUG) { 167 Slog.v(TAG,"exiting idle : " + action); 168 } 169 //cancel the alarm 170 mAlarm.cancel(mIdleAlarmListener); 171 if (mIdle) { 172 // possible transition to not-idle 173 mIdle = false; 174 reportNewIdleState(mIdle); 175 } 176 } else if (action.equals(Intent.ACTION_SCREEN_OFF) 177 || action.equals(Intent.ACTION_DREAMING_STARTED) 178 || action.equals(Intent.ACTION_DOCK_IDLE)) { 179 // when the screen goes off or dreaming starts or wireless charging dock in idle, 180 // we schedule the alarm that will tell us when we have decided the device is 181 // truly idle. 182 if (action.equals(Intent.ACTION_DOCK_IDLE)) { 183 if (!mScreenOn) { 184 // Ignore this intent during screen off 185 return; 186 } else { 187 mDockIdle = true; 188 } 189 } else { 190 mScreenOn = false; 191 mDockIdle = false; 192 } 193 final long nowElapsed = sElapsedRealtimeClock.millis(); 194 final long when = nowElapsed + mInactivityIdleThreshold; 195 if (DEBUG) { 196 Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when=" 197 + when); 198 } 199 mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, 200 when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null); 201 } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) { 202 handleIdleTrigger(); 203 } 204 } 205 206 private void handleIdleTrigger() { 207 // idle time starts now. Do not set mIdle if screen is on. 208 if (!mIdle && (!mScreenOn || mDockIdle)) { 209 if (DEBUG) { 210 Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis()); 211 } 212 mIdle = true; 213 reportNewIdleState(mIdle); 214 } else { 215 if (DEBUG) { 216 Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle=" 217 + mIdle + " screen=" + mScreenOn); 218 } 219 } 220 } 221 } 222 223 @Override 224 public void dumpControllerStateLocked(IndentingPrintWriter pw, 225 Predicate<JobStatus> predicate) { 226 pw.println("Currently idle: " + mIdleTracker.isIdle()); 227 pw.println(); 228 229 for (int i = 0; i < mTrackedTasks.size(); i++) { 230 final JobStatus js = mTrackedTasks.valueAt(i); 231 if (!predicate.test(js)) { 232 continue; 233 } 234 pw.print("#"); 235 js.printUniqueId(pw); 236 pw.print(" from "); 237 UserHandle.formatUid(pw, js.getSourceUid()); 238 pw.println(); 239 } 240 } 241 242 @Override 243 public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, 244 Predicate<JobStatus> predicate) { 245 final long token = proto.start(fieldId); 246 final long mToken = proto.start(StateControllerProto.IDLE); 247 248 proto.write(StateControllerProto.IdleController.IS_IDLE, mIdleTracker.isIdle()); 249 250 for (int i = 0; i < mTrackedTasks.size(); i++) { 251 final JobStatus js = mTrackedTasks.valueAt(i); 252 if (!predicate.test(js)) { 253 continue; 254 } 255 final long jsToken = proto.start(StateControllerProto.IdleController.TRACKED_JOBS); 256 js.writeToShortProto(proto, StateControllerProto.IdleController.TrackedJob.INFO); 257 proto.write(StateControllerProto.IdleController.TrackedJob.SOURCE_UID, 258 js.getSourceUid()); 259 proto.end(jsToken); 260 } 261 262 proto.end(mToken); 263 proto.end(token); 264 } 265 } 266