diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java index b2e95a58f0ec5..c5f0f32a5aa01 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java @@ -16,8 +16,12 @@ package com.android.server.tare; +import static android.text.format.DateUtils.DAY_IN_MILLIS; + import static com.android.server.tare.EconomicPolicy.REGULATION_BASIC_INCOME; import static com.android.server.tare.EconomicPolicy.REGULATION_BIRTHRIGHT; +import static com.android.server.tare.EconomicPolicy.REGULATION_DEMOTION; +import static com.android.server.tare.EconomicPolicy.REGULATION_PROMOTION; import static com.android.server.tare.EconomicPolicy.REGULATION_WEALTH_RECLAMATION; import static com.android.server.tare.EconomicPolicy.TYPE_ACTION; import static com.android.server.tare.EconomicPolicy.TYPE_REWARD; @@ -603,6 +607,51 @@ class Agent { } } + /** + * Reclaim a percentage of unused ARCs from an app that was just removed from an exemption list. + * The amount reclaimed will depend on how recently the app was used. The reclamation will not + * reduce an app's balance below its current minimum balance. + */ + @GuardedBy("mLock") + void onAppUnexemptedLocked(final int userId, @NonNull final String pkgName) { + final long curBalance = getBalanceLocked(userId, pkgName); + final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName); + if (curBalance <= minBalance) { + return; + } + // AppStandby only counts elapsed time for things like this + // TODO: should we use clock time instead? + final long timeSinceLastUsedMs = + mAppStandbyInternal.getTimeSinceLastUsedByUser(pkgName, userId); + // The app is no longer exempted. We should take away some of credits so it's more in line + // with other non-exempt apps. However, don't take away as many credits if the app was used + // recently. + final double percentageToReclaim; + if (timeSinceLastUsedMs < DAY_IN_MILLIS) { + percentageToReclaim = .25; + } else if (timeSinceLastUsedMs < 2 * DAY_IN_MILLIS) { + percentageToReclaim = .5; + } else if (timeSinceLastUsedMs < 3 * DAY_IN_MILLIS) { + percentageToReclaim = .75; + } else { + percentageToReclaim = 1; + } + final long overage = curBalance - minBalance; + final long toReclaim = (long) (overage * percentageToReclaim); + if (toReclaim > 0) { + if (DEBUG) { + Slog.i(TAG, "Reclaiming bonus wealth! Taking " + toReclaim + + " from " + appToString(userId, pkgName)); + } + + final long now = getCurrentTimeMillis(); + final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); + recordTransactionLocked(userId, pkgName, ledger, + new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim), + true); + } + } + /** Returns true if an app should be given credits in the general distributions. */ private boolean shouldGiveCredits(@NonNull PackageInfo packageInfo) { final ApplicationInfo applicationInfo = packageInfo.applicationInfo; @@ -700,6 +749,21 @@ class Agent { Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))), true); } + @GuardedBy("mLock") + void onAppExemptedLocked(final int userId, @NonNull final String pkgName) { + final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName); + final long missing = minBalance - getBalanceLocked(userId, pkgName); + if (missing <= 0) { + return; + } + + final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); + final long now = getCurrentTimeMillis(); + + recordTransactionLocked(userId, pkgName, ledger, + new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing), true); + } + @GuardedBy("mLock") void onPackageRemovedLocked(final int userId, @NonNull final String pkgName) { reclaimAssetsLocked(userId, pkgName); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java index 2b4938bc7746d..d7290191dc3c4 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java @@ -35,6 +35,7 @@ import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WA import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP; import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_CIRCULATION; import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE; +import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED; import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP; import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT; import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX; @@ -71,6 +72,7 @@ import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP; import static android.app.tare.EconomyManager.KEY_AM_MAX_CIRCULATION; import static android.app.tare.EconomyManager.KEY_AM_MAX_SATIATED_BALANCE; +import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED; import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP; import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT; import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_MAX; @@ -138,7 +140,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { COST_MODIFIER_PROCESS_STATE }; - private long mMinSatiatedBalance; + private long mMinSatiatedBalanceExempted; + private long mMinSatiatedBalanceOther; private long mMaxSatiatedBalance; private long mMaxSatiatedCirculation; @@ -163,8 +166,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { @Override long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) { - // TODO: take exemption into account - return mMinSatiatedBalance; + if (mInternalResourceService.isPackageExempted(userId, pkgName)) { + return mMinSatiatedBalanceExempted; + } + // TODO: take other exemptions into account + return mMinSatiatedBalanceOther; } @Override @@ -205,7 +211,9 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalance = arcToNarc(mParser.getInt(KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, + mMinSatiatedBalanceExempted = arcToNarc(mParser.getInt(KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, + DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED)); + mMinSatiatedBalanceOther = arcToNarc(mParser.getInt(KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP)); mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE)); @@ -343,7 +351,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { @Override void dump(IndentingPrintWriter pw) { - pw.print("Min satiated balance", narcToString(mMinSatiatedBalance)).println(); + pw.println("Min satiated balances:"); + pw.increaseIndent(); + pw.print("Exempted", narcToString(mMinSatiatedBalanceExempted)).println(); + pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println(); + pw.decreaseIndent(); pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println(); pw.print("Min satiated circulation", narcToString(mMaxSatiatedCirculation)).println(); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java index d16e378bce9e5..c1177b20bb0e2 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java @@ -57,6 +57,8 @@ public abstract class EconomicPolicy { static final int REGULATION_BASIC_INCOME = TYPE_REGULATION | 0; static final int REGULATION_BIRTHRIGHT = TYPE_REGULATION | 1; static final int REGULATION_WEALTH_RECLAMATION = TYPE_REGULATION | 2; + static final int REGULATION_PROMOTION = TYPE_REGULATION | 3; + static final int REGULATION_DEMOTION = TYPE_REGULATION | 4; static final int REWARD_NOTIFICATION_SEEN = TYPE_REWARD | 0; static final int REWARD_NOTIFICATION_INTERACTION = TYPE_REWARD | 1; @@ -363,6 +365,10 @@ public abstract class EconomicPolicy { return "BIRTHRIGHT"; case REGULATION_WEALTH_RECLAMATION: return "WEALTH_RECLAMATION"; + case REGULATION_PROMOTION: + return "PROMOTION"; + case REGULATION_DEMOTION: + return "DEMOTION"; } return "UNKNOWN_REGULATION:" + Integer.toHexString(eventId); } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index 1cd873c675c07..ed11097202e19 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -45,8 +45,12 @@ import android.net.Uri; import android.os.BatteryManagerInternal; import android.os.Binder; import android.os.Handler; +import android.os.IDeviceIdleController; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; @@ -105,6 +109,8 @@ public class InternalResourceService extends SystemService { private final PackageManager mPackageManager; private final PackageManagerInternal mPackageManagerInternal; + private IDeviceIdleController mDeviceIdleController; + private final Agent mAgent; private final ConfigObserver mConfigObserver; private final EconomyManagerStub mEconomyManagerStub; @@ -163,8 +169,14 @@ public class InternalResourceService extends SystemService { @GuardedBy("mPackageToUidCache") private final SparseArrayMap mPackageToUidCache = new SparseArrayMap<>(); + /** List of packages that are "exempted" from battery restrictions. */ + // TODO(144864180): include userID + @GuardedBy("mLock") + private ArraySet mExemptedApps = new ArraySet<>(); + private volatile boolean mIsEnabled; private volatile int mBootPhase; + private volatile boolean mExemptListLoaded; // In the range [0,100] to represent 0% to 100% battery. @GuardedBy("mLock") private int mCurrentBatteryLevel; @@ -213,6 +225,9 @@ public class InternalResourceService extends SystemService { onUserRemoved(userId); } break; + case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: + onExemptionListChanged(); + break; } } }; @@ -285,7 +300,22 @@ public class InternalResourceService extends SystemService { if (PHASE_SYSTEM_SERVICES_READY == phase) { mConfigObserver.start(); + mDeviceIdleController = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); setupEverything(); + } else if (PHASE_BOOT_COMPLETED == phase) { + if (!mExemptListLoaded) { + synchronized (mLock) { + try { + mExemptedApps = + new ArraySet<>(mDeviceIdleController.getFullPowerWhitelist()); + } catch (RemoteException e) { + // Shouldn't happen. + Slog.wtf(TAG, e); + } + mExemptListLoaded = true; + } + } } } @@ -350,6 +380,12 @@ public class InternalResourceService extends SystemService { return mIsEnabled; } + boolean isPackageExempted(final int userId, @NonNull String pkgName) { + synchronized (mLock) { + return mExemptedApps.contains(pkgName); + } + } + boolean isSystem(final int userId, @NonNull String pkgName) { if ("android".equals(pkgName)) { return true; @@ -375,6 +411,53 @@ public class InternalResourceService extends SystemService { } } + void onExemptionListChanged() { + final int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds(); + synchronized (mLock) { + final ArraySet removed = mExemptedApps; + final ArraySet added = new ArraySet<>(); + try { + mExemptedApps = new ArraySet<>(mDeviceIdleController.getFullPowerWhitelist()); + } catch (RemoteException e) { + // Shouldn't happen. + Slog.wtf(TAG, e); + return; + } + + for (int i = mExemptedApps.size() - 1; i >= 0; --i) { + final String pkg = mExemptedApps.valueAt(i); + if (!removed.contains(pkg)) { + added.add(pkg); + } + removed.remove(pkg); + } + for (int a = added.size() - 1; a >= 0; --a) { + final String pkgName = added.valueAt(a); + for (int userId : userIds) { + // Since the exemption list doesn't specify user ID and we track by user ID, + // we need to see if the app exists on the user before talking to the agent. + // Otherwise, we may end up with invalid ledgers. + final boolean appExists = getUid(userId, pkgName) >= 0; + if (appExists) { + mAgent.onAppExemptedLocked(userId, pkgName); + } + } + } + for (int r = removed.size() - 1; r >= 0; --r) { + final String pkgName = removed.valueAt(r); + for (int userId : userIds) { + // Since the exemption list doesn't specify user ID and we track by user ID, + // we need to see if the app exists on the user before talking to the agent. + // Otherwise, we may end up with invalid ledgers. + final boolean appExists = getUid(userId, pkgName) >= 0; + if (appExists) { + mAgent.onAppUnexemptedLocked(userId, pkgName); + } + } + } + } + } + void onPackageAdded(final int uid, @NonNull final String pkgName) { final int userId = UserHandle.getUserId(uid); final PackageInfo packageInfo; @@ -602,6 +685,7 @@ public class InternalResourceService extends SystemService { private void registerListeners() { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED); + filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); final IntentFilter pkgFilter = new IntentFilter(); @@ -625,6 +709,15 @@ public class InternalResourceService extends SystemService { private void setupHeavyWork() { synchronized (mLock) { loadInstalledPackageListLocked(); + if (mBootPhase >= PHASE_BOOT_COMPLETED && !mExemptListLoaded) { + try { + mExemptedApps = new ArraySet<>(mDeviceIdleController.getFullPowerWhitelist()); + } catch (RemoteException e) { + // Shouldn't happen. + Slog.wtf(TAG, e); + } + mExemptListLoaded = true; + } final boolean isFirstSetup = !mScribe.recordExists(); if (isFirstSetup) { mAgent.grantBirthrightsLocked(); @@ -654,6 +747,8 @@ public class InternalResourceService extends SystemService { synchronized (mLock) { mAgent.tearDownLocked(); mCompleteEconomicPolicy.tearDown(); + mExemptedApps.clear(); + mExemptListLoaded = false; mHandler.post(() -> { // Never call out to AlarmManager with the lock held. This sits below AM. AlarmManager alarmManager = getContext().getSystemService(AlarmManager.class); @@ -953,9 +1048,9 @@ public class InternalResourceService extends SystemService { pw.print("Current battery level: "); pw.println(mCurrentBatteryLevel); - final long maxCircluation = getMaxCirculationLocked(); + final long maxCirculation = getMaxCirculationLocked(); pw.print("Max circulation (current/satiated): "); - pw.print(narcToString(maxCircluation)); + pw.print(narcToString(maxCirculation)); pw.print("/"); pw.println(narcToString(mCompleteEconomicPolicy.getMaxSatiatedCirculation())); @@ -963,9 +1058,13 @@ public class InternalResourceService extends SystemService { pw.print("Current GDP: "); pw.print(narcToString(currentCirculation)); pw.print(" ("); - pw.print(String.format("%.2f", 100f * currentCirculation / maxCircluation)); + pw.print(String.format("%.2f", 100f * currentCirculation / maxCirculation)); pw.println("% of current max)"); + pw.println(); + pw.print("Exempted apps", mExemptedApps); + pw.println(); + pw.println(); mCompleteEconomicPolicy.dump(pw); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java index 728f5e80d4ca5..2318026d9f16b 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -40,6 +40,7 @@ import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENA import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP; import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_CIRCULATION; import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE; +import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED; import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP; import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT; import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX; @@ -80,6 +81,7 @@ import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_ import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP; import static android.app.tare.EconomyManager.KEY_JS_MAX_CIRCULATION; import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE; +import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED; import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP; import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT; import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_INTERACTION_MAX; @@ -140,7 +142,8 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { COST_MODIFIER_PROCESS_STATE }; - private long mMinSatiatedBalance; + private long mMinSatiatedBalanceExempted; + private long mMinSatiatedBalanceOther; private long mMaxSatiatedBalance; private long mMaxSatiatedCirculation; @@ -165,8 +168,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { @Override long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) { - // TODO: incorporate time since usage - return mMinSatiatedBalance; + if (mInternalResourceService.isPackageExempted(userId, pkgName)) { + return mMinSatiatedBalanceExempted; + } + // TODO: take other exemptions into account + return mMinSatiatedBalanceOther; } @Override @@ -207,7 +213,10 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalance = arcToNarc( + mMinSatiatedBalanceExempted = arcToNarc( + mParser.getInt(KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, + DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED)); + mMinSatiatedBalanceOther = arcToNarc( mParser.getInt(KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP)); mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_JS_MAX_SATIATED_BALANCE, @@ -317,7 +326,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { @Override void dump(IndentingPrintWriter pw) { - pw.print("Min satiated balance", narcToString(mMinSatiatedBalance)).println(); + pw.println("Min satiated balances:"); + pw.increaseIndent(); + pw.print("Exempted", narcToString(mMinSatiatedBalanceExempted)).println(); + pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println(); + pw.decreaseIndent(); pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println(); pw.print("Min satiated circulation", narcToString(mMaxSatiatedCirculation)).println();