blob: b0a1aca37d7cacb80b39e0a759103a819514eb7a [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import android.app.Activity;
import android.app.ActivityManagerNative;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
/**
* This service observes the device state and when applicable sends
* broadcasts at the beginning and at the end of a period during which
* observers can perform idle maintenance tasks. Typical use of the
* idle maintenance is to perform somehow expensive tasks that can be
* postponed to a moment when they will not degrade user experience.
*
* The current implementation is very simple. The start of a maintenance
* window is announced if: the screen is off or showing a dream AND the
* battery level is more than twenty percent AND at least one hour passed
* activity).
*
* The end of a maintenance window is announced only if: a start was
* announced AND the screen turned on or a dream was stopped.
*/
public class IdleMaintenanceService extends BroadcastReceiver {
private static final boolean DEBUG = false;
private static final String LOG_TAG = IdleMaintenanceService.class.getSimpleName();
private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1;
private static final long MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS = 24 * 60 * 60 * 1000; // 1 day
private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING = 30; // percent
private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING = 80; // percent
private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING = 20; // percent
private static final long MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START = 71 * 60 * 1000; // 71 min
private static final long MAX_IDLE_MAINTENANCE_DURATION = 71 * 60 * 1000; // 71 min
private static final String ACTION_UPDATE_IDLE_MAINTENANCE_STATE =
"com.android.server.IdleMaintenanceService.action.UPDATE_IDLE_MAINTENANCE_STATE";
private static final String ACTION_FORCE_IDLE_MAINTENANCE =
"com.android.server.IdleMaintenanceService.action.FORCE_IDLE_MAINTENANCE";
private static final Intent sIdleMaintenanceStartIntent;
static {
sIdleMaintenanceStartIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_START);
sIdleMaintenanceStartIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
};
private static final Intent sIdleMaintenanceEndIntent;
static {
sIdleMaintenanceEndIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_END);
sIdleMaintenanceEndIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
private final AlarmManager mAlarmService;
private final BatteryService mBatteryService;
private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent;
private final Context mContext;
private final WakeLock mWakeLock;
private final Handler mHandler;
private long mLastIdleMaintenanceStartTimeMillis;
private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
private boolean mIdleMaintenanceStarted;
public IdleMaintenanceService(Context context, BatteryService batteryService) {
mContext = context;
mBatteryService = batteryService;
mAlarmService = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
mHandler = new Handler(mContext.getMainLooper());
Intent intent = new Intent(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mUpdateIdleMaintenanceStatePendingIntent = PendingIntent.getBroadcast(mContext, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
register(mHandler);
}
public void register(Handler handler) {
IntentFilter intentFilter = new IntentFilter();
// Alarm actions.
intentFilter.addAction(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
// Battery actions.
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
// Screen actions.
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
// Dream actions.
intentFilter.addAction(Intent.ACTION_DREAMING_STARTED);
intentFilter.addAction(Intent.ACTION_DREAMING_STOPPED);
mContext.registerReceiverAsUser(this, UserHandle.ALL,
intentFilter, null, mHandler);
intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_FORCE_IDLE_MAINTENANCE);
mContext.registerReceiverAsUser(this, UserHandle.ALL,
intentFilter, android.Manifest.permission.SET_ACTIVITY_WATCHER, mHandler);
}
private void scheduleUpdateIdleMaintenanceState(long delayMillis) {
final long triggetRealTimeMillis = SystemClock.elapsedRealtime() + delayMillis;
mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggetRealTimeMillis,
mUpdateIdleMaintenanceStatePendingIntent);
}
private void unscheduleUpdateIdleMaintenanceState() {
mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent);
}
private void updateIdleMaintenanceState(boolean noisy) {
if (mIdleMaintenanceStarted) {
// Idle maintenance can be interrupted by user activity, or duration
// time out, or low battery.
if (!lastUserActivityPermitsIdleMaintenanceRunning()
|| !batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
unscheduleUpdateIdleMaintenanceState();
mIdleMaintenanceStarted = false;
EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
isBatteryCharging() ? 1 : 0);
sendIdleMaintenanceEndIntent();
// We stopped since we don't have enough battery or timed out but the
// user is not using the device, so we should be able to run maintenance
// in the next maintenance window since the battery may be charged
// without interaction and the min interval between maintenances passed.
if (!batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
scheduleUpdateIdleMaintenanceState(
getNextIdleMaintenanceIntervalStartFromNow());
}
}
} else if (deviceStatePermitsIdleMaintenanceStart(noisy)
&& lastUserActivityPermitsIdleMaintenanceStart(noisy)
&& lastRunPermitsIdleMaintenanceStart(noisy)) {
// Now that we started idle maintenance, we should schedule another
// update for the moment when the idle maintenance times out.
scheduleUpdateIdleMaintenanceState(MAX_IDLE_MAINTENANCE_DURATION);
mIdleMaintenanceStarted = true;
EventLogTags.writeIdleMaintenanceWindowStart(SystemClock.elapsedRealtime(),
mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
isBatteryCharging() ? 1 : 0);
mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime();
sendIdleMaintenanceStartIntent();
} else if (lastUserActivityPermitsIdleMaintenanceStart(noisy)) {
if (lastRunPermitsIdleMaintenanceStart(noisy)) {
// The user does not use the device and we did not run maintenance in more
// than the min interval between runs, so schedule an update - maybe the
// battery will be charged latter.
scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
} else {
// The user does not use the device but we have run maintenance in the min
// interval between runs, so schedule an update after the min interval ends.
scheduleUpdateIdleMaintenanceState(
getNextIdleMaintenanceIntervalStartFromNow());
}
}
}
private long getNextIdleMaintenanceIntervalStartFromNow() {
return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS
- SystemClock.elapsedRealtime();
}
private void sendIdleMaintenanceStartIntent() {
mWakeLock.acquire();
try {
ActivityManagerNative.getDefault().performIdleMaintenance();
} catch (RemoteException e) {
}
mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceStartIntent, UserHandle.ALL,
null, this, mHandler, Activity.RESULT_OK, null, null);
}
private void sendIdleMaintenanceEndIntent() {
mWakeLock.acquire();
mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceEndIntent, UserHandle.ALL,
null, this, mHandler, Activity.RESULT_OK, null, null);
}
private boolean deviceStatePermitsIdleMaintenanceStart(boolean noisy) {
final int minBatteryLevel = isBatteryCharging()
? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING
: MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING;
boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
&& mBatteryService.getBatteryLevel() > minBatteryLevel);
if (!allowed && noisy) {
Slog.i("IdleMaintenance", "Idle maintenance not allowed due to power");
}
return allowed;
}
private boolean lastUserActivityPermitsIdleMaintenanceStart(boolean noisy) {
// The last time the user poked the device is above the threshold.
boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
&& SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis
> MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
if (!allowed && noisy) {
Slog.i("IdleMaintenance", "Idle maintenance not allowed due to last user activity");
}
return allowed;
}
private boolean lastRunPermitsIdleMaintenanceStart(boolean noisy) {
// Enough time passed since the last maintenance run.
boolean allowed = SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis
> MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
if (!allowed && noisy) {
Slog.i("IdleMaintenance", "Idle maintenance not allowed due time since last");
}
return allowed;
}
private boolean lastUserActivityPermitsIdleMaintenanceRunning() {
// The user is not using the device.
return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID);
}
private boolean batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning() {
// Battery not too low and the maintenance duration did not timeout.
return (mBatteryService.getBatteryLevel() > MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING
&& mLastIdleMaintenanceStartTimeMillis + MAX_IDLE_MAINTENANCE_DURATION
> SystemClock.elapsedRealtime());
}
private boolean isBatteryCharging() {
return mBatteryService.getPlugType() > 0
&& mBatteryService.getInvalidCharger() == 0;
}
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) {
Log.i(LOG_TAG, intent.getAction());
}
String action = intent.getAction();
if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
// We care about battery only if maintenance is in progress so we can
// stop it if battery is too low. Note that here we assume that the
// maintenance clients are properly holding a wake lock. We will
// refactor the maintenance to use services instead of intents for the
// next release. The only client for this for now is internal an holds
// a wake lock correctly.
if (mIdleMaintenanceStarted) {
updateIdleMaintenanceState(false);
}
} else if (Intent.ACTION_SCREEN_ON.equals(action)
|| Intent.ACTION_DREAMING_STOPPED.equals(action)) {
mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
// Unschedule any future updates since we already know that maintenance
// cannot be performed since the user is back.
unscheduleUpdateIdleMaintenanceState();
// If the screen went on/stopped dreaming, we know the user is using the
// device which means that idle maintenance should be stopped if running.
updateIdleMaintenanceState(false);
} else if (Intent.ACTION_SCREEN_OFF.equals(action)
|| Intent.ACTION_DREAMING_STARTED.equals(action)) {
mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime();
// If screen went off/started dreaming, we may be able to start idle maintenance
// after the minimal user inactivity elapses. We schedule an alarm for when
// this timeout elapses since the device may go to sleep by then.
scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
} else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) {
updateIdleMaintenanceState(false);
} else if (ACTION_FORCE_IDLE_MAINTENANCE.equals(action)) {
long now = SystemClock.elapsedRealtime() - 1;
mLastUserActivityElapsedTimeMillis = now - MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START;
mLastIdleMaintenanceStartTimeMillis = now - MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
updateIdleMaintenanceState(true);
} else if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)
|| Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) {
// We were holding a wake lock while broadcasting the idle maintenance
// intents but now that we finished the broadcast release the wake lock.
mWakeLock.release();
}
}
}