Merge "Implementing TIP1." into tm-dev

This commit is contained in:
Kweku Adams
2022-02-22 01:20:10 +00:00
committed by Android (Google) Code Review
14 changed files with 817 additions and 508 deletions

View File

@@ -39,7 +39,9 @@ public class EconomyManager {
/** @hide */
public static final String KEY_AM_MAX_SATIATED_BALANCE = "am_max_satiated_balance";
/** @hide */
public static final String KEY_AM_MAX_CIRCULATION = "am_max_circulation";
public static final String KEY_AM_INITIAL_CONSUMPTION_LIMIT = "am_initial_consumption_limit";
/** @hide */
public static final String KEY_AM_HARD_CONSUMPTION_LIMIT = "am_hard_consumption_limit";
// TODO: Add AlarmManager modifier keys
/** @hide */
public static final String KEY_AM_REWARD_TOP_ACTIVITY_INSTANT =
@@ -163,7 +165,9 @@ public class EconomyManager {
public static final String KEY_JS_MAX_SATIATED_BALANCE =
"js_max_satiated_balance";
/** @hide */
public static final String KEY_JS_MAX_CIRCULATION = "js_max_circulation";
public static final String KEY_JS_INITIAL_CONSUMPTION_LIMIT = "js_initial_consumption_limit";
/** @hide */
public static final String KEY_JS_HARD_CONSUMPTION_LIMIT = "js_hard_consumption_limit";
// TODO: Add JobScheduler modifier keys
/** @hide */
public static final String KEY_JS_REWARD_TOP_ACTIVITY_INSTANT =
@@ -280,7 +284,9 @@ public class EconomyManager {
/** @hide */
public static final int DEFAULT_AM_MAX_SATIATED_BALANCE = 1440;
/** @hide */
public static final int DEFAULT_AM_MAX_CIRCULATION = 52000;
public static final int DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT = 28800;
/** @hide */
public static final int DEFAULT_AM_HARD_CONSUMPTION_LIMIT = 52000;
// TODO: add AlarmManager modifier default values
/** @hide */
public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT = 0;
@@ -359,7 +365,7 @@ public class EconomyManager {
// Default values JobScheduler factors
// TODO: add time_since_usage variable to min satiated balance factors
/** @hide */
public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED = 50000;
public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED = 20000;
/** @hide */
public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP = 10000;
/** @hide */
@@ -367,7 +373,9 @@ public class EconomyManager {
/** @hide */
public static final int DEFAULT_JS_MAX_SATIATED_BALANCE = 60000;
/** @hide */
public static final int DEFAULT_JS_MAX_CIRCULATION = 691200;
public static final int DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT = 460_000;
/** @hide */
public static final int DEFAULT_JS_HARD_CONSUMPTION_LIMIT = 900_000;
// TODO: add JobScheduler modifier default values
/** @hide */
public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT = 0;

View File

@@ -3007,7 +3007,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
} else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Disconnected from power @ " + sElapsedRealtimeClock.millis());
Slog.d(TAG, "Battery discharging @ " + sElapsedRealtimeClock.millis());
}
if (mCharging) {
mCharging = false;

View File

@@ -55,9 +55,6 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.utils.AlarmQueue;
import libcore.util.EmptyArray;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
@@ -105,54 +102,13 @@ class Agent {
private final BalanceThresholdAlarmQueue mBalanceThresholdAlarmQueue;
/**
* Comparator to use to sort apps before we distribute ARCs so that we try to give the most
* important apps ARCs first.
* Check the affordability notes of all apps.
*/
@VisibleForTesting
final Comparator<PackageInfo> mPackageDistributionComparator =
new Comparator<PackageInfo>() {
@Override
public int compare(PackageInfo pi1, PackageInfo pi2) {
final ApplicationInfo appInfo1 = pi1.applicationInfo;
final ApplicationInfo appInfo2 = pi2.applicationInfo;
// Put any packages that don't declare an application at the end. A missing
// <application> tag likely means the app won't be doing any work anyway.
if (appInfo1 == null) {
if (appInfo2 == null) {
return 0;
}
return 1;
} else if (appInfo2 == null) {
return -1;
}
// Privileged apps eat first. They're likely required for the device to
// function properly.
// TODO: include headless system apps
if (appInfo1.isPrivilegedApp()) {
if (!appInfo2.isPrivilegedApp()) {
return -1;
}
} else if (appInfo2.isPrivilegedApp()) {
return 1;
}
// Sort by most recently used.
final long timeSinceLastUsedMs1 =
mAppStandbyInternal.getTimeSinceLastUsedByUser(
pi1.packageName, UserHandle.getUserId(pi1.applicationInfo.uid));
final long timeSinceLastUsedMs2 =
mAppStandbyInternal.getTimeSinceLastUsedByUser(
pi2.packageName, UserHandle.getUserId(pi2.applicationInfo.uid));
if (timeSinceLastUsedMs1 < timeSinceLastUsedMs2) {
return -1;
} else if (timeSinceLastUsedMs1 > timeSinceLastUsedMs2) {
return 1;
}
return 0;
}
};
private static final int MSG_CHECK_BALANCE = 0;
private static final int MSG_CHECK_ALL_AFFORDABILITY = 0;
/**
* Check the affordability notes of a single app.
*/
private static final int MSG_CHECK_INDIVIDUAL_AFFORDABILITY = 1;
Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe) {
mLock = irs.getLock();
@@ -179,7 +135,7 @@ class Agent {
@Override
public void accept(OngoingEvent ongoingEvent) {
mTotal += getActualDeltaLocked(ongoingEvent, mLedger, mNowElapsed, mNow);
mTotal += getActualDeltaLocked(ongoingEvent, mLedger, mNowElapsed, mNow).price;
}
}
@@ -203,6 +159,11 @@ class Agent {
return balance;
}
@GuardedBy("mLock")
private boolean isAffordableLocked(long balance, long price, long ctp) {
return balance >= price && mScribe.getRemainingConsumableNarcsLocked() >= ctp;
}
@GuardedBy("mLock")
void noteInstantaneousEventLocked(final int userId, @NonNull final String pkgName,
final int eventId, @Nullable String tag) {
@@ -218,10 +179,13 @@ class Agent {
final int eventType = getEventType(eventId);
switch (eventType) {
case TYPE_ACTION:
final long actionCost = economicPolicy.getCostOfAction(eventId, userId, pkgName);
final EconomicPolicy.Cost actionCost =
economicPolicy.getCostOfAction(eventId, userId, pkgName);
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, eventId, tag, -actionCost), true);
new Ledger.Transaction(now, now, eventId, tag,
-actionCost.price, actionCost.costToProduce),
true);
break;
case TYPE_REWARD:
@@ -231,7 +195,7 @@ class Agent {
final long rewardVal = Math.max(0,
Math.min(reward.maxDailyReward - rewardSum, reward.instantReward));
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, eventId, tag, rewardVal), true);
new Ledger.Transaction(now, now, eventId, tag, rewardVal, 0), true);
}
break;
@@ -268,11 +232,12 @@ class Agent {
final int eventType = getEventType(eventId);
switch (eventType) {
case TYPE_ACTION:
final long actionCost = economicPolicy.getCostOfAction(eventId, userId, pkgName);
final EconomicPolicy.Cost actionCost =
economicPolicy.getCostOfAction(eventId, userId, pkgName);
if (ongoingEvent == null) {
ongoingEvents.add(eventId, tag,
new OngoingEvent(eventId, tag, null, startElapsed, -actionCost));
new OngoingEvent(eventId, tag, startElapsed, actionCost));
} else {
ongoingEvent.refCount++;
}
@@ -283,7 +248,7 @@ class Agent {
if (reward != null) {
if (ongoingEvent == null) {
ongoingEvents.add(eventId, tag, new OngoingEvent(
eventId, tag, reward, startElapsed, reward.ongoingRewardPerSecond));
eventId, tag, startElapsed, reward));
} else {
ongoingEvent.refCount++;
}
@@ -306,52 +271,7 @@ class Agent {
@GuardedBy("mLock")
void onPricingChangedLocked() {
final long now = getCurrentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
mCurrentOngoingEvents.forEach((userId, pkgName, ongoingEvents) -> {
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
mActionAffordabilityNotes.get(userId, pkgName);
final boolean[] wasAffordable;
if (actionAffordabilityNotes != null) {
final int size = actionAffordabilityNotes.size();
wasAffordable = new boolean[size];
for (int i = 0; i < size; ++i) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
final long originalBalance =
mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
wasAffordable[i] = originalBalance >= note.getCachedModifiedPrice();
}
} else {
wasAffordable = EmptyArray.BOOLEAN;
}
ongoingEvents.forEach((ongoingEvent) -> {
// Disable balance check & affordability notifications here because we're in the
// middle of updating ongoing action costs/prices and sending out notifications
// or rescheduling the balance check alarm would be a waste since we'll have to
// redo them again after all of our internal state is updated.
stopOngoingActionLocked(userId, pkgName, ongoingEvent.eventId,
ongoingEvent.tag, nowElapsed, now, false, false);
noteOngoingEventLocked(userId, pkgName, ongoingEvent.eventId, ongoingEvent.tag,
nowElapsed, false);
});
if (actionAffordabilityNotes != null) {
final int size = actionAffordabilityNotes.size();
for (int i = 0; i < size; ++i) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
note.recalculateModifiedPrice(economicPolicy, userId, pkgName);
final long newBalance =
mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
final boolean isAffordable = newBalance >= note.getCachedModifiedPrice();
if (wasAffordable[i] != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
}
}
}
scheduleBalanceCheckLocked(userId, pkgName);
});
onAnythingChangedLocked(true);
}
@GuardedBy("mLock")
@@ -365,40 +285,21 @@ class Agent {
SparseArrayMap<String, OngoingEvent> ongoingEvents =
mCurrentOngoingEvents.get(userId, pkgName);
if (ongoingEvents != null) {
mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed);
ongoingEvents.forEach(mOngoingEventUpdater);
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
mActionAffordabilityNotes.get(userId, pkgName);
final boolean[] wasAffordable;
if (actionAffordabilityNotes != null) {
final int size = actionAffordabilityNotes.size();
wasAffordable = new boolean[size];
final long newBalance =
mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
for (int n = 0; n < size; ++n) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
final long originalBalance =
mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
wasAffordable[n] = originalBalance >= note.getCachedModifiedPrice();
}
} else {
wasAffordable = EmptyArray.BOOLEAN;
}
ongoingEvents.forEach((ongoingEvent) -> {
// Disable balance check & affordability notifications here because we're in the
// middle of updating ongoing action costs/prices and sending out notifications
// or rescheduling the balance check alarm would be a waste since we'll have to
// redo them again after all of our internal state is updated.
stopOngoingActionLocked(userId, pkgName, ongoingEvent.eventId,
ongoingEvent.tag, nowElapsed, now, false, false);
noteOngoingEventLocked(userId, pkgName, ongoingEvent.eventId, ongoingEvent.tag,
nowElapsed, false);
});
if (actionAffordabilityNotes != null) {
final int size = actionAffordabilityNotes.size();
for (int n = 0; n < size; ++n) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
note.recalculateModifiedPrice(economicPolicy, userId, pkgName);
final long newBalance =
mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
final boolean isAffordable = newBalance >= note.getCachedModifiedPrice();
if (wasAffordable[n] != isAffordable) {
note.recalculateCosts(economicPolicy, userId, pkgName);
final boolean isAffordable =
isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
}
@@ -409,6 +310,59 @@ class Agent {
}
}
@GuardedBy("mLock")
private void onAnythingChangedLocked(final boolean updateOngoingEvents) {
final long now = getCurrentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
for (int uIdx = mCurrentOngoingEvents.numMaps() - 1; uIdx >= 0; --uIdx) {
final int userId = mCurrentOngoingEvents.keyAt(uIdx);
for (int pIdx = mCurrentOngoingEvents.numElementsForKey(userId) - 1; pIdx >= 0;
--pIdx) {
final String pkgName = mCurrentOngoingEvents.keyAt(uIdx, pIdx);
SparseArrayMap<String, OngoingEvent> ongoingEvents =
mCurrentOngoingEvents.valueAt(uIdx, pIdx);
if (ongoingEvents != null) {
if (updateOngoingEvents) {
mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed);
ongoingEvents.forEach(mOngoingEventUpdater);
}
scheduleBalanceCheckLocked(userId, pkgName);
}
}
}
for (int uIdx = mActionAffordabilityNotes.numMaps() - 1; uIdx >= 0; --uIdx) {
final int userId = mActionAffordabilityNotes.keyAt(uIdx);
for (int pIdx = mActionAffordabilityNotes.numElementsForKey(userId) - 1; pIdx >= 0;
--pIdx) {
final String pkgName = mActionAffordabilityNotes.keyAt(uIdx, pIdx);
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
mActionAffordabilityNotes.valueAt(uIdx, pIdx);
if (actionAffordabilityNotes != null) {
final int size = actionAffordabilityNotes.size();
final long newBalance = getBalanceLocked(userId, pkgName);
for (int n = 0; n < size; ++n) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
note.recalculateCosts(economicPolicy, userId, pkgName);
final boolean isAffordable =
isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
}
}
}
}
}
}
@GuardedBy("mLock")
void stopOngoingActionLocked(final int userId, @NonNull final String pkgName, final int eventId,
@Nullable String tag, final long nowElapsed, final long now) {
@@ -416,9 +370,9 @@ class Agent {
}
/**
* @param updateBalanceCheck Whether or not to reschedule the affordability/balance
* @param updateBalanceCheck Whether to reschedule the affordability/balance
* check alarm.
* @param notifyOnAffordabilityChange Whether or not to evaluate the app's ability to afford
* @param notifyOnAffordabilityChange Whether to evaluate the app's ability to afford
* registered bills and notify listeners about any changes.
*/
@GuardedBy("mLock")
@@ -453,9 +407,11 @@ class Agent {
if (ongoingEvent.refCount <= 0) {
final long startElapsed = ongoingEvent.startTimeElapsed;
final long startTime = now - (nowElapsed - startElapsed);
final long actualDelta = getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
final EconomicPolicy.Cost actualDelta =
getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(startTime, now, eventId, tag, actualDelta),
new Ledger.Transaction(startTime, now, eventId, tag, actualDelta.price,
actualDelta.costToProduce),
notifyOnAffordabilityChange);
ongoingEvents.delete(eventId, tag);
@@ -466,17 +422,20 @@ class Agent {
}
@GuardedBy("mLock")
private long getActualDeltaLocked(@NonNull OngoingEvent ongoingEvent, @NonNull Ledger ledger,
long nowElapsed, long now) {
@NonNull
private EconomicPolicy.Cost getActualDeltaLocked(@NonNull OngoingEvent ongoingEvent,
@NonNull Ledger ledger, long nowElapsed, long now) {
final long startElapsed = ongoingEvent.startTimeElapsed;
final long durationSecs = (nowElapsed - startElapsed) / 1000;
final long computedDelta = durationSecs * ongoingEvent.deltaPerSec;
final long computedDelta = durationSecs * ongoingEvent.getDeltaPerSec();
if (ongoingEvent.reward == null) {
return computedDelta;
return new EconomicPolicy.Cost(
durationSecs * ongoingEvent.getCtpPerSec(), computedDelta);
}
final long rewardSum = ledger.get24HourSum(ongoingEvent.eventId, now);
return Math.max(0,
Math.min(ongoingEvent.reward.maxDailyReward - rewardSum, computedDelta));
return new EconomicPolicy.Cost(0,
Math.max(0,
Math.min(ongoingEvent.reward.maxDailyReward - rewardSum, computedDelta)));
}
@VisibleForTesting
@@ -494,22 +453,6 @@ class Agent {
return;
}
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
final long maxCirculationAllowed = mIrs.getMaxCirculationLocked();
final long curNarcsInCirculation = mScribe.getNarcsInCirculationLocked();
final long newArcsInCirculation = curNarcsInCirculation + transaction.delta;
if (transaction.delta > 0 && newArcsInCirculation > maxCirculationAllowed) {
// Set lower bound at 0 so we don't accidentally take away credits when we were trying
// to _give_ the app credits.
final long newDelta = Math.max(0, maxCirculationAllowed - curNarcsInCirculation);
Slog.i(TAG, "Would result in too many credits in circulation. Decreasing transaction "
+ eventToString(transaction.eventId)
+ (transaction.tag == null ? "" : ":" + transaction.tag)
+ " for " + appToString(userId, pkgName)
+ " by " + narcToString(transaction.delta - newDelta));
transaction = new Ledger.Transaction(
transaction.startTimeMs, transaction.endTimeMs,
transaction.eventId, transaction.tag, newDelta);
}
final long originalBalance = ledger.getCurrentBalance();
if (transaction.delta > 0
&& originalBalance + transaction.delta > economicPolicy.getMaxSatiatedBalance()) {
@@ -524,10 +467,10 @@ class Agent {
+ " by " + narcToString(transaction.delta - newDelta));
transaction = new Ledger.Transaction(
transaction.startTimeMs, transaction.endTimeMs,
transaction.eventId, transaction.tag, newDelta);
transaction.eventId, transaction.tag, newDelta, transaction.ctp);
}
ledger.recordTransaction(transaction);
mScribe.adjustNarcsInCirculationLocked(transaction.delta);
mScribe.adjustRemainingConsumableNarcsLocked(-transaction.ctp);
if (transaction.delta != 0 && notifyOnAffordabilityChange) {
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
mActionAffordabilityNotes.get(userId, pkgName);
@@ -535,7 +478,9 @@ class Agent {
final long newBalance = ledger.getCurrentBalance();
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
final boolean isAffordable = newBalance >= note.getCachedModifiedPrice();
final boolean isAffordable =
isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -543,6 +488,10 @@ class Agent {
}
}
}
if (transaction.ctp != 0) {
mHandler.sendEmptyMessage(MSG_CHECK_ALL_AFFORDABILITY);
mIrs.maybePerformQuantitativeEasingLocked();
}
}
/**
@@ -599,8 +548,8 @@ class Agent {
}
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(
now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim),
new Ledger.Transaction(now, now, REGULATION_WEALTH_RECLAMATION,
null, -toReclaim, 0),
true);
}
}
@@ -648,7 +597,7 @@ class Agent {
final long now = getCurrentTimeMillis();
final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim),
new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim, 0),
true);
}
}
@@ -665,10 +614,13 @@ class Agent {
return !mIrs.isSystem(userId, packageInfo.packageName);
}
void onCreditSupplyChanged() {
mHandler.sendEmptyMessage(MSG_CHECK_ALL_AFFORDABILITY);
}
@GuardedBy("mLock")
void distributeBasicIncomeLocked(int batteryLevel) {
List<PackageInfo> pkgs = mIrs.getInstalledPackages();
pkgs.sort(mPackageDistributionComparator);
final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
@@ -686,7 +638,7 @@ class Agent {
if (shortfall > 0) {
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_BASIC_INCOME,
null, (long) (perc * shortfall)), true);
null, (long) (perc * shortfall), 0), true);
}
}
}
@@ -705,12 +657,8 @@ class Agent {
@GuardedBy("mLock")
void grantBirthrightsLocked(final int userId) {
final List<PackageInfo> pkgs = mIrs.getInstalledPackages(userId);
final long maxBirthright =
mIrs.getMaxCirculationLocked() / mIrs.getInstalledPackages().size();
final long now = getCurrentTimeMillis();
pkgs.sort(mPackageDistributionComparator);
for (int i = 0; i < pkgs.size(); ++i) {
final PackageInfo packageInfo = pkgs.get(i);
if (!shouldGiveCredits(packageInfo)) {
@@ -726,7 +674,7 @@ class Agent {
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null,
Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))),
mIrs.getMinBalanceLocked(userId, pkgName), 0),
true);
}
}
@@ -740,14 +688,11 @@ class Agent {
return;
}
List<PackageInfo> pkgs = mIrs.getInstalledPackages();
final int numPackages = pkgs.size();
final long maxBirthright = mIrs.getMaxCirculationLocked() / numPackages;
final long now = getCurrentTimeMillis();
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null,
Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))), true);
mIrs.getMinBalanceLocked(userId, pkgName), 0), true);
}
@GuardedBy("mLock")
@@ -762,7 +707,7 @@ class Agent {
final long now = getCurrentTimeMillis();
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing), true);
new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing, 0), true);
}
@GuardedBy("mLock")
@@ -779,7 +724,7 @@ class Agent {
private void reclaimAssetsLocked(final int userId, @NonNull final String pkgName) {
final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
if (ledger.getCurrentBalance() != 0) {
mScribe.adjustNarcsInCirculationLocked(-ledger.getCurrentBalance());
mScribe.adjustRemainingConsumableNarcsLocked(-ledger.getCurrentBalance());
}
mScribe.discardLedgerLocked(userId, pkgName);
mCurrentOngoingEvents.delete(userId, pkgName);
@@ -803,6 +748,7 @@ class Agent {
static final long WILL_NOT_CROSS_THRESHOLD = -1;
private long mCurBalance;
private long mRemainingConsumableCredits;
/**
* The maximum change in credits per second towards the upper threshold
* {@link #mUpperThreshold}. A value of 0 means the current ongoing events will never
@@ -815,15 +761,25 @@ class Agent {
* result in the app crossing the lower threshold.
*/
private long mMaxDeltaPerSecToLowerThreshold;
/**
* The maximum change in credits per second towards the highest CTP threshold below the
* remaining consumable credits (cached in {@link #mCtpThreshold}). A value of 0 means
* the current ongoing events will never result in the app crossing the lower threshold.
*/
private long mMaxDeltaPerSecToCtpThreshold;
private long mUpperThreshold;
private long mLowerThreshold;
private long mCtpThreshold;
void reset(long curBalance,
void reset(long curBalance, long remainingConsumableCredits,
@Nullable ArraySet<ActionAffordabilityNote> actionAffordabilityNotes) {
mCurBalance = curBalance;
mRemainingConsumableCredits = remainingConsumableCredits;
mMaxDeltaPerSecToUpperThreshold = mMaxDeltaPerSecToLowerThreshold = 0;
mMaxDeltaPerSecToCtpThreshold = 0;
mUpperThreshold = Long.MIN_VALUE;
mLowerThreshold = Long.MAX_VALUE;
mCtpThreshold = 0;
if (actionAffordabilityNotes != null) {
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
@@ -835,6 +791,10 @@ class Agent {
mUpperThreshold = (mUpperThreshold == Long.MIN_VALUE)
? price : Math.min(mUpperThreshold, price);
}
final long ctp = note.getCtp();
if (ctp <= mRemainingConsumableCredits) {
mCtpThreshold = Math.max(mCtpThreshold, ctp);
}
}
}
}
@@ -847,13 +807,23 @@ class Agent {
* threshold.
*/
long getTimeToCrossLowerThresholdMs() {
if (mMaxDeltaPerSecToLowerThreshold == 0) {
// Will never cross upper threshold based on current events.
if (mMaxDeltaPerSecToLowerThreshold == 0 && mMaxDeltaPerSecToCtpThreshold == 0) {
// Will never cross lower threshold based on current events.
return WILL_NOT_CROSS_THRESHOLD;
}
// deltaPerSec is a negative value, so do threshold-balance to cancel out the negative.
final long minSeconds =
(mLowerThreshold - mCurBalance) / mMaxDeltaPerSecToLowerThreshold;
long minSeconds = Long.MAX_VALUE;
if (mMaxDeltaPerSecToLowerThreshold != 0) {
// deltaPerSec is a negative value, so do threshold-balance to cancel out the
// negative.
minSeconds = (mLowerThreshold - mCurBalance) / mMaxDeltaPerSecToLowerThreshold;
}
if (mMaxDeltaPerSecToCtpThreshold != 0) {
minSeconds = Math.min(minSeconds,
// deltaPerSec is a negative value, so do threshold-balance to cancel
// out the negative.
(mCtpThreshold - mRemainingConsumableCredits)
/ mMaxDeltaPerSecToCtpThreshold);
}
return minSeconds * 1000;
}
@@ -876,10 +846,15 @@ class Agent {
@Override
public void accept(OngoingEvent ongoingEvent) {
if (mCurBalance >= mLowerThreshold && ongoingEvent.deltaPerSec < 0) {
mMaxDeltaPerSecToLowerThreshold += ongoingEvent.deltaPerSec;
} else if (mCurBalance < mUpperThreshold && ongoingEvent.deltaPerSec > 0) {
mMaxDeltaPerSecToUpperThreshold += ongoingEvent.deltaPerSec;
final long deltaPerSec = ongoingEvent.getDeltaPerSec();
if (mCurBalance >= mLowerThreshold && deltaPerSec < 0) {
mMaxDeltaPerSecToLowerThreshold += deltaPerSec;
} else if (mCurBalance < mUpperThreshold && deltaPerSec > 0) {
mMaxDeltaPerSecToUpperThreshold += deltaPerSec;
}
final long ctpPerSec = ongoingEvent.getCtpPerSec();
if (mRemainingConsumableCredits >= mCtpThreshold && deltaPerSec < 0) {
mMaxDeltaPerSecToCtpThreshold -= ctpPerSec;
}
}
}
@@ -896,8 +871,9 @@ class Agent {
mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
return;
}
mTrendCalculator.reset(
getBalanceLocked(userId, pkgName), mActionAffordabilityNotes.get(userId, pkgName));
mTrendCalculator.reset(getBalanceLocked(userId, pkgName),
mScribe.getRemainingConsumableNarcsLocked(),
mActionAffordabilityNotes.get(userId, pkgName));
ongoingEvents.forEach(mTrendCalculator);
final long lowerTimeMs = mTrendCalculator.getTimeToCrossLowerThresholdMs();
final long upperTimeMs = mTrendCalculator.getTimeToCrossUpperThresholdMs();
@@ -931,20 +907,79 @@ class Agent {
public final String tag;
@Nullable
public final EconomicPolicy.Reward reward;
public final long deltaPerSec;
@Nullable
public final EconomicPolicy.Cost actionCost;
public int refCount;
OngoingEvent(int eventId, @Nullable String tag,
@Nullable EconomicPolicy.Reward reward, long startTimeElapsed, long deltaPerSec) {
OngoingEvent(int eventId, @Nullable String tag, long startTimeElapsed,
@NonNull EconomicPolicy.Reward reward) {
this.startTimeElapsed = startTimeElapsed;
this.eventId = eventId;
this.tag = tag;
this.reward = reward;
this.deltaPerSec = deltaPerSec;
this.actionCost = null;
refCount = 1;
}
OngoingEvent(int eventId, @Nullable String tag, long startTimeElapsed,
@NonNull EconomicPolicy.Cost actionCost) {
this.startTimeElapsed = startTimeElapsed;
this.eventId = eventId;
this.tag = tag;
this.reward = null;
this.actionCost = actionCost;
refCount = 1;
}
long getDeltaPerSec() {
if (actionCost != null) {
return -actionCost.price;
}
if (reward != null) {
return reward.ongoingRewardPerSecond;
}
Slog.wtfStack(TAG, "No action or reward in ongoing event?!??!");
return 0;
}
long getCtpPerSec() {
if (actionCost != null) {
return actionCost.costToProduce;
}
return 0;
}
}
private class OngoingEventUpdater implements Consumer<OngoingEvent> {
private int mUserId;
private String mPkgName;
private long mNow;
private long mNowElapsed;
private void reset(int userId, String pkgName, long now, long nowElapsed) {
mUserId = userId;
mPkgName = pkgName;
mNow = now;
mNowElapsed = nowElapsed;
}
@Override
public void accept(OngoingEvent ongoingEvent) {
// Disable balance check & affordability notifications here because
// we're in the middle of updating ongoing action costs/prices and
// sending out notifications or rescheduling the balance check alarm
// would be a waste since we'll have to redo them again after all of
// our internal state is updated.
final boolean updateBalanceCheck = false;
stopOngoingActionLocked(mUserId, mPkgName, ongoingEvent.eventId, ongoingEvent.tag,
mNowElapsed, mNow, updateBalanceCheck, /* notifyOnAffordabilityChange */ false);
noteOngoingEventLocked(mUserId, mPkgName, ongoingEvent.eventId, ongoingEvent.tag,
mNowElapsed, updateBalanceCheck);
}
}
private final OngoingEventUpdater mOngoingEventUpdater = new OngoingEventUpdater();
private static final class Package {
public final String packageName;
public final int userId;
@@ -996,7 +1031,8 @@ class Agent {
protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
for (int i = 0; i < expired.size(); ++i) {
Package p = expired.valueAt(i);
mHandler.obtainMessage(MSG_CHECK_BALANCE, p.userId, 0, p.packageName)
mHandler.obtainMessage(
MSG_CHECK_INDIVIDUAL_AFFORDABILITY, p.userId, 0, p.packageName)
.sendToTarget();
}
}
@@ -1023,9 +1059,10 @@ class Agent {
note.setNewAffordability(true);
return;
}
note.recalculateModifiedPrice(economicPolicy, userId, pkgName);
note.recalculateCosts(economicPolicy, userId, pkgName);
note.setNewAffordability(
getBalanceLocked(userId, pkgName) >= note.getCachedModifiedPrice());
isAffordableLocked(getBalanceLocked(userId, pkgName),
note.getCachedModifiedPrice(), note.getCtp()));
mIrs.postAffordabilityChanged(userId, pkgName, note);
// Update ongoing alarm
scheduleBalanceCheckLocked(userId, pkgName);
@@ -1052,6 +1089,7 @@ class Agent {
static final class ActionAffordabilityNote {
private final EconomyManagerInternal.ActionBill mActionBill;
private final EconomyManagerInternal.AffordabilityChangeListener mListener;
private long mCtp;
private long mModifiedPrice;
private boolean mIsAffordable;
@@ -1086,22 +1124,29 @@ class Agent {
return mModifiedPrice;
}
private long getCtp() {
return mCtp;
}
@VisibleForTesting
long recalculateModifiedPrice(@NonNull EconomicPolicy economicPolicy,
void recalculateCosts(@NonNull EconomicPolicy economicPolicy,
int userId, @NonNull String pkgName) {
long modifiedPrice = 0;
long ctp = 0;
final List<EconomyManagerInternal.AnticipatedAction> anticipatedActions =
mActionBill.getAnticipatedActions();
for (int i = 0; i < anticipatedActions.size(); ++i) {
final EconomyManagerInternal.AnticipatedAction aa = anticipatedActions.get(i);
final long actionCost =
final EconomicPolicy.Cost actionCost =
economicPolicy.getCostOfAction(aa.actionId, userId, pkgName);
modifiedPrice += actionCost * aa.numInstantaneousCalls
+ actionCost * (aa.ongoingDurationMs / 1000);
modifiedPrice += actionCost.price * aa.numInstantaneousCalls
+ actionCost.price * (aa.ongoingDurationMs / 1000);
ctp += actionCost.costToProduce * aa.numInstantaneousCalls
+ actionCost.costToProduce * (aa.ongoingDurationMs / 1000);
}
mModifiedPrice = modifiedPrice;
return modifiedPrice;
mCtp = ctp;
}
boolean isCurrentlyAffordable() {
@@ -1138,7 +1183,15 @@ class Agent {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CHECK_BALANCE: {
case MSG_CHECK_ALL_AFFORDABILITY: {
synchronized (mLock) {
removeMessages(MSG_CHECK_ALL_AFFORDABILITY);
onAnythingChangedLocked(false);
}
}
break;
case MSG_CHECK_INDIVIDUAL_AFFORDABILITY: {
final int userId = msg.arg1;
final String pkgName = (String) msg.obj;
synchronized (mLock) {
@@ -1151,8 +1204,8 @@ class Agent {
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
final ActionAffordabilityNote note =
actionAffordabilityNotes.valueAt(i);
final boolean isAffordable =
newBalance >= note.getCachedModifiedPrice();
final boolean isAffordable = isAffordableLocked(
newBalance, note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -1207,7 +1260,12 @@ class Agent {
pw.print(" runtime=");
TimeUtils.formatDuration(nowElapsed - ongoingEvent.startTimeElapsed, pw);
pw.print(" delta/sec=");
pw.print(ongoingEvent.deltaPerSec);
pw.print(narcToString(ongoingEvent.getDeltaPerSec()));
final long ctp = ongoingEvent.getCtpPerSec();
if (ctp != 0) {
pw.print(" ctp/sec=");
pw.print(narcToString(ongoingEvent.getCtpPerSec()));
}
pw.print(" refCount=");
pw.print(ongoingEvent.refCount);
pw.println();

View File

@@ -33,7 +33,8 @@ import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NO
import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP;
import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE;
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_HARD_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT;
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;
@@ -70,7 +71,8 @@ import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAK
import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP;
import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE;
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_HARD_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT;
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;
@@ -143,7 +145,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
private long mMinSatiatedBalanceExempted;
private long mMinSatiatedBalanceOther;
private long mMaxSatiatedBalance;
private long mMaxSatiatedCirculation;
private long mInitialSatiatedConsumptionLimit;
private long mHardSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final InternalResourceService mInternalResourceService;
@@ -179,8 +182,13 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
}
@Override
long getMaxSatiatedCirculation() {
return mMaxSatiatedCirculation;
long getInitialSatiatedConsumptionLimit() {
return mInitialSatiatedConsumptionLimit;
}
@Override
long getHardSatiatedConsumptionLimit() {
return mHardSatiatedConsumptionLimit;
}
@NonNull
@@ -217,8 +225,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP));
mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_AM_MAX_SATIATED_BALANCE,
DEFAULT_AM_MAX_SATIATED_BALANCE));
mMaxSatiatedCirculation = arcToNarc(mParser.getInt(KEY_AM_MAX_CIRCULATION,
DEFAULT_AM_MAX_CIRCULATION));
mInitialSatiatedConsumptionLimit = arcToNarc(mParser.getInt(
KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT));
mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit,
arcToNarc(mParser.getInt(
KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT)));
final long exactAllowWhileIdleWakeupBasePrice = arcToNarc(
mParser.getInt(KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE,
@@ -357,7 +368,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println();
pw.decreaseIndent();
pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
pw.print("Consumption limits: [");
pw.print(narcToString(mInitialSatiatedConsumptionLimit));
pw.print(", ");
pw.print(narcToString(mHardSatiatedConsumptionLimit));
pw.println("]");
pw.println();
pw.println("Actions:");

View File

@@ -34,7 +34,7 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
private final SparseArray<Reward> mRewards = new SparseArray<>();
private final int[] mCostModifiers;
private long mMaxSatiatedBalance;
private long mMaxSatiatedCirculation;
private long mConsumptionLimit;
CompleteEconomicPolicy(@NonNull InternalResourceService irs) {
super(irs);
@@ -74,9 +74,9 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
max = 0;
for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
max += mEnabledEconomicPolicies.valueAt(i).getMaxSatiatedCirculation();
max += mEnabledEconomicPolicies.valueAt(i).getInitialSatiatedConsumptionLimit();
}
mMaxSatiatedCirculation = max;
mConsumptionLimit = max;
}
@Override
@@ -94,8 +94,13 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
}
@Override
long getMaxSatiatedCirculation() {
return mMaxSatiatedCirculation;
long getInitialSatiatedConsumptionLimit() {
return mConsumptionLimit;
}
@Override
long getHardSatiatedConsumptionLimit() {
return mConsumptionLimit;
}
@NonNull

View File

@@ -151,6 +151,16 @@ public abstract class EconomicPolicy {
}
}
static class Cost {
public final long costToProduce;
public final long price;
Cost(long costToProduce, long price) {
this.costToProduce = costToProduce;
this.price = price;
}
}
private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS];
EconomicPolicy(@NonNull InternalResourceService irs) {
@@ -193,10 +203,18 @@ public abstract class EconomicPolicy {
abstract long getMaxSatiatedBalance();
/**
* Returns the maximum number of narcs that should be in circulation at once when the device is
* at 100% battery.
* Returns the maximum number of narcs that should be consumed during a full 100% discharge
* cycle. This is the initial limit. The system may choose to increase the limit over time,
* but the increased limit should never exceed the value returned from
* {@link #getHardSatiatedConsumptionLimit()}.
*/
abstract long getMaxSatiatedCirculation();
abstract long getInitialSatiatedConsumptionLimit();
/**
* Returns the maximum number of narcs that should be consumed during a full 100% discharge
* cycle. This is the hard limit that should never be exceeded.
*/
abstract long getHardSatiatedConsumptionLimit();
/** Return the set of modifiers that should apply to this policy's costs. */
@NonNull
@@ -211,10 +229,11 @@ public abstract class EconomicPolicy {
void dump(IndentingPrintWriter pw) {
}
final long getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
@NonNull
final Cost getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
final Action action = getAction(actionId);
if (action == null) {
return 0;
return new Cost(0, 0);
}
long ctp = action.costToProduce;
long price = action.basePrice;
@@ -235,7 +254,7 @@ public abstract class EconomicPolicy {
(ProcessStateModifier) getModifier(COST_MODIFIER_PROCESS_STATE);
price = processStateModifier.getModifiedPrice(userId, pkgName, ctp, price);
}
return price;
return new Cost(ctp, price);
}
private static void initModifier(@Modifier.CostModifier final int modifierId,

View File

@@ -69,6 +69,7 @@ import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.tare.EconomicPolicy.Cost;
import com.android.server.tare.EconomyManagerInternal.TareStateChangeListener;
import java.io.FileDescriptor;
@@ -100,6 +101,11 @@ public class InternalResourceService extends SystemService {
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;
/**
* The battery level above which we may consider quantitative easing (increasing the consumption
* limit).
*/
private static final int QUANTITATIVE_EASING_BATTERY_THRESHOLD = 50;
private static final int PACKAGE_QUERY_FLAGS =
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_APEX;
@@ -122,44 +128,6 @@ 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<>();
@@ -266,8 +234,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_FORCE_RECLAIM = 3;
private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 4;
private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 3;
private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
/**
@@ -362,8 +329,8 @@ public class InternalResourceService extends SystemService {
}
@GuardedBy("mLock")
long getMaxCirculationLocked() {
return mCurrentBatteryLevel * mCompleteEconomicPolicy.getMaxSatiatedCirculation() / 100;
long getConsumptionLimitLocked() {
return mCurrentBatteryLevel * mScribe.getSatiatedConsumptionLimitLocked() / 100;
}
@GuardedBy("mLock")
@@ -372,6 +339,11 @@ public class InternalResourceService extends SystemService {
/ 100;
}
@GuardedBy("mLock")
long getInitialSatiatedConsumptionLimitLocked() {
return mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
}
int getUid(final int userId, @NonNull final String pkgName) {
synchronized (mPackageToUidCache) {
Integer uid = mPackageToUidCache.get(userId, pkgName);
@@ -403,12 +375,15 @@ public class InternalResourceService extends SystemService {
void onBatteryLevelChanged() {
synchronized (mLock) {
final int newBatteryLevel = getCurrentBatteryLevel();
if (newBatteryLevel > mCurrentBatteryLevel) {
final boolean increased = newBatteryLevel > mCurrentBatteryLevel;
if (increased) {
mAgent.distributeBasicIncomeLocked(newBatteryLevel);
} else if (newBatteryLevel < mCurrentBatteryLevel) {
mHandler.obtainMessage(MSG_MAYBE_FORCE_RECLAIM).sendToTarget();
} else if (newBatteryLevel == mCurrentBatteryLevel) {
Slog.wtf(TAG, "Battery level stayed the same");
return;
}
mCurrentBatteryLevel = newBatteryLevel;
adjustCreditSupplyLocked(increased);
}
}
@@ -546,6 +521,31 @@ public class InternalResourceService extends SystemService {
}
}
/**
* Try to increase the consumption limit if apps are reaching the current limit too quickly.
*/
@GuardedBy("mLock")
void maybePerformQuantitativeEasingLocked() {
// We don't need to increase the limit if the device runs out of consumable credits
// when the battery is low.
final long remainingConsumableNarcs = mScribe.getRemainingConsumableNarcsLocked();
if (mCurrentBatteryLevel <= QUANTITATIVE_EASING_BATTERY_THRESHOLD
|| remainingConsumableNarcs > 0) {
return;
}
final long currentConsumptionLimit = mScribe.getSatiatedConsumptionLimitLocked();
final long shortfall = (mCurrentBatteryLevel - QUANTITATIVE_EASING_BATTERY_THRESHOLD)
* currentConsumptionLimit / 100;
final long newConsumptionLimit = Math.min(currentConsumptionLimit + shortfall,
mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit());
if (newConsumptionLimit != currentConsumptionLimit) {
Slog.i(TAG, "Increasing consumption limit from " + narcToString(currentConsumptionLimit)
+ " to " + narcToString(newConsumptionLimit));
mScribe.setConsumptionLimitLocked(newConsumptionLimit);
adjustCreditSupplyLocked(/* allowIncrease */ true);
}
}
void postAffordabilityChanged(final int userId, @NonNull final String pkgName,
@NonNull Agent.ActionAffordabilityNote affordabilityNote) {
if (DEBUG) {
@@ -559,6 +559,23 @@ public class InternalResourceService extends SystemService {
mHandler.obtainMessage(MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER, args).sendToTarget();
}
@GuardedBy("mLock")
private void adjustCreditSupplyLocked(boolean allowIncrease) {
final long newLimit = getConsumptionLimitLocked();
final long remainingConsumableNarcs = mScribe.getRemainingConsumableNarcsLocked();
if (remainingConsumableNarcs == newLimit) {
return;
}
if (remainingConsumableNarcs > newLimit) {
mScribe.adjustRemainingConsumableNarcsLocked(newLimit - remainingConsumableNarcs);
} else if (allowIncrease) {
final double perc = mCurrentBatteryLevel / 100d;
final long shortfall = newLimit - remainingConsumableNarcs;
mScribe.adjustRemainingConsumableNarcsLocked((long) (perc * shortfall));
}
mAgent.onCreditSupplyChanged();
}
@GuardedBy("mLock")
private void processUsageEventLocked(final int userId, @NonNull UsageEvents.Event event) {
if (!mIsEnabled) {
@@ -650,48 +667,6 @@ 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);
@@ -731,8 +706,18 @@ public class InternalResourceService extends SystemService {
final boolean isFirstSetup = !mScribe.recordExists();
if (isFirstSetup) {
mAgent.grantBirthrightsLocked();
mScribe.setConsumptionLimitLocked(
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
} else {
mScribe.loadFromDiskLocked();
if (mScribe.getSatiatedConsumptionLimitLocked()
< mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
|| mScribe.getSatiatedConsumptionLimitLocked()
> mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
// Reset the consumption limit since several factors may have changed.
mScribe.setConsumptionLimitLocked(
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
}
}
scheduleUnusedWealthReclamationLocked();
}
@@ -787,14 +772,6 @@ public class InternalResourceService extends SystemService {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_MAYBE_FORCE_RECLAIM: {
removeMessages(MSG_MAYBE_FORCE_RECLAIM);
synchronized (mLock) {
maybeForceReclaimLocked();
}
}
break;
case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: {
final SomeArgs args = (SomeArgs) msg.obj;
final int userId = args.argi1;
@@ -936,12 +913,13 @@ public class InternalResourceService extends SystemService {
synchronized (mLock) {
for (int i = 0; i < projectedActions.size(); ++i) {
AnticipatedAction action = projectedActions.get(i);
final long cost = mCompleteEconomicPolicy.getCostOfAction(
final Cost cost = mCompleteEconomicPolicy.getCostOfAction(
action.actionId, userId, pkgName);
requiredBalance += cost * action.numInstantaneousCalls
+ cost * (action.ongoingDurationMs / 1000);
requiredBalance += cost.price * action.numInstantaneousCalls
+ cost.price * (action.ongoingDurationMs / 1000);
}
return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance;
return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance
&& mScribe.getRemainingConsumableNarcsLocked() >= requiredBalance;
}
}
@@ -960,14 +938,17 @@ public class InternalResourceService extends SystemService {
synchronized (mLock) {
for (int i = 0; i < projectedActions.size(); ++i) {
AnticipatedAction action = projectedActions.get(i);
final long cost = mCompleteEconomicPolicy.getCostOfAction(
final Cost cost = mCompleteEconomicPolicy.getCostOfAction(
action.actionId, userId, pkgName);
totalCostPerSecond += cost;
totalCostPerSecond += cost.price;
}
if (totalCostPerSecond == 0) {
return FOREVER_MS;
}
return mAgent.getBalanceLocked(userId, pkgName) * 1000 / totalCostPerSecond;
final long minBalance = Math.min(
mAgent.getBalanceLocked(userId, pkgName),
mScribe.getRemainingConsumableNarcsLocked());
return minBalance * 1000 / totalCostPerSecond;
}
}
@@ -1085,10 +1066,20 @@ public class InternalResourceService extends SystemService {
private void updateEconomicPolicy() {
synchronized (mLock) {
final long initialLimit =
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
final long hardLimit = mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit();
mCompleteEconomicPolicy.tearDown();
mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this);
if (mIsEnabled && mBootPhase >= PHASE_SYSTEM_SERVICES_READY) {
mCompleteEconomicPolicy.setup();
if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
|| hardLimit
!= mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
// Reset the consumption limit since several factors may have changed.
mScribe.setConsumptionLimitLocked(
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
}
mAgent.onPricingChangedLocked();
}
}
@@ -1110,18 +1101,23 @@ public class InternalResourceService extends SystemService {
pw.print("Current battery level: ");
pw.println(mCurrentBatteryLevel);
final long maxCirculation = getMaxCirculationLocked();
pw.print("Max circulation (current/satiated): ");
pw.print(narcToString(maxCirculation));
final long consumptionLimit = getConsumptionLimitLocked();
pw.print("Consumption limit (current/initial-satiated/current-satiated): ");
pw.print(narcToString(consumptionLimit));
pw.print("/");
pw.println(narcToString(mCompleteEconomicPolicy.getMaxSatiatedCirculation()));
pw.print(narcToString(mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()));
pw.print("/");
pw.println(narcToString(mScribe.getSatiatedConsumptionLimitLocked()));
final long currentCirculation = mScribe.getNarcsInCirculationLocked();
pw.print("Current GDP: ");
pw.print(narcToString(currentCirculation));
final long remainingConsumable = mScribe.getRemainingConsumableNarcsLocked();
pw.print("Goods remaining: ");
pw.print(narcToString(remainingConsumable));
pw.print(" (");
pw.print(String.format("%.2f", 100f * currentCirculation / maxCirculation));
pw.println("% of current max)");
pw.print(String.format("%.2f", 100f * remainingConsumable / consumptionLimit));
pw.println("% of current limit)");
pw.print("Device wealth: ");
pw.println(narcToString(mScribe.getNarcsInCirculationForLoggingLocked()));
pw.println();
pw.print("Exempted apps", mExemptedApps);

View File

@@ -38,7 +38,8 @@ import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_BA
import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP;
import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE;
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_HARD_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT;
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;
@@ -79,7 +80,8 @@ import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_BASE_P
import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_CTP;
import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE;
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_HARD_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT;
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;
@@ -145,7 +147,8 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
private long mMinSatiatedBalanceExempted;
private long mMinSatiatedBalanceOther;
private long mMaxSatiatedBalance;
private long mMaxSatiatedCirculation;
private long mInitialSatiatedConsumptionLimit;
private long mHardSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final InternalResourceService mInternalResourceService;
@@ -181,8 +184,13 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
}
@Override
long getMaxSatiatedCirculation() {
return mMaxSatiatedCirculation;
long getInitialSatiatedConsumptionLimit() {
return mInitialSatiatedConsumptionLimit;
}
@Override
long getHardSatiatedConsumptionLimit() {
return mHardSatiatedConsumptionLimit;
}
@NonNull
@@ -221,8 +229,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP));
mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_JS_MAX_SATIATED_BALANCE,
DEFAULT_JS_MAX_SATIATED_BALANCE));
mMaxSatiatedCirculation = arcToNarc(mParser.getInt(KEY_JS_MAX_CIRCULATION,
DEFAULT_JS_MAX_CIRCULATION));
mInitialSatiatedConsumptionLimit = arcToNarc(mParser.getInt(
KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT));
mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit,
arcToNarc(mParser.getInt(
KEY_JS_HARD_CONSUMPTION_LIMIT, DEFAULT_JS_HARD_CONSUMPTION_LIMIT)));
mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START,
arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MAX_START_CTP,
@@ -332,7 +343,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println();
pw.decreaseIndent();
pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
pw.print("Consumption limits: [");
pw.print(narcToString(mInitialSatiatedConsumptionLimit));
pw.print(", ");
pw.print(narcToString(mHardSatiatedConsumptionLimit));
pw.println("]");
pw.println();
pw.println("Actions:");

View File

@@ -41,14 +41,16 @@ class Ledger {
@Nullable
public final String tag;
public final long delta;
public final long ctp;
Transaction(long startTimeMs, long endTimeMs,
int eventId, @Nullable String tag, long delta) {
int eventId, @Nullable String tag, long delta, long ctp) {
this.startTimeMs = startTimeMs;
this.endTimeMs = endTimeMs;
this.eventId = eventId;
this.tag = tag;
this.delta = delta;
this.ctp = ctp;
}
}
@@ -144,7 +146,10 @@ class Ledger {
pw.print(")");
}
pw.print(" --> ");
pw.println(narcToString(transaction.delta));
pw.print(narcToString(transaction.delta));
pw.print(" (ctp=");
pw.print(narcToString(transaction.ctp));
pw.println(")");
}
}
}

View File

@@ -73,6 +73,7 @@ public class Scribe {
private static final String XML_TAG_TRANSACTION = "transaction";
private static final String XML_TAG_USER = "user";
private static final String XML_ATTR_CTP = "ctp";
private static final String XML_ATTR_DELTA = "delta";
private static final String XML_ATTR_EVENT_ID = "eventId";
private static final String XML_ATTR_TAG = "tag";
@@ -83,6 +84,8 @@ public class Scribe {
private static final String XML_ATTR_USER_ID = "userId";
private static final String XML_ATTR_VERSION = "version";
private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime";
private static final String XML_ATTR_REMAINING_CONSUMABLE_NARCS = "remainingConsumableNarcs";
private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
/** Version of the file schema. */
private static final int STATE_FILE_VERSION = 0;
@@ -95,7 +98,9 @@ public class Scribe {
@GuardedBy("mIrs.getLock()")
private long mLastReclamationTime;
@GuardedBy("mIrs.getLock()")
private long mNarcsInCirculation;
private long mSatiatedConsumptionLimit;
@GuardedBy("mIrs.getLock()")
private long mRemainingConsumableNarcs;
@GuardedBy("mIrs.getLock()")
private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>();
@@ -117,10 +122,10 @@ public class Scribe {
}
@GuardedBy("mIrs.getLock()")
void adjustNarcsInCirculationLocked(long delta) {
void adjustRemainingConsumableNarcsLocked(long delta) {
if (delta != 0) {
// No point doing any work if the change is 0.
mNarcsInCirculation += delta;
mRemainingConsumableNarcs += delta;
postWrite();
}
}
@@ -131,6 +136,11 @@ public class Scribe {
postWrite();
}
@GuardedBy("mIrs.getLock()")
long getSatiatedConsumptionLimitLocked() {
return mSatiatedConsumptionLimit;
}
@GuardedBy("mIrs.getLock()")
long getLastReclamationTimeLocked() {
return mLastReclamationTime;
@@ -153,19 +163,37 @@ public class Scribe {
return mLedgers;
}
/** Returns the total amount of narcs currently allocated to apps. */
/**
* Returns the sum of credits granted to all apps on the system. This is expensive so don't
* call it for normal operation.
*/
@GuardedBy("mIrs.getLock()")
long getNarcsInCirculationLocked() {
return mNarcsInCirculation;
long getNarcsInCirculationForLoggingLocked() {
long sum = 0;
for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) {
for (int pIdx = mLedgers.numElementsForKeyAt(uIdx) - 1; pIdx >= 0; --pIdx) {
sum += mLedgers.valueAt(uIdx, pIdx).getCurrentBalance();
}
}
return sum;
}
/** Returns the total amount of narcs that remain to be consumed. */
@GuardedBy("mIrs.getLock()")
long getRemainingConsumableNarcsLocked() {
return mRemainingConsumableNarcs;
}
@GuardedBy("mIrs.getLock()")
void loadFromDiskLocked() {
mLedgers.clear();
mNarcsInCirculation = 0;
if (!recordExists()) {
mSatiatedConsumptionLimit = mIrs.getInitialSatiatedConsumptionLimitLocked();
mRemainingConsumableNarcs = mIrs.getConsumptionLimitLocked();
return;
}
mSatiatedConsumptionLimit = 0;
mRemainingConsumableNarcs = 0;
final SparseArray<ArraySet<String>> installedPackagesPerUser = new SparseArray<>();
final List<PackageInfo> installedPackages = mIrs.getInstalledPackages();
@@ -222,6 +250,13 @@ public class Scribe {
case XML_TAG_HIGH_LEVEL_STATE:
mLastReclamationTime =
parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME);
mSatiatedConsumptionLimit =
parser.getAttributeLong(null, XML_ATTR_CONSUMPTION_LIMIT,
mIrs.getInitialSatiatedConsumptionLimitLocked());
final long consumptionLimit = mIrs.getConsumptionLimitLocked();
mRemainingConsumableNarcs = Math.min(consumptionLimit,
parser.getAttributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_NARCS,
consumptionLimit));
break;
case XML_TAG_USER:
earliestEndTime = Math.min(earliestEndTime,
@@ -248,6 +283,18 @@ public class Scribe {
return mStateFile.exists();
}
@GuardedBy("mIrs.getLock()")
void setConsumptionLimitLocked(long limit) {
if (mRemainingConsumableNarcs > limit) {
mRemainingConsumableNarcs = limit;
} else if (limit > mSatiatedConsumptionLimit) {
final long diff = mSatiatedConsumptionLimit - mRemainingConsumableNarcs;
mRemainingConsumableNarcs = (limit - diff);
}
mSatiatedConsumptionLimit = limit;
postWrite();
}
@GuardedBy("mIrs.getLock()")
void setLastReclamationTimeLocked(long time) {
mLastReclamationTime = time;
@@ -259,7 +306,8 @@ public class Scribe {
TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
mLedgers.clear();
mNarcsInCirculation = 0;
mRemainingConsumableNarcs = 0;
mSatiatedConsumptionLimit = 0;
mLastReclamationTime = 0;
}
@@ -339,13 +387,14 @@ public class Scribe {
final long endTime = parser.getAttributeLong(null, XML_ATTR_END_TIME);
final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
final long ctp = parser.getAttributeLong(null, XML_ATTR_CTP);
if (endTime <= endTimeCutoff) {
if (DEBUG) {
Slog.d(TAG, "Skipping event because it's too old.");
}
continue;
}
transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta));
transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta, ctp));
}
if (!isInstalled) {
@@ -395,7 +444,6 @@ public class Scribe {
final Ledger ledger = ledgerData.second;
if (ledger != null) {
mLedgers.add(curUser, ledgerData.first, ledger);
mNarcsInCirculation += Math.max(0, ledger.getCurrentBalance());
final Ledger.Transaction transaction = ledger.getEarliestTransaction();
if (transaction != null) {
earliestEndTime = Math.min(earliestEndTime, transaction.endTimeMs);
@@ -442,6 +490,9 @@ public class Scribe {
out.startTag(null, XML_TAG_HIGH_LEVEL_STATE);
out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime);
out.attributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mSatiatedConsumptionLimit);
out.attributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_NARCS,
mRemainingConsumableNarcs);
out.endTag(null, XML_TAG_HIGH_LEVEL_STATE);
for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) {
@@ -505,6 +556,7 @@ public class Scribe {
out.attribute(null, XML_ATTR_TAG, transaction.tag);
}
out.attributeLong(null, XML_ATTR_DELTA, transaction.delta);
out.attributeLong(null, XML_ATTR_CTP, transaction.ctp);
out.endTag(null, XML_TAG_TRANSACTION);
}

View File

@@ -73,6 +73,7 @@ public class AgentTest {
.startMocking();
when(mIrs.getContext()).thenReturn(mContext);
when(mIrs.getCompleteEconomicPolicyLocked()).thenReturn(mEconomicPolicy);
when(mIrs.getLock()).thenReturn(mIrs);
when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mock(AlarmManager.class));
mScribe = new MockScribe(mIrs);
}
@@ -89,75 +90,75 @@ public class AgentTest {
Agent agent = new Agent(mIrs, mScribe);
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5);
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995);
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500);
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L);
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 500);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000_000L, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -1_000_001L);
transaction = new Ledger.Transaction(0, 0, 0, null, -1_000_001L, 1000);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(-1, ledger.getCurrentBalance());
}
@Test
public void testRecordTransaction_MaxCirculation() {
public void testRecordTransaction_MaxConsumptionLimit() {
Agent agent = new Agent(mIrs, mScribe);
Ledger ledger = new Ledger();
doReturn(1000L).when(mIrs).getMaxCirculationLocked();
doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance();
doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5);
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995);
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500);
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 2000);
transaction = new Ledger.Transaction(0, 0, 0, null, 2000, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
assertEquals(2500, ledger.getCurrentBalance());
// MaxCirculation can change as the battery level changes. Any already allocated ARCSs
// shouldn't be removed by recordTransaction().
doReturn(900L).when(mIrs).getMaxCirculationLocked();
// ConsumptionLimit can change as the battery level changes. Ledger balances shouldn't be
// affected.
doReturn(900L).when(mIrs).getConsumptionLimitLocked();
transaction = new Ledger.Transaction(0, 0, 0, null, 100);
transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
assertEquals(2600, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -50);
transaction = new Ledger.Transaction(0, 0, 0, null, -50, 50);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(950, ledger.getCurrentBalance());
assertEquals(2550, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -200);
transaction = new Ledger.Transaction(0, 0, 0, null, -200, 100);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(750, ledger.getCurrentBalance());
assertEquals(2350, ledger.getCurrentBalance());
doReturn(800L).when(mIrs).getMaxCirculationLocked();
doReturn(800L).when(mIrs).getConsumptionLimitLocked();
transaction = new Ledger.Transaction(0, 0, 0, null, 100);
transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(800, ledger.getCurrentBalance());
assertEquals(2450, ledger.getCurrentBalance());
}
@Test
@@ -165,33 +166,33 @@ public class AgentTest {
Agent agent = new Agent(mIrs, mScribe);
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance();
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5);
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995);
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500);
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L);
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 1000);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000, ledger.getCurrentBalance());
// Shouldn't change in normal operation, but adding test case in case it does.
doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance();
transaction = new Ledger.Transaction(0, 0, 0, null, 500);
transaction = new Ledger.Transaction(0, 0, 0, null, 500, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -1001);
transaction = new Ledger.Transaction(0, 0, 0, null, -1001, 500);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(-1, ledger.getCurrentBalance());
}

View File

@@ -107,20 +107,26 @@ public class ScribeTest {
@Test
public void testWriteHighLevelStateToDisk() {
long lastReclamationTime = System.currentTimeMillis();
long narcsInCirculation = 2000L;
long remainingConsumableNarcs = 2000L;
long consumptionLimit = 500_000L;
Ledger ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000));
ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000, 0));
// Negative ledger balance shouldn't affect the total circulation value.
ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID + 1, TEST_PACKAGE);
ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, -5000));
ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, -5000, 3000));
mScribeUnderTest.setLastReclamationTimeLocked(lastReclamationTime);
mScribeUnderTest.setConsumptionLimitLocked(consumptionLimit);
mScribeUnderTest.adjustRemainingConsumableNarcsLocked(
remainingConsumableNarcs - consumptionLimit);
mScribeUnderTest.writeImmediatelyForTesting();
mScribeUnderTest.loadFromDiskLocked();
assertEquals(lastReclamationTime, mScribeUnderTest.getLastReclamationTimeLocked());
assertEquals(narcsInCirculation, mScribeUnderTest.getNarcsInCirculationLocked());
assertEquals(remainingConsumableNarcs,
mScribeUnderTest.getRemainingConsumableNarcsLocked());
assertEquals(consumptionLimit, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
}
@Test
@@ -135,9 +141,9 @@ public class ScribeTest {
@Test
public void testWritingPopulatedLedgerToDisk() {
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 0));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, -1));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 12));
mScribeUnderTest.writeImmediatelyForTesting();
mScribeUnderTest.loadFromDiskLocked();
@@ -156,11 +162,11 @@ public class ScribeTest {
addInstalledPackage(userId, pkgName);
final Ledger ledger = mScribeUnderTest.getLedgerLocked(userId, pkgName);
ledger.recordTransaction(new Ledger.Transaction(
0, 1000L * u + l, 1, null, 51L * u + l));
0, 1000L * u + l, 1, null, -51L * u + l, 50));
ledger.recordTransaction(new Ledger.Transaction(
1500L * u + l, 2000L * u + l, 2 * u + l, "green" + u + l, 52L * u + l));
1500L * u + l, 2000L * u + l, 2 * u + l, "green" + u + l, 52L * u + l, 0));
ledger.recordTransaction(new Ledger.Transaction(
2500L * u + l, 3000L * u + l, 3 * u + l, "blue" + u + l, 3L * u + l));
2500L * u + l, 3000L * u + l, 3 * u + l, "blue" + u + l, 3L * u + l, 0));
ledgers.add(userId, pkgName, ledger);
}
}
@@ -174,9 +180,9 @@ public class ScribeTest {
@Test
public void testDiscardLedgerFromDisk() {
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 1));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 0));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 1));
mScribeUnderTest.writeImmediatelyForTesting();
mScribeUnderTest.loadFromDiskLocked();
@@ -195,9 +201,9 @@ public class ScribeTest {
public void testLoadingMissingPackageFromDisk() {
final String pkgName = TEST_PACKAGE + ".uninstalled";
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, pkgName);
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 1));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 2));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3));
mScribeUnderTest.writeImmediatelyForTesting();
// Package isn't installed, so make sure it's not saved to memory after loading.
@@ -209,9 +215,9 @@ public class ScribeTest {
public void testLoadingMissingUserFromDisk() {
final int userId = TEST_USER_ID + 1;
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE);
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 0));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 1));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3));
mScribeUnderTest.writeImmediatelyForTesting();
// User doesn't show up with any packages, so make sure nothing is saved after loading.
@@ -219,6 +225,37 @@ public class ScribeTest {
assertLedgersEqual(new Ledger(), mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE));
}
@Test
public void testChangingConsumable() {
assertEquals(0, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(0, mScribeUnderTest.getRemainingConsumableNarcsLocked());
// Limit increased, so remaining value should be adjusted as well
mScribeUnderTest.setConsumptionLimitLocked(1000);
assertEquals(1000, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(1000, mScribeUnderTest.getRemainingConsumableNarcsLocked());
// Limit decreased below remaining, so remaining value should be adjusted as well
mScribeUnderTest.setConsumptionLimitLocked(500);
assertEquals(500, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(500, mScribeUnderTest.getRemainingConsumableNarcsLocked());
mScribeUnderTest.adjustRemainingConsumableNarcsLocked(-100);
assertEquals(500, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(400, mScribeUnderTest.getRemainingConsumableNarcsLocked());
// Limit increased, so remaining value should be adjusted by the difference as well
mScribeUnderTest.setConsumptionLimitLocked(1000);
assertEquals(1000, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(900, mScribeUnderTest.getRemainingConsumableNarcsLocked());
// Limit decreased, but above remaining, so remaining value should left alone
mScribeUnderTest.setConsumptionLimitLocked(950);
assertEquals(950, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(900, mScribeUnderTest.getRemainingConsumableNarcsLocked());
}
private void assertLedgersEqual(Ledger expected, Ledger actual) {
if (expected == null) {
assertNull(actual);
@@ -245,6 +282,7 @@ public class ScribeTest {
assertEquals(expected.eventId, actual.eventId);
assertEquals(expected.tag, actual.tag);
assertEquals(expected.delta, actual.delta);
assertEquals(expected.ctp, actual.ctp);
}
private void addInstalledPackage(int userId, String pkgName) {

View File

@@ -65,7 +65,12 @@ public class AgentTrendCalculatorTest {
}
@Override
long getMaxSatiatedCirculation() {
long getInitialSatiatedConsumptionLimit() {
return 0;
}
@Override
long getHardSatiatedConsumptionLimit() {
return 0;
}
@@ -102,7 +107,7 @@ public class AgentTrendCalculatorTest {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, 20);
trendCalculator.reset(0, null);
trendCalculator.reset(0, 0, null);
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
@@ -115,10 +120,10 @@ public class AgentTrendCalculatorTest {
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
trendCalculator.reset(1234, affordabilityNotes);
trendCalculator.reset(1234, 1234, affordabilityNotes);
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
@@ -133,32 +138,28 @@ public class AgentTrendCalculatorTest {
OngoingEvent[] events = new OngoingEvent[]{
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
null, 1, 1),
1, new EconomicPolicy.Cost(1, 4)),
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
null, 2, 3),
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "3",
null, 3, -3),
2, new EconomicPolicy.Cost(3, 6)),
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "3", 3,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 0, 3, 3)),
};
trendCalculator.reset(0, null);
trendCalculator.reset(0, 100, null);
for (OngoingEvent event : events) {
trendCalculator.accept(event);
}
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(25_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
trendCalculator.reset(1234, affordabilityNotes);
trendCalculator.reset(1234, 1234, affordabilityNotes);
for (OngoingEvent event : events) {
trendCalculator.accept(event);
}
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(308_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
@@ -174,18 +175,19 @@ public class AgentTrendCalculatorTest {
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING, 0, 1000))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
// Balance is already above threshold and events are all positive delta.
// There should be no time to report.
trendCalculator.reset(1234, affordabilityNotes);
trendCalculator.reset(1234, 1234, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
null, 1, 1));
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
null, 2, 3));
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION,
3, 3, 3)));
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
@@ -196,16 +198,16 @@ public class AgentTrendCalculatorTest {
// Balance is already below threshold and events are all negative delta.
// There should be no time to report.
trendCalculator.reset(1, affordabilityNotes);
trendCalculator.reset(1, 0, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
null, 1, -1));
1, new EconomicPolicy.Cost(1, 1)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
null, 2, -3));
2, new EconomicPolicy.Cost(3, 3)));
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
0,
trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
@@ -213,7 +215,7 @@ public class AgentTrendCalculatorTest {
}
@Test
public void testSimpleTrendToThreshold() {
public void testSimpleTrendToThreshold_Balance() {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
@@ -222,18 +224,19 @@ public class AgentTrendCalculatorTest {
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
// Balance is below threshold and events are all positive delta.
// Should report the correct time to the upper threshold.
trendCalculator.reset(0, affordabilityNotes);
trendCalculator.reset(0, 1000, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
null, 1, 1));
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2",
null, 2, 3));
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION,
3, 3, 3)));
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
@@ -242,13 +245,13 @@ public class AgentTrendCalculatorTest {
// Balance is above the threshold and events are all negative delta.
// Should report the correct time to the lower threshold.
trendCalculator.reset(40, affordabilityNotes);
trendCalculator.reset(40, 100, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
null, 1, -1));
1, new EconomicPolicy.Cost(1, 1)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
null, 2, -3));
2, new EconomicPolicy.Cost(3, 3)));
assertEquals(5_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
@@ -256,6 +259,123 @@ public class AgentTrendCalculatorTest {
trendCalculator.getTimeToCrossUpperThresholdMs());
}
@Test
public void testSelectCorrectThreshold_Balance() {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 15);
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START, 10);
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 5);
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
// Balance is below threshold and events are all positive delta.
// Should report the correct time to the correct upper threshold.
trendCalculator.reset(0, 10_000, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(5_000, trendCalculator.getTimeToCrossUpperThresholdMs());
// Balance is above the threshold and events are all negative delta.
// Should report the correct time to the correct lower threshold.
trendCalculator.reset(30, 500, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
1, new EconomicPolicy.Cost(1, 1)));
assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
}
@Test
public void testTrendsToBothThresholds_Balance() {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
mEconomicPolicy.mEventCosts.put(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 50);
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
// Balance is between both thresholds and events are mixed positive/negative delta.
// Should report the correct time to each threshold.
trendCalculator.reset(35, 10_000, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 3, 3, 3)));
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, 2,
2, 2)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING, "3",
3, new EconomicPolicy.Cost(2, 2)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "4",
4, new EconomicPolicy.Cost(3, 3)));
assertEquals(3_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(3_000, trendCalculator.getTimeToCrossUpperThresholdMs());
}
@Test
public void testSimpleTrendToThreshold_ConsumptionLimit() {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
// Events are all negative delta. Consumable credits will run out before app's balance.
// Should report the correct time to the lower threshold.
trendCalculator.reset(10000, 40, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
1, new EconomicPolicy.Cost(1, 10)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
2, new EconomicPolicy.Cost(3, 40)));
assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
}
@Test
public void testSelectCorrectThreshold() {
TrendCalculator trendCalculator = new TrendCalculator();
@@ -278,68 +398,45 @@ public class AgentTrendCalculatorTest {
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
// Balance is below threshold and events are all positive delta.
// Should report the correct time to the correct upper threshold.
trendCalculator.reset(0, affordabilityNotes);
// Balance is above threshold, consumable credits is 0, and events are all positive delta.
// There should be no time to the upper threshold since consumable credits is the limiting
// factor.
trendCalculator.reset(10_000, 0, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
null, 1, 1));
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(5_000, trendCalculator.getTimeToCrossUpperThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
// Balance is above the threshold and events are all negative delta.
// Should report the correct time to the correct lower threshold.
trendCalculator.reset(30, affordabilityNotes);
// Balance is above threshold, consumable credits is low, and events are all negative delta.
trendCalculator.reset(10_000, 4, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
null, 1, -1));
1, new EconomicPolicy.Cost(1, 10)));
assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(4000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
// Balance is above threshold, consumable credits is 0, and events are all negative delta.
// Time to the lower threshold should be 0 since consumable credits is already 0.
trendCalculator.reset(10_000, 0, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
1, new EconomicPolicy.Cost(1, 10)));
assertEquals(0, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
}
@Test
public void testTrendsToBothThresholds() {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
mEconomicPolicy.mEventCosts.put(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 50);
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
}
// Balance is between both thresholds and events are mixed positive/negative delta.
// Should report the correct time to each threshold.
trendCalculator.reset(35, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
null, 1, 3));
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2",
null, 2, 2));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING, "3",
null, 3, -2));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "4",
null, 4, -3));
assertEquals(3_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(3_000, trendCalculator.getTimeToCrossUpperThresholdMs());
}
}

