diff options
| author | 2015-05-08 17:21:48 -0700 | |
|---|---|---|
| committer | 2015-05-11 15:16:47 -0700 | |
| commit | 8d66b3fbf5d8036e25d64a8472bcd2b6b7892a1a (patch) | |
| tree | cc4aa1432f4abecd3b7d34275945a0c1c678fd34 | |
| parent | cfd6e9dfd063cba795497f251aa6f7fe2554f10b (diff) | |
Implement device idle in power manager.
When in device idle mode, we now prevent most apps
from being able to hold partial wake locks. The
device idle controller now pushes its white list of
app uids into the power manager, so it can apply this
policy correctly to only apps that are not whitelisted.
The implementation adds a new "disabled" flag to a wake
lock which is set when we want to apply this policy. When
set, we ensure that we tell battery stats that the wake lock
is not being held and ignore that wake lock when computing
the power state summary.
Also add new SDK APIs to schedule alarms that are allowed
to execute while in idle mode.
Finally add new dumpsys commands to the device idle controller
to completely disable and re-enable its operation, to use
for testing.
Change-Id: I1f16672c6ac06d03bb538f9854d5843db9aa6f27
| -rw-r--r-- | api/current.txt | 2 | ||||
| -rw-r--r-- | api/system-current.txt | 2 | ||||
| -rw-r--r-- | core/java/android/app/AlarmManager.java | 93 | ||||
| -rw-r--r-- | core/java/android/os/PowerManagerInternal.java | 2 | ||||
| -rw-r--r-- | services/core/java/com/android/server/DeviceIdleController.java | 54 | ||||
| -rw-r--r-- | services/core/java/com/android/server/power/PowerManagerService.java | 93 |
6 files changed, 225 insertions, 21 deletions
diff --git a/api/current.txt b/api/current.txt index 0ef476632ee3..99db3c538254 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3762,7 +3762,9 @@ package android.app { method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock(); method public void set(int, long, android.app.PendingIntent); method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent); + method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent); method public void setExact(int, long, android.app.PendingIntent); + method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent); method public void setInexactRepeating(int, long, long, android.app.PendingIntent); method public void setRepeating(int, long, long, android.app.PendingIntent); method public void setTime(long); diff --git a/api/system-current.txt b/api/system-current.txt index 3aa6ca0cd473..b4cc56e5e7f4 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3852,7 +3852,9 @@ package android.app { method public void set(int, long, android.app.PendingIntent); method public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource); method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent); + method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent); method public void setExact(int, long, android.app.PendingIntent); + method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent); method public void setInexactRepeating(int, long, long, android.app.PendingIntent); method public void setRepeating(int, long, long, android.app.PendingIntent); method public void setTime(long); diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index b0fda9c25190..5e7bd0dc39f8 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -71,10 +71,7 @@ import java.io.IOException; * {@link android.content.Context#getSystemService * Context.getSystemService(Context.ALARM_SERVICE)}. */ -public class AlarmManager -{ - private static final String TAG = "AlarmManager"; - +public class AlarmManager { /** * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()} * (wall clock time in UTC), which will wake up the device when @@ -558,7 +555,93 @@ public class AlarmManager long intervalMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, null); } - + + /** + * Like {@link #set(int, long, PendingIntent)}, but this alarm will be allowed to execute + * even when the system is in low-power idle modes. This type of alarm must <b>only</b> + * be used for situations where it is actually required that the alarm go off while in + * idle -- a reasonable example would be for a calendar notification that should make a + * sound so the user is aware of it. These alarms can significantly impact the power use + * of the device when idle (and thus cause significant battery blame to the app scheduling + * them), so they should be used with care. + * + * <p>Unlike other alarms, the system is free to reschedule this type of alarm to happen + * out of order with any other alarms, even those from the same app. This will clearly happen + * when the device is idle (since this alarm can go off while idle, when any other alarms + * from the app will be held until later), but may also happen even when not idle.</p> + * + * <p>Regardless of the app's target SDK version, this call always allows batching of the + * alarm.</p> + * + * @param type One of {@link #ELAPSED_REALTIME}, {@link #ELAPSED_REALTIME_WAKEUP}, + * {@link #RTC}, or {@link #RTC_WAKEUP}. + * @param triggerAtMillis time in milliseconds that the alarm should go + * off, using the appropriate clock (depending on the alarm type). + * @param operation Action to perform when the alarm goes off; + * typically comes from {@link PendingIntent#getBroadcast + * IntentSender.getBroadcast()}. + * + * @see #set(int, long, PendingIntent) + * @see #setExactAndAllowWhileIdle + * @see #cancel + * @see android.content.Context#sendBroadcast + * @see android.content.Context#registerReceiver + * @see android.content.Intent#filterEquals + * @see #ELAPSED_REALTIME + * @see #ELAPSED_REALTIME_WAKEUP + * @see #RTC + * @see #RTC_WAKEUP + */ + public void setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) { + setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, operation, + null, null); + } + + /** + * Like {@link #setExact(int, long, PendingIntent)}, but this alarm will be allowed to execute + * even when the system is in low-power idle modes. If you don't need exact scheduling of + * the alarm but still need to execute while idle, consider using + * {@link #setAndAllowWhileIdle}. This type of alarm must <b>only</b> + * be used for situations where it is actually required that the alarm go off while in + * idle -- a reasonable example would be for a calendar notification that should make a + * sound so the user is aware of it. These alarms can significantly impact the power use + * of the device when idle (and thus cause significant battery blame to the app scheduling + * them), so they should be used with care. + * + * <p>Unlike other alarms, the system is free to reschedule this type of alarm to happen + * out of order with any other alarms, even those from the same app. This will clearly happen + * when the device is idle (since this alarm can go off while idle, when any other alarms + * from the app will be held until later), but may also happen even when not idle. + * Note that the OS will allow itself more flexibility for scheduling these alarms than + * regular exact alarms, since the application has opted into this behavior. When the + * device is idle it may take even more liberties with scheduling in order to optimize + * for battery life.</p> + * + * @param type One of {@link #ELAPSED_REALTIME}, {@link #ELAPSED_REALTIME_WAKEUP}, + * {@link #RTC}, or {@link #RTC_WAKEUP}. + * @param triggerAtMillis time in milliseconds that the alarm should go + * off, using the appropriate clock (depending on the alarm type). + * @param operation Action to perform when the alarm goes off; + * typically comes from {@link PendingIntent#getBroadcast + * IntentSender.getBroadcast()}. + * + * @see #set + * @see #setRepeating + * @see #setWindow + * @see #cancel + * @see android.content.Context#sendBroadcast + * @see android.content.Context#registerReceiver + * @see android.content.Intent#filterEquals + * @see #ELAPSED_REALTIME + * @see #ELAPSED_REALTIME_WAKEUP + * @see #RTC + * @see #RTC_WAKEUP + */ + public void setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) { + setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation, + null, null); + } + /** * Remove any alarms with a matching {@link Intent}. * Any alarm, of any type, whose Intent matches this one (as defined by diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 00ab262fda4e..9a0d0d094d25 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -134,4 +134,6 @@ public abstract class PowerManagerInternal { } public abstract void setDeviceIdleMode(boolean enabled); + + public abstract void setDeviceIdleWhitelist(int[] appids); } diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index b7bc0f02ea4f..76465d45a180 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -142,6 +142,7 @@ public class DeviceIdleController extends SystemService { private PendingIntent mAlarmIntent; private Intent mIdleIntent; private Display mCurDisplay; + private boolean mIdleDisabled; private boolean mScreenOn; private boolean mCharging; private boolean mSigMotionActive; @@ -187,10 +188,16 @@ public class DeviceIdleController extends SystemService { private final ArrayMap<String, Integer> mPowerSaveWhitelistUserApps = new ArrayMap<>(); /** - * UIDs that have been white-listed to opt out of power save restrictions. + * App IDs that have been white-listed to opt out of power save restrictions. */ private final SparseBooleanArray mPowerSaveWhitelistAppIds = new SparseBooleanArray(); + /** + * Current app IDs that are in the complete power save white list. This array can + * be shared with others because it will not be modified once set. + */ + private int[] mPowerSaveWhitelistAppIdArray = new int[0]; + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { @@ -381,6 +388,8 @@ public class DeviceIdleController extends SystemService { filter.addAction(ACTION_STEP_IDLE_STATE); getContext().registerReceiver(mReceiver, filter); + mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAppIdArray); + mDisplayManager.registerDisplayListener(mDisplayListener, null); updateDisplayLocked(); } @@ -445,12 +454,7 @@ public class DeviceIdleController extends SystemService { public int[] getAppIdWhitelistInternal() { synchronized (this) { - int size = mPowerSaveWhitelistAppIds.size(); - int[] appids = new int[size]; - for (int i = 0; i < size; i++) { - appids[i] = mPowerSaveWhitelistAppIds.keyAt(i); - } - return appids; + return mPowerSaveWhitelistAppIdArray; } } @@ -499,7 +503,7 @@ public class DeviceIdleController extends SystemService { } void becomeInactiveIfAppropriateLocked() { - if (!mScreenOn && !mCharging && mState == STATE_ACTIVE) { + if (!mScreenOn && !mCharging && !mIdleDisabled && mState == STATE_ACTIVE) { // Screen has turned off; we are now going to become inactive and start // waiting to see if we will ultimately go idle. mState = STATE_INACTIVE; @@ -625,6 +629,15 @@ public class DeviceIdleController extends SystemService { for (int i=0; i<mPowerSaveWhitelistUserApps.size(); i++) { mPowerSaveWhitelistAppIds.put(mPowerSaveWhitelistUserApps.valueAt(i), true); } + int size = mPowerSaveWhitelistAppIds.size(); + int[] appids = new int[size]; + for (int i = 0; i < size; i++) { + appids[i] = mPowerSaveWhitelistAppIds.keyAt(i); + } + mPowerSaveWhitelistAppIdArray = appids; + if (mLocalPowerManager != null) { + mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAppIdArray); + } } private void reportPowerSaveWhitelistChangedLocked() { @@ -763,6 +776,10 @@ public class DeviceIdleController extends SystemService { pw.println("Commands:"); pw.println(" step"); pw.println(" Immediately step to next state, without waiting for alarm."); + pw.println(" disable"); + pw.println(" Completely disable device idle mode."); + pw.println(" enable"); + pw.println(" Re-enable device idle mode after it had previously been disabled."); pw.println(" whitelist"); pw.println(" Add (prefix with +) or remove (prefix with -) packages."); } @@ -782,12 +799,32 @@ public class DeviceIdleController extends SystemService { if ("-h".equals(arg)) { dumpHelp(pw); return; + } else if ("-a".equals(arg)) { + // Ignore, we always dump all. } else if ("step".equals(arg)) { synchronized (this) { stepIdleStateLocked(); pw.print("Stepped to: "); pw.println(stateToString(mState)); } return; + } else if ("disable".equals(arg)) { + synchronized (this) { + if (!mIdleDisabled) { + mIdleDisabled = true; + becomeActiveLocked("disabled"); + pw.println("Idle mode disabled"); + } + } + return; + } else if ("enable".equals(arg)) { + synchronized (this) { + if (mIdleDisabled) { + mIdleDisabled = false; + becomeInactiveIfAppropriateLocked(); + pw.println("Idle mode enabled"); + } + } + return; } else if ("whitelist".equals(arg)) { i++; while (i < args.length) { @@ -853,6 +890,7 @@ public class DeviceIdleController extends SystemService { } pw.print(" mSigMotionSensor="); pw.println(mSigMotionSensor); pw.print(" mCurDisplay="); pw.println(mCurDisplay); + pw.print(" mIdleDisabled="); pw.println(mIdleDisabled); pw.print(" mScreenOn="); pw.println(mScreenOn); pw.print(" mCharging="); pw.println(mCharging); pw.print(" mSigMotionActive="); pw.println(mSigMotionActive); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index f790f75096da..decca169ef44 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -69,6 +69,7 @@ import android.view.WindowManagerPolicy; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import libcore.util.Objects; @@ -423,6 +424,9 @@ public final class PowerManagerService extends SystemService // True if we are currently in device idle mode. private boolean mDeviceIdleMode; + // Set of app ids that we will always respect the wake locks for. + int[] mDeviceIdleWhitelist = new int[0]; + // True if theater mode is enabled private boolean mTheaterModeEnabled; @@ -758,6 +762,7 @@ public final class PowerManagerService extends SystemService throw new IllegalArgumentException("Wake lock is already dead."); } mWakeLocks.add(wakeLock); + setWakeLockDisabledStateLocked(wakeLock); notifyAcquire = true; } @@ -894,7 +899,7 @@ public final class PowerManagerService extends SystemService } private void notifyWakeLockAcquiredLocked(WakeLock wakeLock) { - if (mSystemReady) { + if (mSystemReady && !wakeLock.mDisabled) { wakeLock.mNotifiedAcquired = true; mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource, @@ -1388,7 +1393,10 @@ public final class PowerManagerService extends SystemService final WakeLock wakeLock = mWakeLocks.get(i); switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { case PowerManager.PARTIAL_WAKE_LOCK: - mWakeLockSummary |= WAKE_LOCK_CPU; + if (!wakeLock.mDisabled) { + // We only respect this if the wake lock is not disabled. + mWakeLockSummary |= WAKE_LOCK_CPU; + } break; case PowerManager.FULL_WAKE_LOCK: mWakeLockSummary |= WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT; @@ -2248,12 +2256,12 @@ public final class PowerManagerService extends SystemService } } - private void setStayOnSettingInternal(int val) { + void setStayOnSettingInternal(int val) { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.STAY_ON_WHILE_PLUGGED_IN, val); } - private void setMaximumScreenOffTimeoutFromDeviceAdminInternal(int timeMs) { + void setMaximumScreenOffTimeoutFromDeviceAdminInternal(int timeMs) { synchronized (mLock) { mMaximumScreenOffTimeoutFromDeviceAdmin = timeMs; mDirty |= DIRTY_SETTINGS; @@ -2261,6 +2269,69 @@ public final class PowerManagerService extends SystemService } } + void setDeviceIdleModeInternal(boolean enabled) { + synchronized (mLock) { + if (mDeviceIdleMode != enabled) { + mDeviceIdleMode = enabled; + updateWakeLockDisabledStatesLocked(); + } + } + } + + void setDeviceIdleWhitelistInternal(int[] appids) { + synchronized (mLock) { + mDeviceIdleWhitelist = appids; + if (mDeviceIdleMode) { + updateWakeLockDisabledStatesLocked(); + } + } + } + + private void updateWakeLockDisabledStatesLocked() { + boolean changed = false; + final int numWakeLocks = mWakeLocks.size(); + for (int i = 0; i < numWakeLocks; i++) { + final WakeLock wakeLock = mWakeLocks.get(i); + if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) + == PowerManager.PARTIAL_WAKE_LOCK) { + if (setWakeLockDisabledStateLocked(wakeLock)) { + changed = true; + if (wakeLock.mDisabled) { + // This wake lock is no longer being respected. + notifyWakeLockReleasedLocked(wakeLock); + } else { + notifyWakeLockAcquiredLocked(wakeLock); + } + } + } + } + if (changed) { + mDirty |= DIRTY_WAKE_LOCKS; + updatePowerStateLocked(); + } + } + + private boolean setWakeLockDisabledStateLocked(WakeLock wakeLock) { + if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) + == PowerManager.PARTIAL_WAKE_LOCK) { + boolean disabled = false; + if (mDeviceIdleMode) { + final int appid = UserHandle.getAppId(wakeLock.mOwnerUid); + // If we are in idle mode, we will ignore all partial wake locks that are + // for application uids that are not whitelisted. + if (appid >= Process.FIRST_APPLICATION_UID && + Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0) { + disabled = true; + } + } + if (wakeLock.mDisabled != disabled) { + wakeLock.mDisabled = disabled; + return true; + } + } + return false; + } + private boolean isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() { return mMaximumScreenOffTimeoutFromDeviceAdmin >= 0 && mMaximumScreenOffTimeoutFromDeviceAdmin < Integer.MAX_VALUE; @@ -2459,6 +2530,8 @@ public final class PowerManagerService extends SystemService pw.println(" mSandmanSummoned=" + mSandmanSummoned); pw.println(" mLowPowerModeEnabled=" + mLowPowerModeEnabled); pw.println(" mBatteryLevelLow=" + mBatteryLevelLow); + pw.println(" mDeviceIdleMode=" + mDeviceIdleMode); + pw.println(" mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist)); pw.println(" mLastWakeTime=" + TimeUtils.formatUptime(mLastWakeTime)); pw.println(" mLastSleepTime=" + TimeUtils.formatUptime(mLastSleepTime)); pw.println(" mLastUserActivityTime=" + TimeUtils.formatUptime(mLastUserActivityTime)); @@ -2671,6 +2744,7 @@ public final class PowerManagerService extends SystemService public final int mOwnerUid; public final int mOwnerPid; public boolean mNotifiedAcquired; + public boolean mDisabled; public WakeLock(IBinder lock, int flags, String tag, String packageName, WorkSource workSource, String historyTag, int ownerUid, int ownerPid) { @@ -2729,7 +2803,7 @@ public final class PowerManagerService extends SystemService @Override public String toString() { return getLockLevelString() - + " '" + mTag + "'" + getLockFlagsString() + + " '" + mTag + "'" + getLockFlagsString() + (mDisabled ? " DISABLED" : "") + " (uid=" + mOwnerUid + ", pid=" + mOwnerPid + ", ws=" + mWorkSource + ")"; } @@ -3340,9 +3414,12 @@ public final class PowerManagerService extends SystemService @Override public void setDeviceIdleMode(boolean enabled) { - synchronized (mLock) { - mDeviceIdleMode = enabled; - } + setDeviceIdleModeInternal(enabled); + } + + @Override + public void setDeviceIdleWhitelist(int[] appids) { + setDeviceIdleWhitelistInternal(appids); } } } |