• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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;
18 
19 import android.app.Activity;
20 import android.app.AlarmManager;
21 import android.app.PendingIntent;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.PowerManager;
29 import android.os.PowerManager.WakeLock;
30 import android.os.SystemClock;
31 import android.os.UserHandle;
32 import android.util.Log;
33 
34 /**
35  * This service observes the device state and when applicable sends
36  * broadcasts at the beginning and at the end of a period during which
37  * observers can perform idle maintenance tasks. Typical use of the
38  * idle maintenance is to perform somehow expensive tasks that can be
39  * postponed to a moment when they will not degrade user experience.
40  *
41  * The current implementation is very simple. The start of a maintenance
42  * window is announced if: the screen is off or showing a dream AND the
43  * battery level is more than twenty percent AND at least one hour passed
44  * activity).
45  *
46  * The end of a maintenance window is announced only if: a start was
47  * announced AND the screen turned on or a dream was stopped.
48  */
49 public class IdleMaintenanceService extends BroadcastReceiver {
50 
51     private static final boolean DEBUG = false;
52 
53     private static final String LOG_TAG = IdleMaintenanceService.class.getSimpleName();
54 
55     private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1;
56 
57     private static final long MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS = 24 * 60 * 60 * 1000; // 1 day
58 
59     private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING = 30; // percent
60 
61     private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING = 80; // percent
62 
63     private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING = 20; // percent
64 
65     private static final long MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START = 71 * 60 * 1000; // 71 min
66 
67     private static final long MAX_IDLE_MAINTENANCE_DURATION = 71 * 60 * 1000; // 71 min
68 
69     private static final String ACTION_UPDATE_IDLE_MAINTENANCE_STATE =
70         "com.android.server.IdleMaintenanceService.action.UPDATE_IDLE_MAINTENANCE_STATE";
71 
72     private static final Intent sIdleMaintenanceStartIntent;
73     static {
74         sIdleMaintenanceStartIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_START);
75         sIdleMaintenanceStartIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
76     };
77 
78     private static final Intent sIdleMaintenanceEndIntent;
79     static {
80         sIdleMaintenanceEndIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_END);
81         sIdleMaintenanceEndIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
82     }
83 
84     private final AlarmManager mAlarmService;
85 
86     private final BatteryService mBatteryService;
87 
88     private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent;
89 
90     private final Context mContext;
91 
92     private final WakeLock mWakeLock;
93 
94     private final Handler mHandler;
95 
96     private long mLastIdleMaintenanceStartTimeMillis;
97 
98     private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
99 
100     private boolean mIdleMaintenanceStarted;
101 
IdleMaintenanceService(Context context, BatteryService batteryService)102     public IdleMaintenanceService(Context context, BatteryService batteryService) {
103         mContext = context;
104         mBatteryService = batteryService;
105 
106         mAlarmService = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
107 
108         PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
109         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
110 
111         mHandler = new Handler(mContext.getMainLooper());
112 
113         Intent intent = new Intent(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
114         intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
115         mUpdateIdleMaintenanceStatePendingIntent = PendingIntent.getBroadcast(mContext, 0,
116                 intent, PendingIntent.FLAG_UPDATE_CURRENT);
117 
118         register(mContext.getMainLooper());
119     }
120 
register(Looper looper)121     public void register(Looper looper) {
122         IntentFilter intentFilter = new IntentFilter();
123 
124         // Alarm actions.
125         intentFilter.addAction(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
126 
127         // Battery actions.
128         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
129 
130         // Screen actions.
131         intentFilter.addAction(Intent.ACTION_SCREEN_ON);
132         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
133 
134         // Dream actions.
135         intentFilter.addAction(Intent.ACTION_DREAMING_STARTED);
136         intentFilter.addAction(Intent.ACTION_DREAMING_STOPPED);
137 
138         mContext.registerReceiverAsUser(this, UserHandle.ALL,
139                 intentFilter, null, new Handler(looper));
140     }
141 
scheduleUpdateIdleMaintenanceState(long delayMillis)142     private void scheduleUpdateIdleMaintenanceState(long delayMillis) {
143         final long triggetRealTimeMillis = SystemClock.elapsedRealtime() + delayMillis;
144         mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggetRealTimeMillis,
145                 mUpdateIdleMaintenanceStatePendingIntent);
146     }
147 
unscheduleUpdateIdleMaintenanceState()148     private void unscheduleUpdateIdleMaintenanceState() {
149         mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent);
150     }
151 
updateIdleMaintenanceState()152     private void updateIdleMaintenanceState() {
153         if (mIdleMaintenanceStarted) {
154             // Idle maintenance can be interrupted by user activity, or duration
155             // time out, or low battery.
156             if (!lastUserActivityPermitsIdleMaintenanceRunning()
157                     || !batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
158                 unscheduleUpdateIdleMaintenanceState();
159                 mIdleMaintenanceStarted = false;
160                 EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
161                         mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
162                         isBatteryCharging() ? 1 : 0);
163                 sendIdleMaintenanceEndIntent();
164                 // We stopped since we don't have enough battery or timed out but the
165                 // user is not using the device, so we should be able to run maintenance
166                 // in the next maintenance window since the battery may be charged
167                 // without interaction and the min interval between maintenances passed.
168                 if (!batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
169                     scheduleUpdateIdleMaintenanceState(
170                             getNextIdleMaintenanceIntervalStartFromNow());
171                 }
172             }
173         } else if (deviceStatePermitsIdleMaintenanceStart()
174                 && lastUserActivityPermitsIdleMaintenanceStart()
175                 && lastRunPermitsIdleMaintenanceStart()) {
176             // Now that we started idle maintenance, we should schedule another
177             // update for the moment when the idle maintenance times out.
178             scheduleUpdateIdleMaintenanceState(MAX_IDLE_MAINTENANCE_DURATION);
179             mIdleMaintenanceStarted = true;
180             EventLogTags.writeIdleMaintenanceWindowStart(SystemClock.elapsedRealtime(),
181                     mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
182                     isBatteryCharging() ? 1 : 0);
183             mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime();
184             sendIdleMaintenanceStartIntent();
185         } else if (lastUserActivityPermitsIdleMaintenanceStart()) {
186              if (lastRunPermitsIdleMaintenanceStart()) {
187                 // The user does not use the device and we did not run maintenance in more
188                 // than the min interval between runs, so schedule an update - maybe the
189                 // battery will be charged latter.
190                 scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
191              } else {
192                  // The user does not use the device but we have run maintenance in the min
193                  // interval between runs, so schedule an update after the min interval ends.
194                  scheduleUpdateIdleMaintenanceState(
195                          getNextIdleMaintenanceIntervalStartFromNow());
196              }
197         }
198     }
199 
getNextIdleMaintenanceIntervalStartFromNow()200     private long getNextIdleMaintenanceIntervalStartFromNow() {
201         return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS
202                 - SystemClock.elapsedRealtime();
203     }
204 
sendIdleMaintenanceStartIntent()205     private void sendIdleMaintenanceStartIntent() {
206         mWakeLock.acquire();
207         mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceStartIntent, UserHandle.ALL,
208                 null, this, mHandler, Activity.RESULT_OK, null, null);
209     }
210 
sendIdleMaintenanceEndIntent()211     private void sendIdleMaintenanceEndIntent() {
212         mWakeLock.acquire();
213         mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceEndIntent, UserHandle.ALL,
214                 null, this, mHandler, Activity.RESULT_OK, null, null);
215     }
216 
deviceStatePermitsIdleMaintenanceStart()217     private boolean deviceStatePermitsIdleMaintenanceStart() {
218         final int minBatteryLevel = isBatteryCharging()
219                 ? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING
220                 : MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING;
221         return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
222                 && mBatteryService.getBatteryLevel() > minBatteryLevel);
223     }
224 
lastUserActivityPermitsIdleMaintenanceStart()225     private boolean lastUserActivityPermitsIdleMaintenanceStart() {
226         // The last time the user poked the device is above the threshold.
227         return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
228                 && SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis
229                     > MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
230     }
231 
lastRunPermitsIdleMaintenanceStart()232     private boolean lastRunPermitsIdleMaintenanceStart() {
233         // Enough time passed since the last maintenance run.
234         return SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis
235                 > MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
236     }
237 
lastUserActivityPermitsIdleMaintenanceRunning()238     private boolean lastUserActivityPermitsIdleMaintenanceRunning() {
239         // The user is not using the device.
240         return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID);
241     }
242 
batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()243     private boolean batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning() {
244         // Battery not too low and the maintenance duration did not timeout.
245         return (mBatteryService.getBatteryLevel() > MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING
246                 && mLastIdleMaintenanceStartTimeMillis + MAX_IDLE_MAINTENANCE_DURATION
247                         > SystemClock.elapsedRealtime());
248     }
249 
isBatteryCharging()250     private boolean isBatteryCharging() {
251         return mBatteryService.getPlugType() > 0
252                 && mBatteryService.getInvalidCharger() == 0;
253     }
254 
255     @Override
onReceive(Context context, Intent intent)256     public void onReceive(Context context, Intent intent) {
257         if (DEBUG) {
258             Log.i(LOG_TAG, intent.getAction());
259         }
260         String action = intent.getAction();
261         if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
262             // We care about battery only if maintenance is in progress so we can
263             // stop it if battery is too low. Note that here we assume that the
264             // maintenance clients are properly holding a wake lock. We will
265             // refactor the maintenance to use services instead of intents for the
266             // next release. The only client for this for now is internal an holds
267             // a wake lock correctly.
268             if (mIdleMaintenanceStarted) {
269                 updateIdleMaintenanceState();
270             }
271         } else if (Intent.ACTION_SCREEN_ON.equals(action)
272                 || Intent.ACTION_DREAMING_STOPPED.equals(action)) {
273             mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
274             // Unschedule any future updates since we already know that maintenance
275             // cannot be performed since the user is back.
276             unscheduleUpdateIdleMaintenanceState();
277             // If the screen went on/stopped dreaming, we know the user is using the
278             // device which means that idle maintenance should be stopped if running.
279             updateIdleMaintenanceState();
280         } else if (Intent.ACTION_SCREEN_OFF.equals(action)
281                 || Intent.ACTION_DREAMING_STARTED.equals(action)) {
282             mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime();
283             // If screen went off/started dreaming, we may be able to start idle maintenance
284             // after the minimal user inactivity elapses. We schedule an alarm for when
285             // this timeout elapses since the device may go to sleep by then.
286             scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
287         } else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) {
288             updateIdleMaintenanceState();
289         } else if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)
290                 || Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) {
291             // We were holding a wake lock while broadcasting the idle maintenance
292             // intents but now that we finished the broadcast release the wake lock.
293             mWakeLock.release();
294         }
295     }
296 }
297