diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java index 9ffef9cdcbde3..f5167b714d8ae 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java @@ -43,7 +43,7 @@ import java.util.Date; */ class Alarm { @VisibleForTesting - public static final int NUM_POLICIES = 4; + public static final int NUM_POLICIES = 5; /** * Index used to store the time the alarm was requested to expire. To be used with * {@link #setPolicyElapsed(int, long)}. @@ -66,6 +66,12 @@ class Alarm { */ public static final int BATTERY_SAVER_POLICY_INDEX = 3; + /** + * Index used to store the earliest time the alarm can expire based on TARE policy. + * To be used with {@link #setPolicyElapsed(int, long)}. + */ + public static final int TARE_POLICY_INDEX = 4; + /** * Reason to use for inexact alarms. */ @@ -254,8 +260,10 @@ class Alarm { return "device_idle"; case BATTERY_SAVER_POLICY_INDEX: return "battery_saver"; + case TARE_POLICY_INDEX: + return "tare"; default: - return "--unknown--"; + return "--unknown(" + index + ")--"; } } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 2b56e4f7d0158..deb81918e940d 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -45,6 +45,7 @@ import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_COMPAT; import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_NOT_APPLICABLE; import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_PERMISSION; import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX; +import static com.android.server.alarm.Alarm.TARE_POLICY_INDEX; import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_ALARM_CANCELLED; import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_DATA_CLEARED; import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_EXACT_PERMISSION_REVOKED; @@ -73,6 +74,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.database.ContentObserver; import android.net.Uri; import android.os.BatteryManager; import android.os.Binder; @@ -112,6 +114,7 @@ import android.util.NtpTrustedTime; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseArrayMap; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.SparseLongArray; @@ -140,6 +143,8 @@ import com.android.server.SystemServiceManager; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; +import com.android.server.tare.AlarmManagerEconomicPolicy; +import com.android.server.tare.EconomyManagerInternal; import com.android.server.usage.AppStandbyInternal; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; @@ -185,6 +190,7 @@ public class AlarmManagerService extends SystemService { static final boolean DEBUG_WAKELOCK = localLOGV || false; static final boolean DEBUG_BG_LIMIT = localLOGV || false; static final boolean DEBUG_STANDBY = localLOGV || false; + static final boolean DEBUG_TARE = localLOGV || false; static final boolean RECORD_ALARMS_IN_HISTORY = true; static final boolean RECORD_DEVICE_IDLE_ALARMS = false; static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; @@ -213,6 +219,7 @@ public class AlarmManagerService extends SystemService { DeviceIdleInternal mLocalDeviceIdleController; private UsageStatsManagerInternal mUsageStatsManagerInternal; private ActivityManagerInternal mActivityManagerInternal; + private final EconomyManagerInternal mEconomyManagerInternal; private PackageManagerInternal mPackageManagerInternal; private volatile PermissionManagerServiceInternal mLocalPermissionManager; @@ -230,6 +237,14 @@ public class AlarmManagerService extends SystemService { @GuardedBy("mLock") SparseIntArray mLastOpScheduleExactAlarm = new SparseIntArray(); + /** + * Local cache of the ability of each userId-pkg to afford the various bills we're tracking for + * them. + */ + @GuardedBy("mLock") + private final SparseArrayMap> + mAffordabilityCache = new SparseArrayMap<>(); + // List of alarms per uid deferred due to user applied background restrictions on the source app SparseArray> mPendingBackgroundAlarms = new SparseArray<>(); private long mNextWakeup; @@ -298,11 +313,13 @@ public class AlarmManagerService extends SystemService { interface Stats { int REORDER_ALARMS_FOR_STANDBY = 0; int HAS_SCHEDULE_EXACT_ALARM = 1; + int REORDER_ALARMS_FOR_TARE = 2; } private final StatLogger mStatLogger = new StatLogger("Alarm manager stats", new String[]{ "REORDER_ALARMS_FOR_STANDBY", "HAS_SCHEDULE_EXACT_ALARM", + "REORDER_ALARMS_FOR_TARE", }); BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic(); @@ -472,7 +489,8 @@ public class AlarmManagerService extends SystemService { * holding the AlarmManagerService.mLock lock. */ @VisibleForTesting - final class Constants implements DeviceConfig.OnPropertiesChangedListener { + final class Constants extends ContentObserver + implements DeviceConfig.OnPropertiesChangedListener { @VisibleForTesting static final int MAX_EXACT_ALARM_DENY_LIST_SIZE = 250; @@ -670,10 +688,13 @@ public class AlarmManagerService extends SystemService { public boolean KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED; + public boolean USE_TARE_POLICY = Settings.Global.DEFAULT_ENABLE_TARE == 1; + private long mLastAllowWhileIdleWhitelistDuration = -1; private int mVersion = 0; - Constants() { + Constants(Handler handler) { + super(handler); updateAllowWhileIdleWhitelistDurationLocked(); for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) { APP_STANDBY_QUOTAS[i] = DEFAULT_APP_STANDBY_QUOTAS[i]; @@ -687,8 +708,11 @@ public class AlarmManagerService extends SystemService { } public void start() { + mInjector.registerContentObserver(this, + Settings.Global.getUriFor(Settings.Global.ENABLE_TARE)); mInjector.registerDeviceConfigListener(this); onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_ALARM_MANAGER)); + updateTareSettings(); } public void updateAllowWhileIdleWhitelistDurationLocked() { @@ -860,6 +884,39 @@ public class AlarmManagerService extends SystemService { } } + @Override + public void onChange(boolean selfChange) { + updateTareSettings(); + } + + private void updateTareSettings() { + synchronized (mLock) { + final boolean isTareEnabled = Settings.Global.getInt( + getContext().getContentResolver(), + Settings.Global.ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE) == 1; + if (USE_TARE_POLICY != isTareEnabled) { + USE_TARE_POLICY = isTareEnabled; + final boolean changed = mAlarmStore.updateAlarmDeliveries(alarm -> { + final boolean standbyChanged = adjustDeliveryTimeBasedOnBucketLocked(alarm); + final boolean tareChanged = adjustDeliveryTimeBasedOnTareLocked(alarm); + if (USE_TARE_POLICY) { + registerTareListener(alarm); + } else { + mEconomyManagerInternal.unregisterAffordabilityChangeListener( + UserHandle.getUserId(alarm.uid), alarm.sourcePackage, + mAffordabilityChangeListener, + TareBill.getAppropriateBill(alarm)); + } + return standbyChanged || tareChanged; + }); + if (changed) { + rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); + } + } + } + } + private void updateExactAlarmDenyList(String[] newDenyList) { final Set newSet = Collections.unmodifiableSet(new ArraySet<>(newDenyList)); final Set removed = new ArraySet<>(EXACT_ALARM_DENY_LIST); @@ -1059,6 +1116,9 @@ public class AlarmManagerService extends SystemService { KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED); pw.println(); + pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY); + pw.println(); + pw.decreaseIndent(); } @@ -1177,6 +1237,7 @@ public class AlarmManagerService extends SystemService { AlarmManagerService(Context context, Injector injector) { super(context); mInjector = injector; + mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class); } public AlarmManagerService(Context context) { @@ -1283,6 +1344,29 @@ public class AlarmManagerService extends SystemService { return changed; } + /** + * Recalculates alarm send times based on TARE wealth. + * + * @param targetPackages [Package, User] pairs for which alarms need to be re-evaluated, + * null indicates all + * @return True if there was any reordering done to the current list. + */ + boolean reorderAlarmsBasedOnTare(ArraySet> targetPackages) { + final long start = mStatLogger.getTime(); + + final boolean changed = mAlarmStore.updateAlarmDeliveries(a -> { + final Pair packageUser = + Pair.create(a.sourcePackage, UserHandle.getUserId(a.creatorUid)); + if (targetPackages != null && !targetPackages.contains(packageUser)) { + return false; + } + return adjustDeliveryTimeBasedOnTareLocked(a); + }); + + mStatLogger.logDurationStat(Stats.REORDER_ALARMS_FOR_TARE, start); + return changed; + } + private boolean restoreRequestedTime(Alarm a) { return a.setPolicyElapsed(REQUESTER_POLICY_INDEX, convertToElapsed(a.origWhen, a.type)); } @@ -1632,7 +1716,7 @@ public class AlarmManagerService extends SystemService { synchronized (mLock) { mHandler = new AlarmHandler(); - mConstants = new Constants(); + mConstants = new Constants(mHandler); mAlarmStore = mConstants.LAZY_BATCHING ? new LazyAlarmStore() : new BatchingAlarmStore(); @@ -2255,7 +2339,7 @@ public class AlarmManagerService extends SystemService { */ private boolean adjustDeliveryTimeBasedOnBucketLocked(Alarm alarm) { final long nowElapsed = mInjector.getElapsedRealtime(); - if (isExemptFromAppStandby(alarm) || mAppStandbyParole) { + if (mConstants.USE_TARE_POLICY || isExemptFromAppStandby(alarm) || mAppStandbyParole) { return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, nowElapsed); } @@ -2299,6 +2383,50 @@ public class AlarmManagerService extends SystemService { return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, nowElapsed); } + /** + * Adjusts the alarm's policy time for TARE. + * + * @param alarm The alarm to update. + * @return {@code true} if the actual delivery time of the given alarm was updated due to + * adjustments made in this call. + */ + private boolean adjustDeliveryTimeBasedOnTareLocked(Alarm alarm) { + final long nowElapsed = mInjector.getElapsedRealtime(); + if (!mConstants.USE_TARE_POLICY + || isExemptFromTare(alarm) || hasEnoughWealthLocked(alarm)) { + return alarm.setPolicyElapsed(TARE_POLICY_INDEX, nowElapsed); + } + + // Not enough wealth. Just keep deferring indefinitely till the quota changes. + return alarm.setPolicyElapsed(TARE_POLICY_INDEX, nowElapsed + INDEFINITE_DELAY); + } + + private void registerTareListener(Alarm alarm) { + if (!mConstants.USE_TARE_POLICY) { + return; + } + mEconomyManagerInternal.registerAffordabilityChangeListener( + UserHandle.getUserId(alarm.uid), alarm.sourcePackage, + mAffordabilityChangeListener, TareBill.getAppropriateBill(alarm)); + } + + /** Unregister the TARE listener associated with the alarm if it's no longer needed. */ + private void maybeUnregisterTareListener(Alarm alarm) { + if (!mConstants.USE_TARE_POLICY) { + return; + } + final EconomyManagerInternal.ActionBill bill = TareBill.getAppropriateBill(alarm); + final Predicate isSameAlarmTypeForSameApp = (a) -> + alarm.creatorUid == a.creatorUid + && alarm.sourcePackage.equals(a.sourcePackage) + && bill.equals(TareBill.getAppropriateBill(a)); + if (mAlarmStore.getCount(isSameAlarmTypeForSameApp) == 0) { + mEconomyManagerInternal.unregisterAffordabilityChangeListener( + UserHandle.getUserId(alarm.uid), alarm.sourcePackage, + mAffordabilityChangeListener, bill); + } + } + private void setImplLocked(Alarm a) { if ((a.flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) { adjustIdleUntilTime(a); @@ -2345,6 +2473,8 @@ public class AlarmManagerService extends SystemService { } adjustDeliveryTimeBasedOnBatterySaver(a); adjustDeliveryTimeBasedOnBucketLocked(a); + adjustDeliveryTimeBasedOnTareLocked(a); + registerTareListener(a); mAlarmStore.add(a); rescheduleKernelAlarmsLocked(); updateNextAlarmClockLocked(); @@ -2711,14 +2841,43 @@ public class AlarmManagerService extends SystemService { mConstants.dump(pw); pw.println(); - if (mAppStateTracker != null) { - mAppStateTracker.dump(pw); + if (mConstants.USE_TARE_POLICY) { + pw.println("TARE details:"); + pw.increaseIndent(); + + pw.println("Affordability cache:"); + pw.increaseIndent(); + mAffordabilityCache.forEach((userId, pkgName, billMap) -> { + final int numBills = billMap.size(); + if (numBills > 0) { + pw.print(userId); + pw.print(":"); + pw.print(pkgName); + pw.println(":"); + + pw.increaseIndent(); + for (int i = 0; i < numBills; ++i) { + pw.print(TareBill.getName(billMap.keyAt(i))); + pw.print(": "); + pw.println(billMap.valueAt(i)); + } + pw.decreaseIndent(); + } + }); + pw.decreaseIndent(); + + pw.decreaseIndent(); + pw.println(); + } else { + if (mAppStateTracker != null) { + mAppStateTracker.dump(pw); + pw.println(); + } + + pw.println("App Standby Parole: " + mAppStandbyParole); pw.println(); } - pw.println("App Standby Parole: " + mAppStandbyParole); - pw.println(); - final long nowELAPSED = mInjector.getElapsedRealtime(); final long nowUPTIME = SystemClock.uptimeMillis(); final long nowRTC = mInjector.getCurrentTimeMillis(); @@ -3673,6 +3832,7 @@ public class AlarmManagerService extends SystemService { mRemovalHistory.put(removed.uid, bufferForUid); } bufferForUid.append(new RemovedAlarm(removed, reason, nowRtc, nowElapsed)); + maybeUnregisterTareListener(removed); } if (removedFromStore) { @@ -4010,6 +4170,11 @@ public class AlarmManagerService extends SystemService { alarm.uid, alarm.statsTag); } mDeliveryTracker.deliverLocked(alarm, nowELAPSED); + reportAlarmEventToTare(alarm); + if (alarm.repeatInterval <= 0) { + // Don't bother trying to unregister for a repeating alarm. + maybeUnregisterTareListener(alarm); + } } catch (RuntimeException e) { Slog.w(TAG, "Failure sending alarm.", e); } @@ -4018,12 +4183,60 @@ public class AlarmManagerService extends SystemService { } } + private void reportAlarmEventToTare(Alarm alarm) { + final boolean allowWhileIdle = + (alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_ALLOW_WHILE_IDLE)) != 0; + final int action; + if (alarm.alarmClock != null) { + action = AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK; + } else if (alarm.wakeup) { + if (alarm.windowLength == 0) { + if (allowWhileIdle) { + action = AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE; + } else { + action = AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_EXACT; + } + } else { + if (allowWhileIdle) { + action = + AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE; + } else { + action = AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_INEXACT; + } + } + } else { + if (alarm.windowLength == 0) { + if (allowWhileIdle) { + action = AlarmManagerEconomicPolicy + .ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE; + } else { + action = AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_EXACT; + } + } else { + if (allowWhileIdle) { + action = AlarmManagerEconomicPolicy + .ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE; + } else { + action = AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_INEXACT; + } + } + } + mEconomyManagerInternal.noteInstantaneousEvent( + UserHandle.getUserId(alarm.uid), alarm.sourcePackage, action, null); + } + @VisibleForTesting static boolean isExemptFromAppStandby(Alarm a) { return a.alarmClock != null || UserHandle.isCore(a.creatorUid) || (a.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_ALLOW_WHILE_IDLE)) != 0; } + @VisibleForTesting + static boolean isExemptFromTare(Alarm a) { + return a.alarmClock != null || UserHandle.isCore(a.creatorUid) + || (a.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0; + } + @VisibleForTesting static class Injector { private long mNativeData; @@ -4117,6 +4330,10 @@ public class AlarmManagerService extends SystemService { return service.new ClockReceiver(); } + void registerContentObserver(ContentObserver contentObserver, Uri uri) { + mContext.getContentResolver().registerContentObserver(uri, false, contentObserver); + } + void registerDeviceConfigListener(DeviceConfig.OnPropertiesChangedListener listener) { DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ALARM_MANAGER, JobSchedulerBackgroundThread.getExecutor(), listener); @@ -4249,13 +4466,23 @@ public class AlarmManagerService extends SystemService { new ArraySet<>(); for (int i = 0; i < triggerList.size(); i++) { final Alarm a = triggerList.get(i); - if (!isExemptFromAppStandby(a)) { + if (mConstants.USE_TARE_POLICY) { + if (!isExemptFromTare(a)) { + triggerPackages.add(Pair.create( + a.sourcePackage, + UserHandle.getUserId(a.creatorUid))); + } + } else if (!isExemptFromAppStandby(a)) { triggerPackages.add(Pair.create( a.sourcePackage, UserHandle.getUserId(a.creatorUid))); } } deliverAlarmsLocked(triggerList, nowELAPSED); - reorderAlarmsBasedOnStandbyBuckets(triggerPackages); + if (mConstants.USE_TARE_POLICY) { + reorderAlarmsBasedOnTare(triggerPackages); + } else { + reorderAlarmsBasedOnStandbyBuckets(triggerPackages); + } rescheduleKernelAlarmsLocked(); updateNextAlarmClockLocked(); MetricsHelper.pushAlarmBatchDelivered(triggerList.size(), wakeUps); @@ -4307,6 +4534,31 @@ public class AlarmManagerService extends SystemService { return alarm.creatorUid; } + @GuardedBy("mLock") + private boolean canAffordBillLocked(@NonNull Alarm alarm, + @NonNull EconomyManagerInternal.ActionBill bill) { + final int userId = UserHandle.getUserId(alarm.uid); + final String pkgName = alarm.sourcePackage; + ArrayMap actionAffordability = + mAffordabilityCache.get(userId, pkgName); + if (actionAffordability == null) { + actionAffordability = new ArrayMap<>(); + mAffordabilityCache.add(userId, pkgName, actionAffordability); + } + + if (actionAffordability.containsKey(bill)) { + return actionAffordability.get(bill); + } + + final boolean canAfford = mEconomyManagerInternal.canPayFor(userId, pkgName, bill); + actionAffordability.put(bill, canAfford); + return canAfford; + } + + @GuardedBy("mLock") + private boolean hasEnoughWealthLocked(@NonNull Alarm alarm) { + return canAffordBillLocked(alarm, TareBill.getAppropriateBill(alarm)); + } @VisibleForTesting class AlarmHandler extends Handler { @@ -4321,6 +4573,7 @@ public class AlarmManagerService extends SystemService { public static final int EXACT_ALARM_DENY_LIST_PACKAGES_ADDED = 9; public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10; public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11; + public static final int TARE_AFFORDABILITY_CHANGED = 12; AlarmHandler() { super(Looper.myLooper()); @@ -4402,6 +4655,20 @@ public class AlarmManagerService extends SystemService { } break; + case TARE_AFFORDABILITY_CHANGED: + synchronized (mLock) { + final int userId = msg.arg1; + final String packageName = (String) msg.obj; + + final ArraySet> filterPackages = new ArraySet<>(); + filterPackages.add(Pair.create(packageName, userId)); + if (reorderAlarmsBasedOnTare(filterPackages)) { + rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); + } + } + break; + case REMOVE_FOR_CANCELED: final PendingIntent operation = (PendingIntent) msg.obj; synchronized (mLock) { @@ -4656,6 +4923,31 @@ public class AlarmManagerService extends SystemService { } } + private final EconomyManagerInternal.AffordabilityChangeListener mAffordabilityChangeListener = + new EconomyManagerInternal.AffordabilityChangeListener() { + @Override + public void onAffordabilityChanged(int userId, @NonNull String packageName, + @NonNull EconomyManagerInternal.ActionBill bill, boolean canAfford) { + if (DEBUG_TARE) { + Slog.d(TAG, + userId + ":" + packageName + " affordability for " + + TareBill.getName(bill) + " changed to " + canAfford); + } + + ArrayMap actionAffordability = + mAffordabilityCache.get(userId, packageName); + if (actionAffordability == null) { + actionAffordability = new ArrayMap<>(); + mAffordabilityCache.add(userId, packageName, actionAffordability); + } + actionAffordability.put(bill, canAfford); + + mHandler.obtainMessage(AlarmHandler.TARE_AFFORDABILITY_CHANGED, userId, + canAfford ? 1 : 0, packageName) + .sendToTarget(); + } + }; + private final Listener mForceAppStandbyListener = new Listener() { @Override diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/TareBill.java b/apex/jobscheduler/service/java/com/android/server/alarm/TareBill.java new file mode 100644 index 0000000000000..e2f5ee14e7673 --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/alarm/TareBill.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2021 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.alarm; + +import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; +import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; + +import static com.android.server.tare.AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK; +import static com.android.server.tare.AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_EXACT; +import static com.android.server.tare.AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE; +import static com.android.server.tare.AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_INEXACT; +import static com.android.server.tare.AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE; +import static com.android.server.tare.AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_EXACT; +import static com.android.server.tare.AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE; +import static com.android.server.tare.AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_INEXACT; +import static com.android.server.tare.AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE; + +import android.annotation.NonNull; + +import com.android.server.tare.EconomyManagerInternal; +import com.android.server.tare.EconomyManagerInternal.ActionBill; + +import java.util.List; + +/** + * Container to maintain alarm TARE {@link ActionBill}s and their related methods. + */ +final class TareBill { + /** + * Bill to use for AlarmClocks. + */ + static final ActionBill ALARM_CLOCK = new ActionBill(List.of( + new EconomyManagerInternal.AnticipatedAction(ACTION_ALARM_CLOCK, 1, 0))); + /** + * Bills to use for various alarm types. + */ + static final ActionBill NONWAKEUP_INEXACT_ALARM = new ActionBill(List.of( + new EconomyManagerInternal.AnticipatedAction(ACTION_ALARM_NONWAKEUP_INEXACT, 1, 0))); + static final ActionBill NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE_ALARM = new ActionBill(List.of( + new EconomyManagerInternal.AnticipatedAction( + ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE, 1, 0))); + static final ActionBill NONWAKEUP_EXACT_ALARM = new ActionBill(List.of( + new EconomyManagerInternal.AnticipatedAction(ACTION_ALARM_NONWAKEUP_EXACT, 1, 0))); + static final ActionBill NONWAKEUP_EXACT_ALLOW_WHILE_IDLE_ALARM = new ActionBill(List.of( + new EconomyManagerInternal.AnticipatedAction( + ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE, 1, 0))); + static final ActionBill WAKEUP_INEXACT_ALARM = new ActionBill(List.of( + new EconomyManagerInternal.AnticipatedAction(ACTION_ALARM_WAKEUP_INEXACT, 1, 0))); + static final ActionBill WAKEUP_INEXACT_ALLOW_WHILE_IDLE_ALARM = new ActionBill(List.of( + new EconomyManagerInternal.AnticipatedAction( + ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE, 1, 0))); + static final ActionBill WAKEUP_EXACT_ALARM = new ActionBill(List.of( + new EconomyManagerInternal.AnticipatedAction(ACTION_ALARM_WAKEUP_EXACT, 1, 0))); + static final ActionBill WAKEUP_EXACT_ALLOW_WHILE_IDLE_ALARM = new ActionBill(List.of( + new EconomyManagerInternal.AnticipatedAction( + ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE, 1, 0))); + + @NonNull + static ActionBill getAppropriateBill(@NonNull Alarm alarm) { + if (alarm.alarmClock != null) { + return ALARM_CLOCK; + } + + final boolean allowWhileIdle = + (alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_ALLOW_WHILE_IDLE)) != 0; + final boolean isExact = alarm.windowLength == 0; + + if (alarm.wakeup) { + if (isExact) { + if (allowWhileIdle) { + return WAKEUP_EXACT_ALLOW_WHILE_IDLE_ALARM; + } + return WAKEUP_EXACT_ALARM; + } + // Inexact + if (allowWhileIdle) { + return WAKEUP_INEXACT_ALLOW_WHILE_IDLE_ALARM; + } + return WAKEUP_INEXACT_ALARM; + } + + // Nonwakeup + if (isExact) { + if (allowWhileIdle) { + return NONWAKEUP_EXACT_ALLOW_WHILE_IDLE_ALARM; + } + return NONWAKEUP_EXACT_ALARM; + + } + if (allowWhileIdle) { + return NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE_ALARM; + } + return NONWAKEUP_INEXACT_ALARM; + } + + @NonNull + static String getName(@NonNull ActionBill bill) { + if (bill.equals(ALARM_CLOCK)) { + return "ALARM_CLOCK_BILL"; + } + if (bill.equals(NONWAKEUP_INEXACT_ALARM)) { + return "NONWAKEUP_INEXACT_ALARM_BILL"; + } + if (bill.equals(NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE_ALARM)) { + return "NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE_ALARM_BILL"; + } + if (bill.equals(NONWAKEUP_EXACT_ALARM)) { + return "NONWAKEUP_EXACT_ALARM_BILL"; + } + if (bill.equals(NONWAKEUP_EXACT_ALLOW_WHILE_IDLE_ALARM)) { + return "NONWAKEUP_EXACT_ALLOW_WHILE_IDLE_ALARM_BILL"; + } + if (bill.equals(WAKEUP_INEXACT_ALARM)) { + return "WAKEUP_INEXACT_ALARM_BILL"; + } + if (bill.equals(WAKEUP_INEXACT_ALLOW_WHILE_IDLE_ALARM)) { + return "WAKEUP_INEXACT_ALLOW_WHILE_IDLE_ALARM_BILL"; + } + if (bill.equals(WAKEUP_EXACT_ALARM)) { + return "WAKEUP_EXACT_ALARM_BILL"; + } + if (bill.equals(WAKEUP_EXACT_ALLOW_WHILE_IDLE_ALARM)) { + return "WAKEUP_EXACT_ALLOW_WHILE_IDLE_ALARM_BILL"; + } + return "UNKNOWN_BILL (" + bill.toString() + ")"; + } + + private TareBill() { + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 6d72d52b26661..942958c595144 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -63,6 +63,7 @@ import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_AL import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED; +import static com.android.server.alarm.AlarmManagerService.AlarmHandler.TARE_AFFORDABILITY_CHANGED; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_WINDOW; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA; @@ -120,9 +121,12 @@ import android.app.IAlarmManager; import android.app.PendingIntent; import android.app.compat.CompatChanges; import android.app.usage.UsageStatsManagerInternal; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManagerInternal; +import android.database.ContentObserver; +import android.net.Uri; import android.os.BatteryManager; import android.os.Bundle; import android.os.Handler; @@ -138,6 +142,7 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.provider.Settings; +import android.text.format.DateFormat; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; @@ -158,6 +163,7 @@ import com.android.server.SystemService; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; +import com.android.server.tare.EconomyManagerInternal; import com.android.server.usage.AppStandbyInternal; import libcore.util.EmptyArray; @@ -201,6 +207,8 @@ public class AlarmManagerServiceTest { @Mock private Context mMockContext; @Mock + private ContentResolver mContentResolver; + @Mock private IActivityManager mIActivityManager; @Mock private IAppOpsService mIAppOpsService; @@ -225,6 +233,8 @@ public class AlarmManagerServiceTest { @Mock private PowerManager.WakeLock mWakeLock; @Mock + private EconomyManagerInternal mEconomyManagerInternal; + @Mock DeviceConfig.Properties mDeviceConfigProperties; HashSet mDeviceConfigKeys = new HashSet<>(); @@ -349,6 +359,11 @@ public class AlarmManagerServiceTest { return mWakeLock; } + @Override + void registerContentObserver(ContentObserver observer, Uri uri) { + // Do nothing. + } + @Override void registerDeviceConfigListener(DeviceConfig.OnPropertiesChangedListener listener) { // Do nothing. @@ -369,6 +384,7 @@ public class AlarmManagerServiceTest { .initMocks(this) .spyStatic(ActivityManager.class) .mockStatic(CompatChanges.class) + .spyStatic(DateFormat.class) .spyStatic(DeviceConfig.class) .mockStatic(LocalServices.class) .spyStatic(Looper.class) @@ -384,6 +400,8 @@ public class AlarmManagerServiceTest { doReturn(mIActivityManager).when(ActivityManager::getService); doReturn(mDeviceIdleInternal).when( () -> LocalServices.getService(DeviceIdleInternal.class)); + doReturn(mEconomyManagerInternal).when( + () -> LocalServices.getService(EconomyManagerInternal.class)); doReturn(mPermissionManagerInternal).when( () -> LocalServices.getService(PermissionManagerServiceInternal.class)); doReturn(mActivityManagerInternal).when( @@ -403,6 +421,8 @@ public class AlarmManagerServiceTest { eq(TEST_CALLING_USER), anyLong())).thenReturn(STANDBY_BUCKET_ACTIVE); doReturn(Looper.getMainLooper()).when(Looper::myLooper); + when(mMockContext.getContentResolver()).thenReturn(mContentResolver); + doReturn(mDeviceConfigKeys).when(mDeviceConfigProperties).getKeyset(); when(mDeviceConfigProperties.getLong(anyString(), anyLong())) .thenAnswer((Answer) invocationOnMock -> { @@ -421,6 +441,9 @@ public class AlarmManagerServiceTest { doReturn(mDeviceConfigProperties).when( () -> DeviceConfig.getProperties( eq(DeviceConfig.NAMESPACE_ALARM_MANAGER), ArgumentMatchers.any())); + // Needed to ensure logging doesn't cause tests to fail. + doReturn(true) + .when(() -> DateFormat.is24HourFormat(eq(mMockContext), anyInt())); when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager); @@ -447,6 +470,7 @@ public class AlarmManagerServiceTest { // Other boot phases don't matter mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + setTareEnabled(false); mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW; mAllowWhileIdleWindow = mService.mConstants.ALLOW_WHILE_IDLE_WINDOW; ArgumentCaptor captor = @@ -596,6 +620,15 @@ public class AlarmManagerServiceTest { mService.mConstants.onPropertiesChanged(mDeviceConfigProperties); } + private void setTareEnabled(boolean enabled) { + doReturn(enabled ? 1 : 0).when( + () -> Settings.Global.getInt(mContentResolver, Settings.Global.ENABLE_TARE)); + doReturn(enabled ? 1 : 0).when( + () -> Settings.Global.getInt(mContentResolver, + Settings.Global.ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE)); + mService.mConstants.onChange(true); + } + /** * Lowers quotas to make testing feasible. Careful while calling as this will replace any * existing settings for the calling test. @@ -1987,6 +2020,44 @@ public class AlarmManagerServiceTest { assertEquals(idleUntil, mTestTimer.getElapsed()); } + @Test + public void tareThrottling() { + setTareEnabled(true); + final ArgumentCaptor listenerCaptor = + ArgumentCaptor.forClass(EconomyManagerInternal.AffordabilityChangeListener.class); + final ArgumentCaptor billCaptor = + ArgumentCaptor.forClass(EconomyManagerInternal.ActionBill.class); + + when(mEconomyManagerInternal + .canPayFor(eq(TEST_CALLING_USER), eq(TEST_CALLING_PACKAGE), billCaptor.capture())) + .thenReturn(false); + + final PendingIntent alarmPi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 15, alarmPi); + assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed()); + + final EconomyManagerInternal.ActionBill bill = billCaptor.getValue(); + verify(mEconomyManagerInternal).registerAffordabilityChangeListener( + eq(TEST_CALLING_USER), eq(TEST_CALLING_PACKAGE), + listenerCaptor.capture(), eq(bill)); + final EconomyManagerInternal.AffordabilityChangeListener listener = + listenerCaptor.getValue(); + + when(mEconomyManagerInternal + .canPayFor(eq(TEST_CALLING_USER), eq(TEST_CALLING_PACKAGE), eq(bill))) + .thenReturn(true); + listener.onAffordabilityChanged(TEST_CALLING_USER, TEST_CALLING_PACKAGE, bill, true); + assertAndHandleMessageSync(TARE_AFFORDABILITY_CHANGED); + assertEquals(mNowElapsedTest + 15, mTestTimer.getElapsed()); + + when(mEconomyManagerInternal + .canPayFor(eq(TEST_CALLING_USER), eq(TEST_CALLING_PACKAGE), eq(bill))) + .thenReturn(false); + listener.onAffordabilityChanged(TEST_CALLING_USER, TEST_CALLING_PACKAGE, bill, false); + assertAndHandleMessageSync(TARE_AFFORDABILITY_CHANGED); + assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed()); + } + @Test public void dispatchOrder() throws Exception { setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0); @@ -3065,6 +3136,23 @@ public class AlarmManagerServiceTest { verify(() -> MetricsHelper.pushAlarmBatchDelivered(10, 5)); } + @Test + public void tareEventPushed() throws Exception { + setTareEnabled(true); + + for (int i = 0; i < 10; i++) { + final int type = (i % 2 == 1) ? ELAPSED_REALTIME : ELAPSED_REALTIME_WAKEUP; + setTestAlarm(type, mNowElapsedTest + i, getNewMockPendingIntent()); + } + + final ArrayList alarms = mService.mAlarmStore.remove((alarm) -> { + return alarm.creatorUid == TEST_CALLING_UID; + }); + mService.deliverAlarmsLocked(alarms, mNowElapsedTest); + verify(mEconomyManagerInternal, times(10)).noteInstantaneousEvent( + eq(TEST_CALLING_USER), eq(TEST_CALLING_PACKAGE), anyInt(), any()); + } + @Test public void setTimeZoneImpl() { final long durationMs = 20000L; diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java index f11cba0db7b9d..a129f39106cd2 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java @@ -28,6 +28,7 @@ import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX; import static com.android.server.alarm.Alarm.NUM_POLICIES; import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX; import static com.android.server.alarm.AlarmManagerService.isExemptFromAppStandby; +import static com.android.server.alarm.AlarmManagerService.isExemptFromTare; import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE; import static com.android.server.alarm.Constants.TEST_CALLING_UID; @@ -193,4 +194,20 @@ public class AlarmTest { createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED))); assertTrue("Alarm clock not exempt", isExemptFromAppStandby(createAlarmClock(anything))); } + + @Test + public void testIsExemptFromTare() { + final long anything = 54321; // Arbitrary number, doesn't matter for this test. + + assertFalse("Basic alarm exempt", isExemptFromTare( + createDefaultAlarm(anything, anything, 0))); + assertFalse("FLAG_ALLOW_WHILE_IDLE_COMPAT exempt", isExemptFromTare( + createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_COMPAT))); + assertFalse("ALLOW_WHILE_IDLE exempt", isExemptFromTare( + createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE))); + + assertTrue("ALLOW_WHILE_IDLE_UNRESTRICTED not exempt", isExemptFromTare( + createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED))); + assertTrue("Alarm clock not exempt", isExemptFromTare(createAlarmClock(anything))); + } }