summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSvetoslav <svetoslavganov@google.com>2013-05-03 11:24:26 -0700
committerSvetoslav <svetoslavganov@google.com>2013-05-03 14:03:06 -0700
commit6a08a12b8e8ef6fa04932c7a1c5255e3f158a3c8 (patch)
treef7995a3cce7ac5cb68f1355555d94536ad632835
parent48f2b548edf8e276cc89d2c4c2d2936cde46fc95 (diff)
downloadframeworks_base-6a08a12b8e8ef6fa04932c7a1c5255e3f158a3c8.zip
frameworks_base-6a08a12b8e8ef6fa04932c7a1c5255e3f158a3c8.tar.gz
frameworks_base-6a08a12b8e8ef6fa04932c7a1c5255e3f158a3c8.tar.bz2
Idle maintenance scheduling broken.
1. The scheduling was relying on receiving battery level broadcasts which however are not sent if the device is asleep. The maintenance window was not bound and we could miss a frame if the user did not interact the device longer than the min time between two maintenance windows. 2. Hide the idle maintenance intents since this will be rewritten to user services. bug:8688454 Change-Id: I17b421b09823cb46ec218cabda19e02432d94f8c
-rw-r--r--api/current.txt2
-rw-r--r--core/java/android/content/Intent.java4
-rw-r--r--services/java/com/android/server/BatteryService.java9
-rw-r--r--services/java/com/android/server/IdleMaintenanceService.java186
-rw-r--r--services/java/com/android/server/SystemServer.java2
5 files changed, 151 insertions, 52 deletions
diff --git a/api/current.txt b/api/current.txt
index 1166546..21c1ef0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6061,8 +6061,6 @@ package android.content {
field public static final java.lang.String ACTION_GTALK_SERVICE_CONNECTED = "android.intent.action.GTALK_CONNECTED";
field public static final java.lang.String ACTION_GTALK_SERVICE_DISCONNECTED = "android.intent.action.GTALK_DISCONNECTED";
field public static final java.lang.String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG";
- field public static final java.lang.String ACTION_IDLE_MAINTENANCE_END = "android.intent.action.ACTION_IDLE_MAINTENANCE_END";
- field public static final java.lang.String ACTION_IDLE_MAINTENANCE_START = "android.intent.action.ACTION_IDLE_MAINTENANCE_START";
field public static final java.lang.String ACTION_INPUT_METHOD_CHANGED = "android.intent.action.INPUT_METHOD_CHANGED";
field public static final java.lang.String ACTION_INSERT = "android.intent.action.INSERT";
field public static final java.lang.String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 67bd952..bfc7bf5 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2356,6 +2356,8 @@ public class Intent implements Parcelable, Cloneable {
* </p>
*
* @see #ACTION_IDLE_MAINTENANCE_END
+ *
+ * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_IDLE_MAINTENANCE_START =
@@ -2383,6 +2385,8 @@ public class Intent implements Parcelable, Cloneable {
* by the system.
*
* @see #ACTION_IDLE_MAINTENANCE_START
+ *
+ * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_IDLE_MAINTENANCE_END =
diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java
index dbffa97..1f2947d 100644
--- a/services/java/com/android/server/BatteryService.java
+++ b/services/java/com/android/server/BatteryService.java
@@ -236,6 +236,15 @@ public final class BatteryService extends Binder {
}
}
+ /**
+ * Returns a non-zero value if an unsupported charger is attached.
+ */
+ public int getInvalidCharger() {
+ synchronized (mLock) {
+ return mInvalidCharger;
+ }
+ }
+
private void shutdownIfNoPowerLocked() {
// shut down gracefully if our battery is critically low and we are not powered.
// wait until the system has booted before attempting to display the shutdown dialog.
diff --git a/services/java/com/android/server/IdleMaintenanceService.java b/services/java/com/android/server/IdleMaintenanceService.java
index 0c90de4..584d4bc 100644
--- a/services/java/com/android/server/IdleMaintenanceService.java
+++ b/services/java/com/android/server/IdleMaintenanceService.java
@@ -17,11 +17,12 @@
package com.android.server;
import android.app.Activity;
+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.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
@@ -40,7 +41,6 @@ import android.util.Log;
* 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
- * since the screen went off or a dream started (i.e. since the last user
* activity).
*
* The end of a maintenance window is announced only if: a start was
@@ -48,27 +48,44 @@ import android.util.Log;
*/
public class IdleMaintenanceService extends BroadcastReceiver {
- private final boolean DEBUG = false;
+ 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 MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
+ 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 = 10; // percent
+ private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING = 20; // percent
- private static final long MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START = 60 * 60 * 1000; // 1 hour
+ private static final long MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START = 71 * 60 * 1000; // 71 min
- private final Intent mIdleMaintenanceStartIntent =
- new Intent(Intent.ACTION_IDLE_MAINTENANCE_START);
+ private static final long MAX_IDLE_MAINTENANCE_DURATION = 71 * 60 * 1000; // 71 min
- private final Intent mIdleMaintenanceEndIntent =
- new Intent(Intent.ACTION_IDLE_MAINTENANCE_END);
+ private static final String ACTION_UPDATE_IDLE_MAINTENANCE_STATE =
+ "com.android.server.IdleMaintenanceService.action.UPDATE_IDLE_MAINTENANCE_STATE";
+
+ 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;
@@ -76,30 +93,37 @@ public class IdleMaintenanceService extends BroadcastReceiver {
private final Handler mHandler;
- private long mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime();
+ private long mLastIdleMaintenanceStartTimeMillis;
private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
- private int mBatteryLevel;
-
- private boolean mBatteryCharging;
-
private boolean mIdleMaintenanceStarted;
- public IdleMaintenanceService(Context context) {
+ 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(mContext.getMainLooper());
}
public void register(Looper looper) {
IntentFilter intentFilter = new IntentFilter();
+ // Alarm actions.
+ intentFilter.addAction(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
+
// Battery actions.
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
@@ -115,67 +139,117 @@ public class IdleMaintenanceService extends BroadcastReceiver {
intentFilter, null, new Handler(looper));
}
+ 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() {
if (mIdleMaintenanceStarted) {
- // Idle maintenance can be interrupted only by
- // a change of the device state.
- if (!deviceStatePermitsIdleMaintenanceRunning()) {
+ // 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, mBatteryLevel,
- mBatteryCharging ? 1 : 0);
+ 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()
&& lastUserActivityPermitsIdleMaintenanceStart()
&& lastRunPermitsIdleMaintenanceStart()) {
+ // 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, mBatteryLevel,
- mBatteryCharging ? 1 : 0);
+ mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
+ isBatteryCharging() ? 1 : 0);
mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime();
sendIdleMaintenanceStartIntent();
+ } else if (lastUserActivityPermitsIdleMaintenanceStart()) {
+ if (lastRunPermitsIdleMaintenanceStart()) {
+ // 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() {
- if (DEBUG) {
- Log.i(LOG_TAG, "Broadcasting " + Intent.ACTION_IDLE_MAINTENANCE_START);
- }
mWakeLock.acquire();
- mContext.sendOrderedBroadcastAsUser(mIdleMaintenanceStartIntent, UserHandle.ALL,
+ mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceStartIntent, UserHandle.ALL,
null, this, mHandler, Activity.RESULT_OK, null, null);
}
private void sendIdleMaintenanceEndIntent() {
- if (DEBUG) {
- Log.i(LOG_TAG, "Broadcasting " + Intent.ACTION_IDLE_MAINTENANCE_END);
- }
mWakeLock.acquire();
- mContext.sendOrderedBroadcastAsUser(mIdleMaintenanceEndIntent, UserHandle.ALL,
+ mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceEndIntent, UserHandle.ALL,
null, this, mHandler, Activity.RESULT_OK, null, null);
}
private boolean deviceStatePermitsIdleMaintenanceStart() {
- final int minBatteryLevel = mBatteryCharging
+ final int minBatteryLevel = isBatteryCharging()
? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING
: MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING;
return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
- && mBatteryLevel > minBatteryLevel);
+ && mBatteryService.getBatteryLevel() > minBatteryLevel);
}
- private boolean deviceStatePermitsIdleMaintenanceRunning() {
+ private boolean lastUserActivityPermitsIdleMaintenanceStart() {
+ // The last time the user poked the device is above the threshold.
return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
- && mBatteryLevel > MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING);
+ && SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis
+ > MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
}
- private boolean lastUserActivityPermitsIdleMaintenanceStart() {
- return (SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis
- > MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
+ private boolean lastRunPermitsIdleMaintenanceStart() {
+ // Enough time passed since the last maintenance run.
+ return SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis
+ > MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
}
- private boolean lastRunPermitsIdleMaintenanceStart() {
- return SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis > MILLIS_IN_DAY;
+ 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
@@ -185,24 +259,38 @@ public class IdleMaintenanceService extends BroadcastReceiver {
}
String action = intent.getAction();
if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
- final int maxBatteryLevel = intent.getExtras().getInt(BatteryManager.EXTRA_SCALE);
- final int currBatteryLevel = intent.getExtras().getInt(BatteryManager.EXTRA_LEVEL);
- mBatteryLevel = (int) (((float) maxBatteryLevel / 100) * currBatteryLevel);
- final int pluggedState = intent.getExtras().getInt(BatteryManager.EXTRA_PLUGGED);
- final int chargerState = intent.getExtras().getInt(
- BatteryManager.EXTRA_INVALID_CHARGER, 0);
- mBatteryCharging = (pluggedState > 0 && chargerState == 0);
+ // 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();
+ }
} 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();
} 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();
} 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();
- return;
}
- updateIdleMaintenanceState();
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 7994b56..9455017 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -745,7 +745,7 @@ class ServerThread extends Thread {
try {
Slog.i(TAG, "IdleMaintenanceService");
- new IdleMaintenanceService(context);
+ new IdleMaintenanceService(context, battery);
} catch (Throwable e) {
reportWtf("starting IdleMaintenanceService", e);
}