diff options
14 files changed, 779 insertions, 470 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java index 8c8d2bfe16e3..88082f7dfa4b 100644 --- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java +++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java @@ -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; diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 4e73b026abce..b9362789c6c6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -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; diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java index a6a007f46b58..c0a81487fe62 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java @@ -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; } } @@ -204,6 +160,11 @@ class Agent { } @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) { if (mIrs.isSystem(userId, pkgName)) { @@ -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,46 +285,80 @@ 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(); + 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); + } } - } 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); - }); + scheduleBalanceCheckLocked(userId, pkgName); + } + } + } + + @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.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); } } } - scheduleBalanceCheckLocked(userId, pkgName); } } } @@ -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(); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java index e1e6e47155aa..71e00cf053e2 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java @@ -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:"); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java index a4e7b808ca6c..2109a8575777 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java @@ -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 diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java index c1177b20bb0e..1e480155f18a 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java @@ -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, diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index 36895a5c3a25..c93480700ca7 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -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) { @@ -560,6 +560,23 @@ public class InternalResourceService extends SystemService { } @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) { return; @@ -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.print(narcToString(mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit())); pw.print("/"); - pw.println(narcToString(mCompleteEconomicPolicy.getMaxSatiatedCirculation())); + 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); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java index 1f8ce26a3fd9..0eddd2223d23 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -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:"); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java index f4917ad82761..dfdc20a32999 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java @@ -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(")"); } } } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java index 86968ef23a49..866211024169 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java @@ -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(); } } @@ -132,6 +137,11 @@ public class Scribe { } @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 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 getNarcsInCirculationLocked() { - return mNarcsInCirculation; + 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, @@ -249,6 +284,18 @@ public class Scribe { } @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; postWrite(); @@ -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); } diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java index 6751b804ad9e..41d46f223f4b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java @@ -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()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java index b72fc23bc16c..ab29e5903f02 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java @@ -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) { diff --git a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java index b1b53fc7ca4c..9e986be99e95 100644 --- a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java +++ b/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java @@ -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", @@ -257,7 +260,7 @@ public class AgentTrendCalculatorTest { } @Test - public void testSelectCorrectThreshold() { + 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); @@ -278,15 +281,15 @@ 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); + trendCalculator.reset(0, 10_000, 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, @@ -295,10 +298,10 @@ public class AgentTrendCalculatorTest { // 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); + trendCalculator.reset(30, 500, affordabilityNotes); trendCalculator.accept( new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1", - null, 1, -1)); + 1, new EconomicPolicy.Cost(1, 1))); assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs()); assertEquals("Expected not to cross upper threshold", @@ -307,7 +310,7 @@ public class AgentTrendCalculatorTest { } @Test - public void testTrendsToBothThresholds() { + 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); @@ -320,26 +323,120 @@ public class AgentTrendCalculatorTest { new AnticipatedAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 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 between both thresholds and events are mixed positive/negative delta. // Should report the correct time to each threshold. - trendCalculator.reset(35, affordabilityNotes); + trendCalculator.reset(35, 10_000, affordabilityNotes); trendCalculator.accept( - new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", - null, 1, 3)); + 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", - null, 2, 2)); + 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", - null, 3, -2)); + 3, new EconomicPolicy.Cost(2, 2))); trendCalculator.accept( new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "4", - null, 4, -3)); + 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(); + 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 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", 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("Expected not to cross upper threshold", + TrendCalculator.WILL_NOT_CROSS_THRESHOLD, + trendCalculator.getTimeToCrossUpperThresholdMs()); + + // 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", + 1, new EconomicPolicy.Cost(1, 10))); + + 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()); + } } diff --git a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java index 4a253234b59e..22dcf842906c 100644 --- a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java +++ b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java @@ -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); |