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