View File

@@ -54,13 +54,13 @@ public class LedgerTest {
@Test
public void testMultipleTransactions() {
final Ledger ledger = new Ledger();
ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 5));
ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 5, 0));
assertEquals(5, ledger.getCurrentBalance());
assertEquals(5, ledger.get24HourSum(1, 60_000));
ledger.recordTransaction(new Ledger.Transaction(2000, 2000, 1, null, 25));
ledger.recordTransaction(new Ledger.Transaction(2000, 2000, 1, null, 25, 0));
assertEquals(30, ledger.getCurrentBalance());
assertEquals(30, ledger.get24HourSum(1, 60_000));
ledger.recordTransaction(new Ledger.Transaction(5000, 5500, 1, null, -10));
ledger.recordTransaction(new Ledger.Transaction(5000, 5500, 1, null, -10, 5));
assertEquals(20, ledger.getCurrentBalance());
assertEquals(20, ledger.get24HourSum(1, 60_000));
}
@@ -68,13 +68,13 @@ public class LedgerTest {
@Test
public void test24HourSum() {
final Ledger ledger = new Ledger();
ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 500));
ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 500, 0));
assertEquals(500, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
ledger.recordTransaction(
new Ledger.Transaction(2 * HOUR_IN_MILLIS, 3 * HOUR_IN_MILLIS, 1, null, 2500));
new Ledger.Transaction(2 * HOUR_IN_MILLIS, 3 * HOUR_IN_MILLIS, 1, null, 2500, 0));
assertEquals(3000, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
ledger.recordTransaction(
new Ledger.Transaction(4 * HOUR_IN_MILLIS, 4 * HOUR_IN_MILLIS, 1, null, 1));
new Ledger.Transaction(4 * HOUR_IN_MILLIS, 4 * HOUR_IN_MILLIS, 1, null, 1, 0));
assertEquals(3001, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
assertEquals(2501, ledger.get24HourSum(1, 25 * HOUR_IN_MILLIS));
assertEquals(2501, ledger.get24HourSum(1, 26 * HOUR_IN_MILLIS));
@@ -93,17 +93,17 @@ public class LedgerTest {
final long now = getCurrentTimeMillis();
Ledger.Transaction transaction1 = new Ledger.Transaction(
now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800);
now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800, 0);
Ledger.Transaction transaction2 = new Ledger.Transaction(
now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600);
now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600, 0);
Ledger.Transaction transaction3 = new Ledger.Transaction(
now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600);
now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600, 0);
// Instant event
Ledger.Transaction transaction4 = new Ledger.Transaction(
now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500);
now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500, 0);
// Recent event
Ledger.Transaction transaction5 = new Ledger.Transaction(
now - 5 * MINUTE_IN_MILLIS, now - MINUTE_IN_MILLIS, 1, null, 400);
now - 5 * MINUTE_IN_MILLIS, now - MINUTE_IN_MILLIS, 1, null, 400, 0);
ledger.recordTransaction(transaction1);
ledger.recordTransaction(transaction2);
ledger.recordTransaction(transaction3);