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