Start reclaiming assets when circulation is too high.
As the battery level decreases, the current circulation may end up being higher than the scaled maximum circulation. When we get into those cases, we will reclaim some ARCs above app's minimum balance. If the circulation percentage is too high, we will lower all app's minimum balance in order to reclaim more credits. Bug: 158300259 Test: Adjust battery level via `adb shell cmd battery set level <lvl>` and verify reclamations occurred Change-Id: Ie0f91093f9dc98c04c409e18d562323d98a153d2
This commit is contained in:
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.server.tare;
|
||||
|
||||
import static android.text.format.DateUtils.HOUR_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;
|
||||
@@ -69,12 +67,6 @@ 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;
|
||||
|
||||
private static final String ALARM_TAG_AFFORDABILITY_CHECK = "*tare.affordability_check*";
|
||||
|
||||
private final Object mLock;
|
||||
@@ -550,41 +542,62 @@ 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.
|
||||
* reclamation will not reduce an app's balance below its minimum balance as dictated by
|
||||
* {@code scaleMinBalance}.
|
||||
*
|
||||
* @param percentage A value between 0 and 1 to indicate how much of the unused balance should
|
||||
* be reclaimed.
|
||||
* @param percentage A value between 0 and 1 to indicate how much of the unused balance
|
||||
* should be reclaimed.
|
||||
* @param minUnusedTimeMs The minimum amount of time (in milliseconds) that must have
|
||||
* transpired since the last user usage event before we will consider
|
||||
* reclaiming ARCs from the app.
|
||||
* @param scaleMinBalance Whether or not to used the scaled minimum app balance. If false,
|
||||
* this will use the constant min balance floor given by
|
||||
* {@link EconomicPolicy#getMinSatiatedBalance(int, String)}. If true,
|
||||
* this will use the scaled balance given by
|
||||
* {@link InternalResourceService#getMinBalanceLocked(int, String)}.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
void reclaimUnusedAssetsLocked(double percentage) {
|
||||
void reclaimUnusedAssetsLocked(double percentage, long minUnusedTimeMs,
|
||||
boolean scaleMinBalance) {
|
||||
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
|
||||
final List<PackageInfo> pkgs = mIrs.getInstalledPackages();
|
||||
final SparseArrayMap<String, Ledger> ledgers = mScribe.getLedgersLocked();
|
||||
final long now = getCurrentTimeMillis();
|
||||
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 = mScribe.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 = economicPolicy.getMinSatiatedBalance(userId, pkgName);
|
||||
for (int u = 0; u < ledgers.numMaps(); ++u) {
|
||||
final int userId = ledgers.keyAt(u);
|
||||
for (int p = 0; p < ledgers.numElementsForKey(userId); ++p) {
|
||||
final Ledger ledger = ledgers.valueAt(u, p);
|
||||
final long curBalance = ledger.getCurrentBalance();
|
||||
long toReclaim = (long) (curBalance * percentage);
|
||||
if (curBalance - toReclaim < minBalance) {
|
||||
toReclaim = curBalance - minBalance;
|
||||
if (curBalance <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (toReclaim > 0) {
|
||||
Slog.i(TAG, "Reclaiming unused wealth! Taking " + toReclaim
|
||||
+ " from " + appToString(userId, pkgName));
|
||||
final String pkgName = ledgers.keyAt(u, p);
|
||||
// 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 >= minUnusedTimeMs) {
|
||||
final long minBalance;
|
||||
if (!scaleMinBalance) {
|
||||
// Use a constant floor instead of the scaled floor from the IRS.
|
||||
minBalance = economicPolicy.getMinSatiatedBalance(userId, pkgName);
|
||||
} else {
|
||||
minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
|
||||
}
|
||||
long toReclaim = (long) (curBalance * percentage);
|
||||
if (curBalance - toReclaim < minBalance) {
|
||||
toReclaim = curBalance - minBalance;
|
||||
}
|
||||
if (toReclaim > 0) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Reclaiming unused wealth! Taking " + toReclaim
|
||||
+ " from " + appToString(userId, pkgName));
|
||||
}
|
||||
|
||||
recordTransactionLocked(userId, pkgName, ledger,
|
||||
new Ledger.Transaction(
|
||||
now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim),
|
||||
true);
|
||||
recordTransactionLocked(userId, pkgName, ledger,
|
||||
new Ledger.Transaction(
|
||||
now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.tare;
|
||||
|
||||
import static android.provider.Settings.Global.TARE_ALARM_MANAGER_CONSTANTS;
|
||||
import static android.provider.Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS;
|
||||
import static android.text.format.DateUtils.DAY_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
@@ -85,6 +86,11 @@ public class InternalResourceService extends SystemService {
|
||||
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;
|
||||
/**
|
||||
* The minimum amount of time an app must not have been used by the user before we start
|
||||
* periodically reclaiming ARCs from it.
|
||||
*/
|
||||
private static final long MIN_UNUSED_TIME_MS = 3 * DAY_IN_MILLIS;
|
||||
/** The amount of time to delay reclamation by after boot. */
|
||||
private static final long RECLAMATION_STARTUP_DELAY_MS = 30_000L;
|
||||
private static final int PACKAGE_QUERY_FLAGS =
|
||||
@@ -107,6 +113,44 @@ public class InternalResourceService extends SystemService {
|
||||
@GuardedBy("mLock")
|
||||
private CompleteEconomicPolicy mCompleteEconomicPolicy;
|
||||
|
||||
private static final class ReclamationConfig {
|
||||
/**
|
||||
* ARC circulation threshold (% circulating vs scaled maximum) above which this config
|
||||
* should come into play.
|
||||
*/
|
||||
public final double circulationPercentageThreshold;
|
||||
/** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
|
||||
public final double reclamationPercentage;
|
||||
/** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
|
||||
public final long minUsedTimeMs;
|
||||
/** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
|
||||
public final boolean scaleMinBalance;
|
||||
|
||||
ReclamationConfig(double circulationPercentageThreshold, double reclamationPercentage,
|
||||
long minUsedTimeMs, boolean scaleMinBalance) {
|
||||
this.circulationPercentageThreshold = circulationPercentageThreshold;
|
||||
this.reclamationPercentage = reclamationPercentage;
|
||||
this.minUsedTimeMs = minUsedTimeMs;
|
||||
this.scaleMinBalance = scaleMinBalance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorted list of reclamation configs used to determine how many credits to force reclaim when
|
||||
* the circulation percentage is too high. The list should *always* be sorted in descending
|
||||
* order of {@link ReclamationConfig#circulationPercentageThreshold}.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private final List<ReclamationConfig> mReclamationConfigs = List.of(
|
||||
new ReclamationConfig(2, .75, 12 * HOUR_IN_MILLIS, true),
|
||||
new ReclamationConfig(1.6, .5, DAY_IN_MILLIS, true),
|
||||
new ReclamationConfig(1.4, .25, DAY_IN_MILLIS, true),
|
||||
new ReclamationConfig(1.2, .25, 2 * DAY_IN_MILLIS, true),
|
||||
new ReclamationConfig(1, .25, MIN_UNUSED_TIME_MS, false),
|
||||
new ReclamationConfig(
|
||||
.9, DEFAULT_UNUSED_RECLAMATION_PERCENTAGE, MIN_UNUSED_TIME_MS, false)
|
||||
);
|
||||
|
||||
@NonNull
|
||||
@GuardedBy("mLock")
|
||||
private final List<PackageInfo> mPkgCache = new ArrayList<>();
|
||||
@@ -190,7 +234,8 @@ public class InternalResourceService extends SystemService {
|
||||
@Override
|
||||
public void onAlarm() {
|
||||
synchronized (mLock) {
|
||||
mAgent.reclaimUnusedAssetsLocked(DEFAULT_UNUSED_RECLAMATION_PERCENTAGE);
|
||||
mAgent.reclaimUnusedAssetsLocked(
|
||||
DEFAULT_UNUSED_RECLAMATION_PERCENTAGE, MIN_UNUSED_TIME_MS, false);
|
||||
mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
|
||||
scheduleUnusedWealthReclamationLocked();
|
||||
}
|
||||
@@ -200,6 +245,7 @@ public class InternalResourceService extends SystemService {
|
||||
private static final int MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER = 0;
|
||||
private static final int MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT = 1;
|
||||
private static final int MSG_PROCESS_USAGE_EVENT = 2;
|
||||
private static final int MSG_MAYBE_FOCE_RECLAIM = 3;
|
||||
private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
|
||||
private static final String KEY_PKG = "pkg";
|
||||
|
||||
@@ -317,6 +363,8 @@ public class InternalResourceService extends SystemService {
|
||||
final int newBatteryLevel = getCurrentBatteryLevel();
|
||||
if (newBatteryLevel > mCurrentBatteryLevel) {
|
||||
mAgent.distributeBasicIncomeLocked(newBatteryLevel);
|
||||
} else if (newBatteryLevel < mCurrentBatteryLevel) {
|
||||
mHandler.obtainMessage(MSG_MAYBE_FOCE_RECLAIM).sendToTarget();
|
||||
}
|
||||
mCurrentBatteryLevel = newBatteryLevel;
|
||||
}
|
||||
@@ -511,6 +559,48 @@ public class InternalResourceService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reclaim unused ARCs above apps' minimum balances if there are too many credits currently
|
||||
* in circulation.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private void maybeForceReclaimLocked() {
|
||||
final long maxCirculation = getMaxCirculationLocked();
|
||||
if (maxCirculation == 0) {
|
||||
Slog.wtf(TAG, "Max scaled circulation is 0...");
|
||||
mAgent.reclaimUnusedAssetsLocked(1, HOUR_IN_MILLIS, true);
|
||||
mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
|
||||
scheduleUnusedWealthReclamationLocked();
|
||||
return;
|
||||
}
|
||||
final long curCirculation = mScribe.getNarcsInCirculationLocked();
|
||||
final double circulationPerc = 1.0 * curCirculation / maxCirculation;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Circulation %: " + circulationPerc);
|
||||
}
|
||||
final int numConfigs = mReclamationConfigs.size();
|
||||
if (numConfigs == 0) {
|
||||
return;
|
||||
}
|
||||
// The configs are sorted in descending order of circulationPercentageThreshold, so we can
|
||||
// short-circuit if the current circulation is lower than the lowest threshold.
|
||||
if (circulationPerc
|
||||
< mReclamationConfigs.get(numConfigs - 1).circulationPercentageThreshold) {
|
||||
return;
|
||||
}
|
||||
// TODO: maybe exclude apps we think will be launched in the next few hours
|
||||
for (int i = 0; i < numConfigs; ++i) {
|
||||
final ReclamationConfig config = mReclamationConfigs.get(i);
|
||||
if (circulationPerc >= config.circulationPercentageThreshold) {
|
||||
mAgent.reclaimUnusedAssetsLocked(
|
||||
config.reclamationPercentage, config.minUsedTimeMs, config.scaleMinBalance);
|
||||
mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
|
||||
scheduleUnusedWealthReclamationLocked();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerListeners() {
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED);
|
||||
@@ -594,6 +684,14 @@ public class InternalResourceService extends SystemService {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_MAYBE_FOCE_RECLAIM: {
|
||||
removeMessages(MSG_MAYBE_FOCE_RECLAIM);
|
||||
synchronized (mLock) {
|
||||
maybeForceReclaimLocked();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: {
|
||||
Bundle data = msg.getData();
|
||||
final int userId = msg.arg1;
|
||||
|
||||
@@ -147,6 +147,12 @@ public class Scribe {
|
||||
return ledger;
|
||||
}
|
||||
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
@NonNull
|
||||
SparseArrayMap<String, Ledger> getLedgersLocked() {
|
||||
return mLedgers;
|
||||
}
|
||||
|
||||
/** Returns the total amount of narcs currently allocated to apps. */
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
long getNarcsInCirculationLocked() {
|
||||
|
||||
Reference in New Issue
Block a user