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

Commit fd5338fc authored by Kweku Adams's avatar Kweku Adams Committed by Android (Google) Code Review
Browse files

Merge "Reclaim unused credits from unused apps."

parents 6575374b d4832cf5
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -71,6 +71,13 @@ public interface AppStandbyInternal {

    long getTimeSinceLastJobRun(String packageName, int userId);

    /**
     * Returns the time (in milliseconds) since the app was last interacted with by the user.
     * This can be larger than the current elapsedRealtime, in case it happened before boot or
     * a really large value if the app was never interacted with.
     */
    long getTimeSinceLastUsedByUser(String packageName, int userId);

    void onUserRemoved(int userId);

    void addListener(AppIdleStateChangeListener listener);
+52 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.text.format.DateUtils.MINUTE_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_WEALTH_RECLAMATION;
import static com.android.server.tare.EconomicPolicy.TYPE_ACTION;
import static com.android.server.tare.EconomicPolicy.TYPE_REWARD;
import static com.android.server.tare.EconomicPolicy.eventToString;
@@ -46,6 +47,7 @@ import android.util.SparseArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.usage.AppStandbyInternal;

import java.util.List;
import java.util.Objects;
@@ -53,7 +55,7 @@ import java.util.PriorityQueue;

/**
 * Other half of the IRS. The agent handles the nitty gritty details, interacting directly with
 * ledgers, carrying out specific events such as tax collection and granting initial balances or
 * ledgers, carrying out specific events such as wealth reclamation, granting initial balances or
 * replenishing balances, and tracking ongoing events.
 */
class Agent {
@@ -61,6 +63,11 @@ class Agent {
    private static final boolean DEBUG = InternalResourceService.DEBUG
            || Log.isLoggable(TAG, Log.DEBUG);

    /**
     * The minimum amount of time an app must not have been used by the user before we start
     * regularly reclaiming ARCs from it.
     */
    private static final long MIN_UNUSED_TIME_MS = 3 * 24 * HOUR_IN_MILLIS;
    /**
     * The maximum amount of time we'll keep a transaction around for.
     * For now, only keep transactions we actually have a use for. We can increase it if we want
@@ -75,6 +82,8 @@ class Agent {
    private final Handler mHandler;
    private final InternalResourceService mIrs;

    private final AppStandbyInternal mAppStandbyInternal;

    @GuardedBy("mLock")
    private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>();

@@ -97,6 +106,7 @@ class Agent {
        mIrs = irs;
        mCompleteEconomicPolicy = completeEconomicPolicy;
        mHandler = new AgentHandler(TareHandlerThread.get().getLooper());
        mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
    }

    @GuardedBy("mLock")
@@ -198,6 +208,47 @@ class Agent {
        }
    }

    /**
     * Reclaim a percentage of unused ARCs from every app that hasn't been used recently. The
     * reclamation will not reduce an app's balance below its minimum balance as dictated by the
     * EconomicPolicy.
     *
     * @param percentage A value between 0 and 1 to indicate how much of the unused balance should
     *                   be reclaimed.
     */
    @GuardedBy("mLock")
    void reclaimUnusedAssetsLocked(double percentage) {
        final List<PackageInfo> pkgs = mIrs.getInstalledPackages();
        final long now = System.currentTimeMillis();
        for (int i = 0; i < pkgs.size(); ++i) {
            final int userId = UserHandle.getUserId(pkgs.get(i).applicationInfo.uid);
            final String pkgName = pkgs.get(i).packageName;
            final Ledger ledger = getLedgerLocked(userId, pkgName);
            // AppStandby only counts elapsed time for things like this
            // TODO: should we use clock time instead?
            final long timeSinceLastUsedMs =
                    mAppStandbyInternal.getTimeSinceLastUsedByUser(pkgName, userId);
            if (timeSinceLastUsedMs >= MIN_UNUSED_TIME_MS) {
                // Use a constant floor instead of the scaled floor from the IRS.
                final long minBalance =
                        mCompleteEconomicPolicy.getMinSatiatedBalance(userId, pkgName);
                final long curBalance = ledger.getCurrentBalance();
                long toReclaim = (long) (curBalance * percentage);
                if (curBalance - toReclaim < minBalance) {
                    toReclaim = curBalance - minBalance;
                }
                if (toReclaim > 0) {
                    Slog.i(TAG, "Reclaiming unused wealth! Taking " + toReclaim
                            + " from <" + userId + ">" + pkgName);

                    recordTransactionLocked(userId, pkgName, ledger,
                            new Ledger.Transaction(
                                    now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim));
                }
            }
        }
    }

    @GuardedBy("mLock")
    void distributeBasicIncomeLocked(int batteryLevel) {
        List<PackageInfo> pkgs = mIrs.getInstalledPackages();
+3 −3
Original line number Diff line number Diff line
@@ -55,7 +55,7 @@ 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_TAX = TYPE_REGULATION | 2;
    static final int REGULATION_WEALTH_RECLAMATION = TYPE_REGULATION | 2;

    static final int REWARD_NOTIFICATION_SEEN = TYPE_REWARD | 0;
    static final int REWARD_NOTIFICATION_INTERACTION = TYPE_REWARD | 1;
@@ -347,8 +347,8 @@ public abstract class EconomicPolicy {
                return "BASIC_INCOME";
            case REGULATION_BIRTHRIGHT:
                return "BIRTHRIGHT";
            case REGULATION_TAX:
                return "TAX";
            case REGULATION_WEALTH_RECLAMATION:
                return "WEALTH_RECLAMATION";
        }
        return "UNKNOWN_REGULATION:" + Integer.toHexString(eventId);
    }
+55 −0
Original line number Diff line number Diff line
@@ -16,8 +16,12 @@

package com.android.server.tare;

import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -29,6 +33,7 @@ import android.os.BatteryManagerInternal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Log;
@@ -57,6 +62,10 @@ public class InternalResourceService extends SystemService {
    public static final String TAG = "TARE-IRS";
    public static final boolean DEBUG = Log.isLoggable("TARE", Log.DEBUG);

    static final long UNUSED_RECLAMATION_PERIOD_MS = 24 * HOUR_IN_MILLIS;
    /** How much of an app's unused wealth should be reclaimed periodically. */
    private static final float DEFAULT_UNUSED_RECLAMATION_PERCENTAGE = .1f;

    /** Global local for all resource economy state. */
    private final Object mLock = new Object();

@@ -83,6 +92,9 @@ public class InternalResourceService extends SystemService {
    // In the range [0,100] to represent 0% to 100% battery.
    @GuardedBy("mLock")
    private int mCurrentBatteryLevel;
    // TODO: load from disk
    @GuardedBy("mLock")
    private long mLastUnusedReclamationTime;

    @SuppressWarnings("FieldCanBeLocal")
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -133,7 +145,21 @@ public class InternalResourceService extends SystemService {
        }
    };

    private final AlarmManager.OnAlarmListener mUnusedWealthReclamationListener =
            new AlarmManager.OnAlarmListener() {
                @Override
                public void onAlarm() {
                    synchronized (mLock) {
                        mAgent.reclaimUnusedAssetsLocked(DEFAULT_UNUSED_RECLAMATION_PERCENTAGE);
                        mLastUnusedReclamationTime = System.currentTimeMillis();
                        scheduleUnusedWealthReclamationLocked();
                    }
                }
            };

    private static final int MSG_NOTIFY_BALANCE_CHANGE_LISTENERS = 0;
    private static final int MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT = 1;
    private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";

    /**
     * Initializes the system service.
@@ -186,6 +212,8 @@ public class InternalResourceService extends SystemService {
                } else {
                    mIsSetup = true;
                }
                scheduleUnusedWealthReclamationLocked();
                mCompleteEconomicPolicy.onSystemServicesReady();
            }
        }
    }
@@ -314,6 +342,26 @@ public class InternalResourceService extends SystemService {
                .sendToTarget();
    }

    @GuardedBy("mLock")
    private void scheduleUnusedWealthReclamationLocked() {
        final long now = System.currentTimeMillis();
        final long nextReclamationTime =
                Math.max(mLastUnusedReclamationTime + UNUSED_RECLAMATION_PERIOD_MS, now + 30_000);
        mHandler.post(() -> {
            // Never call out to AlarmManager with the lock held. This sits below AM.
            AlarmManager alarmManager = getContext().getSystemService(AlarmManager.class);
            if (alarmManager != null) {
                alarmManager.setWindow(AlarmManager.ELAPSED_REALTIME,
                        SystemClock.elapsedRealtime() + (nextReclamationTime - now),
                        30 * MINUTE_IN_MILLIS,
                        ALARM_TAG_WEALTH_RECLAMATION, mUnusedWealthReclamationListener, mHandler);
            } else {
                mHandler.sendEmptyMessageDelayed(
                        MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT, 30_000);
            }
        });
    }

    private int getCurrentBatteryLevel() {
        return mBatteryManagerInternal.getBatteryLevel();
    }
@@ -351,6 +399,13 @@ public class InternalResourceService extends SystemService {
                        }
                    }
                    break;

                case MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT:
                    removeMessages(MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT);
                    synchronized (mLock) {
                        scheduleUnusedWealthReclamationLocked();
                    }
                    break;
            }
        }
    }
+11 −0
Original line number Diff line number Diff line
@@ -462,6 +462,17 @@ public class AppIdleHistory {
        return getElapsedTime(elapsedRealtime) - appUsageHistory.lastJobRunTime;
    }

    public long getTimeSinceLastUsedByUser(String packageName, int userId, long elapsedRealtime) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
        AppUsageHistory appUsageHistory =
                getPackageHistory(userHistory, packageName, elapsedRealtime, false);
        if (appUsageHistory == null || appUsageHistory.lastUsedByUserElapsedTime == Long.MIN_VALUE
                || appUsageHistory.lastUsedByUserElapsedTime == 0) {
            return Long.MAX_VALUE;
        }
        return getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedByUserElapsedTime;
    }

    public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
        AppUsageHistory appUsageHistory =
Loading