Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2a25c418 authored by Kweku Adams's avatar Kweku Adams
Browse files

Account for the power allowlist.

Give apps on the power allowlist a higher minimum balance so they're
able to do more work. When a user removes an app from the list, we
reclaim some of the credits (with the amount reclaimed depending on the
time since the app was used).

Bug: 158300259
Bug: 202954395
Test: Charge device from 1% and see exempted apps get more credits.
Exempted apps approach minimum balance as device nears 100%.

Change-Id: Id19c43d31477daa90173bda5294479487b025ea2
parent 087aad75
Loading
Loading
Loading
Loading
+64 −0
Original line number Diff line number Diff line
@@ -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);
+17 −5
Original line number Diff line number Diff line
@@ -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();

+6 −0
Original line number Diff line number Diff line
@@ -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);
    }
+102 −3
Original line number Diff line number Diff line
@@ -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<String, Integer> mPackageToUidCache = new SparseArrayMap<>();

    /** List of packages that are "exempted" from battery restrictions. */
    // TODO(144864180): include userID
    @GuardedBy("mLock")
    private ArraySet<String> 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<String> removed = mExemptedApps;
            final ArraySet<String> 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);

+18 −5
Original line number Diff line number Diff line
@@ -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();