Extreme battery saver: AlarmManager
- AlarmManagerService now uses ForceAppStandbyTracker.
- Now AlarmManagerService uses the system + user power-save whitelist,
rather than just the user whitelist.
Test: Manual test
Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
Bug 68769804
Change-Id: Ie2bd17fe0c3cb8b09ec4c4a78f9254277be16926
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 3904fc9..4330f5d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -46,7 +46,6 @@
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -55,7 +54,6 @@
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -81,6 +79,7 @@
import java.util.Random;
import java.util.TimeZone;
import java.util.TreeSet;
+import java.util.function.Predicate;
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.app.AlarmManager.RTC;
@@ -88,11 +87,17 @@
import static android.app.AlarmManager.ELAPSED_REALTIME;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.LocalLog;
+import com.android.server.ForceAppStandbyTracker.Listener;
+/**
+ * Alarm manager implementaion.
+ *
+ * Unit test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
+ */
class AlarmManagerService extends SystemService {
private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
private static final int RTC_MASK = 1 << RTC;
@@ -131,13 +136,10 @@
final LocalLog mLog = new LocalLog(TAG);
AppOpsManager mAppOps;
- IAppOpsService mAppOpsService;
DeviceIdleController.LocalService mLocalDeviceIdleController;
final Object mLock = new Object();
- ArraySet<String> mForcedAppStandbyPackages = new ArraySet<>();
- SparseBooleanArray mForegroundUids = new SparseBooleanArray();
// List of alarms per uid deferred due to user applied background restrictions on the source app
SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
long mNativeData;
@@ -184,12 +186,6 @@
int mSystemUiUid;
/**
- * The current set of user whitelisted apps for device idle mode, meaning these are allowed
- * to freely schedule alarms.
- */
- int[] mDeviceIdleUserWhitelist = new int[0];
-
- /**
* For each uid, this is the last time we dispatched an "allow while idle" alarm,
* used to determine the earliest we can dispatch the next such alarm. Times are in the
* 'elapsed' timebase.
@@ -223,6 +219,8 @@
private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
new SparseArray<>();
+ private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
/**
* All times are in milliseconds. These constants are kept synchronized with the system
* global Settings. Any access to this class or its fields should be done while
@@ -757,6 +755,9 @@
public AlarmManagerService(Context context) {
super(context);
mConstants = new Constants(mHandler);
+
+ mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+ mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
}
static long convertToElapsed(long when, int type) {
@@ -894,17 +895,48 @@
deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
}
- void sendPendingBackgroundAlarmsForAppIdLocked(int appId) {
+ /**
+ * Check all alarms in {@link #mPendingBackgroundAlarms} and send the ones that are not
+ * restricted.
+ *
+ * This is only called when the global "force all apps-standby" flag changes or when the
+ * power save whitelist changes, so it's okay to be slow.
+ */
+ void sendAllUnrestrictedPendingBackgroundAlarmsLocked() {
final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
- for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
- final int uid = mPendingBackgroundAlarms.keyAt(i);
- final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
- if (UserHandle.getAppId(uid) == appId) {
- alarmsToDeliver.addAll(alarmsForUid);
- mPendingBackgroundAlarms.removeAt(i);
+
+ findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+ mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted);
+
+ if (alarmsToDeliver.size() > 0) {
+ deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+ }
+ }
+
+ @VisibleForTesting
+ static void findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+ SparseArray<ArrayList<Alarm>> pendingAlarms, ArrayList<Alarm> unrestrictedAlarms,
+ Predicate<Alarm> isBackgroundRestricted) {
+
+ for (int uidIndex = pendingAlarms.size() - 1; uidIndex >= 0; uidIndex--) {
+ final int uid = pendingAlarms.keyAt(uidIndex);
+ final ArrayList<Alarm> alarmsForUid = pendingAlarms.valueAt(uidIndex);
+
+ for (int alarmIndex = alarmsForUid.size() - 1; alarmIndex >= 0; alarmIndex--) {
+ final Alarm alarm = alarmsForUid.get(alarmIndex);
+
+ if (isBackgroundRestricted.test(alarm)) {
+ continue;
+ }
+
+ unrestrictedAlarms.add(alarm);
+ alarmsForUid.remove(alarmIndex);
+ }
+
+ if (alarmsForUid.size() == 0) {
+ pendingAlarms.removeAt(uidIndex);
}
}
- deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
}
private void deliverPendingBackgroundAlarmsLocked(ArrayList<Alarm> alarms, long nowELAPSED) {
@@ -1234,10 +1266,8 @@
} catch (RemoteException e) {
// ignored; both services live in system_server
}
- mAppOpsService = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
publishBinderService(Context.ALARM_SERVICE, mService);
- publishLocalService(LocalService.class, new LocalService());
+ mForceAppStandbyTracker.start();
}
@Override
@@ -1247,13 +1277,6 @@
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
- try {
- mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
- new AppOpsWatcher());
- } catch (RemoteException rexc) {
- // Shouldn't happen as they are in the same process.
- Slog.e(TAG, "AppOps service not reachable", rexc);
- }
}
}
@@ -1582,8 +1605,7 @@
// timing restrictions.
} else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
|| callingUid == mSystemUiUid
- || Arrays.binarySearch(mDeviceIdleUserWhitelist,
- UserHandle.getAppId(callingUid)) >= 0)) {
+ || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
}
@@ -1660,24 +1682,14 @@
}
};
- public final class LocalService {
- public void setDeviceIdleUserWhitelist(int[] appids) {
- setDeviceIdleUserWhitelistImpl(appids);
- }
- }
-
void dumpImpl(PrintWriter pw) {
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
mConstants.dump(pw);
pw.println();
- pw.print(" Foreground uids: [");
- for (int i = 0; i < mForegroundUids.size(); i++) {
- if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
- }
- pw.println("]");
- pw.println(" Forced app standby packages: " + mForcedAppStandbyPackages);
+ mForceAppStandbyTracker.dump(pw, " ");
+
final long nowRTC = System.currentTimeMillis();
final long nowELAPSED = SystemClock.elapsedRealtime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -1717,7 +1729,6 @@
pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw);
pw.println();
pw.print(" Num time change events: "); pw.println(mNumTimeChanged);
- pw.println(" mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist));
pw.println();
pw.println(" Next alarm clock information: ");
@@ -1990,15 +2001,8 @@
mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
- final int foregroundUidsSize = mForegroundUids.size();
- for (int i = 0; i < foregroundUidsSize; i++) {
- if (mForegroundUids.valueAt(i)) {
- proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
- }
- }
- for (String pkg : mForcedAppStandbyPackages) {
- proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
- }
+ mForceAppStandbyTracker.dumpProto(proto,
+ AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
if (!mInteractive) {
@@ -2022,9 +2026,6 @@
proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
nowElapsed - mLastWakeupSet);
proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
- for (int i : mDeviceIdleUserWhitelist) {
- proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
- }
final TreeSet<Integer> users = new TreeSet<>();
final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
@@ -2266,28 +2267,6 @@
}
}
- void setDeviceIdleUserWhitelistImpl(int[] appids) {
- synchronized (mLock) {
- // appids are sorted, just send pending alarms for any new appids added to the whitelist
- int i = 0, j = 0;
- while (i < appids.length) {
- while (j < mDeviceIdleUserWhitelist.length
- && mDeviceIdleUserWhitelist[j] < appids[i]) {
- j++;
- }
- if (j < mDeviceIdleUserWhitelist.length
- && appids[i] != mDeviceIdleUserWhitelist[j]) {
- if (DEBUG_BG_LIMIT) {
- Slog.d(TAG, "Sending blocked alarms for whitelisted appid " + appids[j]);
- }
- sendPendingBackgroundAlarmsForAppIdLocked(appids[j]);
- }
- i++;
- }
- mDeviceIdleUserWhitelist = appids;
- }
- }
-
AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
synchronized (mLock) {
return mNextAlarmClockForUser.get(userId);
@@ -2710,9 +2689,7 @@
final String sourcePackage =
(alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName;
final int sourceUid = alarm.creatorUid;
- return mForcedAppStandbyPackages.contains(sourcePackage) && !mForegroundUids.get(sourceUid)
- && Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(sourceUid))
- < 0;
+ return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage);
}
private native long init();
@@ -2859,7 +2836,8 @@
}
}
- private static class Alarm {
+ @VisibleForTesting
+ static class Alarm {
public final int type;
public final long origWhen;
public final boolean wakeup;
@@ -3476,17 +3454,10 @@
if (disabled) {
removeForStoppedLocked(uid);
}
- mForegroundUids.delete(uid);
}
}
@Override public void onUidActive(int uid) {
- synchronized (mLock) {
- if (!mForegroundUids.get(uid)) {
- mForegroundUids.put(uid, true);
- sendPendingBackgroundAlarmsLocked(uid, null);
- }
- }
}
@Override public void onUidIdle(int uid, boolean disabled) {
@@ -3494,7 +3465,6 @@
if (disabled) {
removeForStoppedLocked(uid);
}
- mForegroundUids.delete(uid);
}
}
@@ -3502,27 +3472,29 @@
}
};
- private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+
+ private final Listener mForceAppStandbyListener = new Listener() {
@Override
- public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ public void unblockAllUnrestrictedAlarms() {
synchronized (mLock) {
- final int mode = mAppOpsService.checkOperation(op, uid, packageName);
- if (DEBUG_BG_LIMIT) {
- Slog.d(TAG,
- "Appop changed for " + uid + ", " + packageName + " to " + mode);
- }
- final boolean changed;
- if (mode != AppOpsManager.MODE_ALLOWED) {
- changed = mForcedAppStandbyPackages.add(packageName);
- } else {
- changed = mForcedAppStandbyPackages.remove(packageName);
- }
- if (changed && mode == AppOpsManager.MODE_ALLOWED) {
- sendPendingBackgroundAlarmsLocked(uid, packageName);
- }
+ sendAllUnrestrictedPendingBackgroundAlarmsLocked();
}
}
- }
+
+ @Override
+ public void unblockAlarmsForUid(int uid) {
+ synchronized (mLock) {
+ sendPendingBackgroundAlarmsLocked(uid, null);
+ }
+ }
+
+ @Override
+ public void unblockAlarmsForUidPackage(int uid, String packageName) {
+ synchronized (mLock) {
+ sendPendingBackgroundAlarmsLocked(uid, packageName);
+ }
+ }
+ };
private final BroadcastStats getStatsLocked(PendingIntent pi) {
String pkg = pi.getCreatorPackage();