From 4bedfc19700ddee7a434e59e82690d14571659ae Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Tue, 3 Jan 2023 19:51:11 +0000 Subject: [PATCH] Exempt exact alarms from the stock limit. Allow apps to set and use exact alarms even if the TARE system has run out of consumable stock. Since apps need to hold the SCHEDULE_EXACT_ALARM or USE_EXACT_ALARM permissions to use exact alarms, we can be a little more lenient with the check. Enforcing the stock limit check could result in some unexpected user journeys. We continue to apply the app balance check so apps can't runaway with exact alarms. Bug: 258032540 Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/tare Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/tare Change-Id: I307fe35370e0450d2248e4bbdbcf0c00839d3214 --- .../java/com/android/server/tare/Agent.java | 41 +++++++++++-------- .../tare/AlarmManagerEconomicPolicy.java | 25 +++++++---- .../android/server/tare/EconomicPolicy.java | 11 +++++ 3 files changed, 54 insertions(+), 23 deletions(-) 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 b84c8a41af1cc..2b59209127183 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java @@ -164,8 +164,9 @@ class Agent { } @GuardedBy("mLock") - private boolean isAffordableLocked(long balance, long price, long ctp) { - return balance >= price && mScribe.getRemainingConsumableCakesLocked() >= ctp; + private boolean isAffordableLocked(long balance, long price, long stockLimitHonoringCtp) { + return balance >= price + && mScribe.getRemainingConsumableCakesLocked() >= stockLimitHonoringCtp; } @GuardedBy("mLock") @@ -303,7 +304,8 @@ class Agent { note.recalculateCosts(economicPolicy, userId, pkgName); final boolean isAffordable = isVip || isAffordableLocked(newBalance, - note.getCachedModifiedPrice(), note.getCtp()); + note.getCachedModifiedPrice(), + note.getStockLimitHonoringCtp()); if (note.isCurrentlyAffordable() != isAffordable) { note.setNewAffordability(isAffordable); mIrs.postAffordabilityChanged(userId, pkgName, note); @@ -339,7 +341,7 @@ class Agent { note.recalculateCosts(economicPolicy, userId, pkgName); final boolean isAffordable = isVip || isAffordableLocked(newBalance, - note.getCachedModifiedPrice(), note.getCtp()); + note.getCachedModifiedPrice(), note.getStockLimitHonoringCtp()); if (note.isCurrentlyAffordable() != isAffordable) { note.setNewAffordability(isAffordable); mIrs.postAffordabilityChanged(userId, pkgName, note); @@ -403,7 +405,8 @@ class Agent { note.recalculateCosts(economicPolicy, userId, pkgName); final boolean isAffordable = isVip || isAffordableLocked(newBalance, - note.getCachedModifiedPrice(), note.getCtp()); + note.getCachedModifiedPrice(), + note.getStockLimitHonoringCtp()); if (note.isCurrentlyAffordable() != isAffordable) { note.setNewAffordability(isAffordable); mIrs.postAffordabilityChanged(userId, pkgName, note); @@ -541,7 +544,7 @@ class Agent { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); final boolean isAffordable = isVip || isAffordableLocked(newBalance, - note.getCachedModifiedPrice(), note.getCtp()); + note.getCachedModifiedPrice(), note.getStockLimitHonoringCtp()); if (note.isCurrentlyAffordable() != isAffordable) { note.setNewAffordability(isAffordable); mIrs.postAffordabilityChanged(userId, pkgName, note); @@ -882,7 +885,7 @@ class Agent { mUpperThreshold = (mUpperThreshold == Long.MIN_VALUE) ? price : Math.min(mUpperThreshold, price); } - final long ctp = note.getCtp(); + final long ctp = note.getStockLimitHonoringCtp(); if (ctp <= mRemainingConsumableCredits) { mCtpThreshold = Math.max(mCtpThreshold, ctp); } @@ -1119,7 +1122,7 @@ class Agent { note.recalculateCosts(economicPolicy, userId, pkgName); note.setNewAffordability(isVip || isAffordableLocked(getBalanceLocked(userId, pkgName), - note.getCachedModifiedPrice(), note.getCtp())); + note.getCachedModifiedPrice(), note.getStockLimitHonoringCtp())); mIrs.postAffordabilityChanged(userId, pkgName, note); // Update ongoing alarm scheduleBalanceCheckLocked(userId, pkgName); @@ -1146,7 +1149,7 @@ class Agent { static final class ActionAffordabilityNote { private final EconomyManagerInternal.ActionBill mActionBill; private final EconomyManagerInternal.AffordabilityChangeListener mListener; - private long mCtp; + private long mStockLimitHonoringCtp; private long mModifiedPrice; private boolean mIsAffordable; @@ -1185,29 +1188,34 @@ class Agent { return mModifiedPrice; } - private long getCtp() { - return mCtp; + /** Returns the cumulative CTP of actions in this note that respect the stock limit. */ + private long getStockLimitHonoringCtp() { + return mStockLimitHonoringCtp; } @VisibleForTesting void recalculateCosts(@NonNull EconomicPolicy economicPolicy, int userId, @NonNull String pkgName) { long modifiedPrice = 0; - long ctp = 0; + long stockLimitHonoringCtp = 0; final List anticipatedActions = mActionBill.getAnticipatedActions(); for (int i = 0; i < anticipatedActions.size(); ++i) { final EconomyManagerInternal.AnticipatedAction aa = anticipatedActions.get(i); + final EconomicPolicy.Action action = economicPolicy.getAction(aa.actionId); final EconomicPolicy.Cost actionCost = economicPolicy.getCostOfAction(aa.actionId, userId, pkgName); modifiedPrice += actionCost.price * aa.numInstantaneousCalls + actionCost.price * (aa.ongoingDurationMs / 1000); - ctp += actionCost.costToProduce * aa.numInstantaneousCalls - + actionCost.costToProduce * (aa.ongoingDurationMs / 1000); + if (action.respectsStockLimit) { + stockLimitHonoringCtp += + actionCost.costToProduce * aa.numInstantaneousCalls + + actionCost.costToProduce * (aa.ongoingDurationMs / 1000); + } } mModifiedPrice = modifiedPrice; - mCtp = ctp; + mStockLimitHonoringCtp = stockLimitHonoringCtp; } boolean isCurrentlyAffordable() { @@ -1267,7 +1275,8 @@ class Agent { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); final boolean isAffordable = isVip || isAffordableLocked( - newBalance, note.getCachedModifiedPrice(), note.getCtp()); + newBalance, note.getCachedModifiedPrice(), + note.getStockLimitHonoringCtp()); if (note.isCurrentlyAffordable() != isAffordable) { note.setNewAffordability(isAffordable); mIrs.postAffordabilityChanged(userId, pkgName, note); 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 46338fa69eb0d..a6d064c760445 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java @@ -262,12 +262,17 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES); + // Apps must hold the SCHEDULE_EXACT_ALARM or USE_EXACT_ALARMS permission in order to use + // exact alarms. Since the user has the option of granting/revoking the permission, we can + // be a little lenient on the action cost checks and only stop the action if the app has + // run out of credits, and not when the system has run out of stock. mActions.put(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE, getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES), - exactAllowWhileIdleWakeupBasePrice)); + exactAllowWhileIdleWakeupBasePrice, + /* respectsStockLimit */ false)); mActions.put(ACTION_ALARM_WAKEUP_EXACT, new Action(ACTION_ALARM_WAKEUP_EXACT, getConstantAsCake(mParser, properties, @@ -275,7 +280,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES), getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES))); + DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES), + /* respectsStockLimit */ false)); final long inexactAllowWhileIdleWakeupBasePrice = getConstantAsCake(mParser, properties, @@ -287,7 +293,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES), - inexactAllowWhileIdleWakeupBasePrice)); + inexactAllowWhileIdleWakeupBasePrice, + /* respectsStockLimit */ false)); mActions.put(ACTION_ALARM_WAKEUP_INEXACT, new Action(ACTION_ALARM_WAKEUP_INEXACT, getConstantAsCake(mParser, properties, @@ -295,7 +302,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES), getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES))); + DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES), + /* respectsStockLimit */ false)); final long exactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE, @@ -305,7 +313,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES), - exactAllowWhileIdleNonWakeupBasePrice)); + exactAllowWhileIdleNonWakeupBasePrice, + /* respectsStockLimit */ false)); mActions.put(ACTION_ALARM_NONWAKEUP_EXACT, new Action(ACTION_ALARM_NONWAKEUP_EXACT, @@ -314,7 +323,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES), getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES))); + DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES), + /* respectsStockLimit */ false)); final long inexactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE, @@ -342,7 +352,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES), getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES))); + DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES), + /* respectsStockLimit */ false)); mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY, getConstantAsCake(mParser, properties, 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 b52f6f11b3bfc..a4043dd8ba789 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java @@ -149,11 +149,22 @@ public abstract class EconomicPolicy { * the action unless a modifier lowers the cost to produce. */ public final long basePrice; + /** + * Whether the remaining stock limit affects an app's ability to perform this action. + * If {@code false}, then the action can be performed, even if the cost is higher + * than the remaining stock. This does not affect checking against an app's balance. + */ + public final boolean respectsStockLimit; Action(int id, long costToProduce, long basePrice) { + this(id, costToProduce, basePrice, true); + } + + Action(int id, long costToProduce, long basePrice, boolean respectsStockLimit) { this.id = id; this.costToProduce = costToProduce; this.basePrice = basePrice; + this.respectsStockLimit = respectsStockLimit; } }