Merge "Implementing TIP1." into tm-dev
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -55,9 +55,6 @@ import com.android.server.pm.UserManagerInternal;
|
||||
import com.android.server.usage.AppStandbyInternal;
|
||||
import com.android.server.utils.AlarmQueue;
|
||||
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
@@ -105,54 +102,13 @@ class Agent {
|
||||
private final BalanceThresholdAlarmQueue mBalanceThresholdAlarmQueue;
|
||||
|
||||
/**
|
||||
* Comparator to use to sort apps before we distribute ARCs so that we try to give the most
|
||||
* important apps ARCs first.
|
||||
* Check the affordability notes of all apps.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
final Comparator<PackageInfo> mPackageDistributionComparator =
|
||||
new Comparator<PackageInfo>() {
|
||||
@Override
|
||||
public int compare(PackageInfo pi1, PackageInfo pi2) {
|
||||
final ApplicationInfo appInfo1 = pi1.applicationInfo;
|
||||
final ApplicationInfo appInfo2 = pi2.applicationInfo;
|
||||
// Put any packages that don't declare an application at the end. A missing
|
||||
// <application> tag likely means the app won't be doing any work anyway.
|
||||
if (appInfo1 == null) {
|
||||
if (appInfo2 == null) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
} else if (appInfo2 == null) {
|
||||
return -1;
|
||||
}
|
||||
// Privileged apps eat first. They're likely required for the device to
|
||||
// function properly.
|
||||
// TODO: include headless system apps
|
||||
if (appInfo1.isPrivilegedApp()) {
|
||||
if (!appInfo2.isPrivilegedApp()) {
|
||||
return -1;
|
||||
}
|
||||
} else if (appInfo2.isPrivilegedApp()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort by most recently used.
|
||||
final long timeSinceLastUsedMs1 =
|
||||
mAppStandbyInternal.getTimeSinceLastUsedByUser(
|
||||
pi1.packageName, UserHandle.getUserId(pi1.applicationInfo.uid));
|
||||
final long timeSinceLastUsedMs2 =
|
||||
mAppStandbyInternal.getTimeSinceLastUsedByUser(
|
||||
pi2.packageName, UserHandle.getUserId(pi2.applicationInfo.uid));
|
||||
if (timeSinceLastUsedMs1 < timeSinceLastUsedMs2) {
|
||||
return -1;
|
||||
} else if (timeSinceLastUsedMs1 > timeSinceLastUsedMs2) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
private static final int MSG_CHECK_BALANCE = 0;
|
||||
private static final int MSG_CHECK_ALL_AFFORDABILITY = 0;
|
||||
/**
|
||||
* Check the affordability notes of a single app.
|
||||
*/
|
||||
private static final int MSG_CHECK_INDIVIDUAL_AFFORDABILITY = 1;
|
||||
|
||||
Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe) {
|
||||
mLock = irs.getLock();
|
||||
@@ -179,7 +135,7 @@ class Agent {
|
||||
|
||||
@Override
|
||||
public void accept(OngoingEvent ongoingEvent) {
|
||||
mTotal += getActualDeltaLocked(ongoingEvent, mLedger, mNowElapsed, mNow);
|
||||
mTotal += getActualDeltaLocked(ongoingEvent, mLedger, mNowElapsed, mNow).price;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +159,11 @@ class Agent {
|
||||
return balance;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean isAffordableLocked(long balance, long price, long ctp) {
|
||||
return balance >= price && mScribe.getRemainingConsumableNarcsLocked() >= ctp;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
void noteInstantaneousEventLocked(final int userId, @NonNull final String pkgName,
|
||||
final int eventId, @Nullable String tag) {
|
||||
@@ -218,10 +179,13 @@ class Agent {
|
||||
final int eventType = getEventType(eventId);
|
||||
switch (eventType) {
|
||||
case TYPE_ACTION:
|
||||
final long actionCost = economicPolicy.getCostOfAction(eventId, userId, pkgName);
|
||||
final EconomicPolicy.Cost actionCost =
|
||||
economicPolicy.getCostOfAction(eventId, userId, pkgName);
|
||||
|
||||
recordTransactionLocked(userId, pkgName, ledger,
|
||||
new Ledger.Transaction(now, now, eventId, tag, -actionCost), true);
|
||||
new Ledger.Transaction(now, now, eventId, tag,
|
||||
-actionCost.price, actionCost.costToProduce),
|
||||
true);
|
||||
break;
|
||||
|
||||
case TYPE_REWARD:
|
||||
@@ -231,7 +195,7 @@ class Agent {
|
||||
final long rewardVal = Math.max(0,
|
||||
Math.min(reward.maxDailyReward - rewardSum, reward.instantReward));
|
||||
recordTransactionLocked(userId, pkgName, ledger,
|
||||
new Ledger.Transaction(now, now, eventId, tag, rewardVal), true);
|
||||
new Ledger.Transaction(now, now, eventId, tag, rewardVal, 0), true);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -268,11 +232,12 @@ class Agent {
|
||||
final int eventType = getEventType(eventId);
|
||||
switch (eventType) {
|
||||
case TYPE_ACTION:
|
||||
final long actionCost = economicPolicy.getCostOfAction(eventId, userId, pkgName);
|
||||
final EconomicPolicy.Cost actionCost =
|
||||
economicPolicy.getCostOfAction(eventId, userId, pkgName);
|
||||
|
||||
if (ongoingEvent == null) {
|
||||
ongoingEvents.add(eventId, tag,
|
||||
new OngoingEvent(eventId, tag, null, startElapsed, -actionCost));
|
||||
new OngoingEvent(eventId, tag, startElapsed, actionCost));
|
||||
} else {
|
||||
ongoingEvent.refCount++;
|
||||
}
|
||||
@@ -283,7 +248,7 @@ class Agent {
|
||||
if (reward != null) {
|
||||
if (ongoingEvent == null) {
|
||||
ongoingEvents.add(eventId, tag, new OngoingEvent(
|
||||
eventId, tag, reward, startElapsed, reward.ongoingRewardPerSecond));
|
||||
eventId, tag, startElapsed, reward));
|
||||
} else {
|
||||
ongoingEvent.refCount++;
|
||||
}
|
||||
@@ -306,52 +271,7 @@ class Agent {
|
||||
|
||||
@GuardedBy("mLock")
|
||||
void onPricingChangedLocked() {
|
||||
final long now = getCurrentTimeMillis();
|
||||
final long nowElapsed = SystemClock.elapsedRealtime();
|
||||
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
|
||||
|
||||
mCurrentOngoingEvents.forEach((userId, pkgName, ongoingEvents) -> {
|
||||
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
|
||||
mActionAffordabilityNotes.get(userId, pkgName);
|
||||
final boolean[] wasAffordable;
|
||||
if (actionAffordabilityNotes != null) {
|
||||
final int size = actionAffordabilityNotes.size();
|
||||
wasAffordable = new boolean[size];
|
||||
for (int i = 0; i < size; ++i) {
|
||||
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
|
||||
final long originalBalance =
|
||||
mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
|
||||
wasAffordable[i] = originalBalance >= note.getCachedModifiedPrice();
|
||||
}
|
||||
} else {
|
||||
wasAffordable = EmptyArray.BOOLEAN;
|
||||
}
|
||||
ongoingEvents.forEach((ongoingEvent) -> {
|
||||
// Disable balance check & affordability notifications here because we're in the
|
||||
// middle of updating ongoing action costs/prices and sending out notifications
|
||||
// or rescheduling the balance check alarm would be a waste since we'll have to
|
||||
// redo them again after all of our internal state is updated.
|
||||
stopOngoingActionLocked(userId, pkgName, ongoingEvent.eventId,
|
||||
ongoingEvent.tag, nowElapsed, now, false, false);
|
||||
noteOngoingEventLocked(userId, pkgName, ongoingEvent.eventId, ongoingEvent.tag,
|
||||
nowElapsed, false);
|
||||
});
|
||||
if (actionAffordabilityNotes != null) {
|
||||
final int size = actionAffordabilityNotes.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
|
||||
note.recalculateModifiedPrice(economicPolicy, userId, pkgName);
|
||||
final long newBalance =
|
||||
mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
|
||||
final boolean isAffordable = newBalance >= note.getCachedModifiedPrice();
|
||||
if (wasAffordable[i] != isAffordable) {
|
||||
note.setNewAffordability(isAffordable);
|
||||
mIrs.postAffordabilityChanged(userId, pkgName, note);
|
||||
}
|
||||
}
|
||||
}
|
||||
scheduleBalanceCheckLocked(userId, pkgName);
|
||||
});
|
||||
onAnythingChangedLocked(true);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@@ -365,40 +285,21 @@ class Agent {
|
||||
SparseArrayMap<String, OngoingEvent> ongoingEvents =
|
||||
mCurrentOngoingEvents.get(userId, pkgName);
|
||||
if (ongoingEvents != null) {
|
||||
mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed);
|
||||
ongoingEvents.forEach(mOngoingEventUpdater);
|
||||
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
|
||||
mActionAffordabilityNotes.get(userId, pkgName);
|
||||
final boolean[] wasAffordable;
|
||||
if (actionAffordabilityNotes != null) {
|
||||
final int size = actionAffordabilityNotes.size();
|
||||
wasAffordable = new boolean[size];
|
||||
final long newBalance =
|
||||
mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
|
||||
for (int n = 0; n < size; ++n) {
|
||||
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
|
||||
final long originalBalance =
|
||||
mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
|
||||
wasAffordable[n] = originalBalance >= note.getCachedModifiedPrice();
|
||||
}
|
||||
} else {
|
||||
wasAffordable = EmptyArray.BOOLEAN;
|
||||
}
|
||||
ongoingEvents.forEach((ongoingEvent) -> {
|
||||
// Disable balance check & affordability notifications here because we're in the
|
||||
// middle of updating ongoing action costs/prices and sending out notifications
|
||||
// or rescheduling the balance check alarm would be a waste since we'll have to
|
||||
// redo them again after all of our internal state is updated.
|
||||
stopOngoingActionLocked(userId, pkgName, ongoingEvent.eventId,
|
||||
ongoingEvent.tag, nowElapsed, now, false, false);
|
||||
noteOngoingEventLocked(userId, pkgName, ongoingEvent.eventId, ongoingEvent.tag,
|
||||
nowElapsed, false);
|
||||
});
|
||||
if (actionAffordabilityNotes != null) {
|
||||
final int size = actionAffordabilityNotes.size();
|
||||
for (int n = 0; n < size; ++n) {
|
||||
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
|
||||
note.recalculateModifiedPrice(economicPolicy, userId, pkgName);
|
||||
final long newBalance =
|
||||
mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
|
||||
final boolean isAffordable = newBalance >= note.getCachedModifiedPrice();
|
||||
if (wasAffordable[n] != isAffordable) {
|
||||
note.recalculateCosts(economicPolicy, userId, pkgName);
|
||||
final boolean isAffordable =
|
||||
isAffordableLocked(newBalance,
|
||||
note.getCachedModifiedPrice(), note.getCtp());
|
||||
if (note.isCurrentlyAffordable() != isAffordable) {
|
||||
note.setNewAffordability(isAffordable);
|
||||
mIrs.postAffordabilityChanged(userId, pkgName, note);
|
||||
}
|
||||
@@ -409,6 +310,59 @@ class Agent {
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void onAnythingChangedLocked(final boolean updateOngoingEvents) {
|
||||
final long now = getCurrentTimeMillis();
|
||||
final long nowElapsed = SystemClock.elapsedRealtime();
|
||||
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
|
||||
|
||||
for (int uIdx = mCurrentOngoingEvents.numMaps() - 1; uIdx >= 0; --uIdx) {
|
||||
final int userId = mCurrentOngoingEvents.keyAt(uIdx);
|
||||
|
||||
for (int pIdx = mCurrentOngoingEvents.numElementsForKey(userId) - 1; pIdx >= 0;
|
||||
--pIdx) {
|
||||
final String pkgName = mCurrentOngoingEvents.keyAt(uIdx, pIdx);
|
||||
|
||||
SparseArrayMap<String, OngoingEvent> ongoingEvents =
|
||||
mCurrentOngoingEvents.valueAt(uIdx, pIdx);
|
||||
if (ongoingEvents != null) {
|
||||
if (updateOngoingEvents) {
|
||||
mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed);
|
||||
ongoingEvents.forEach(mOngoingEventUpdater);
|
||||
}
|
||||
scheduleBalanceCheckLocked(userId, pkgName);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int uIdx = mActionAffordabilityNotes.numMaps() - 1; uIdx >= 0; --uIdx) {
|
||||
final int userId = mActionAffordabilityNotes.keyAt(uIdx);
|
||||
|
||||
for (int pIdx = mActionAffordabilityNotes.numElementsForKey(userId) - 1; pIdx >= 0;
|
||||
--pIdx) {
|
||||
final String pkgName = mActionAffordabilityNotes.keyAt(uIdx, pIdx);
|
||||
|
||||
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
|
||||
mActionAffordabilityNotes.valueAt(uIdx, pIdx);
|
||||
|
||||
if (actionAffordabilityNotes != null) {
|
||||
final int size = actionAffordabilityNotes.size();
|
||||
final long newBalance = getBalanceLocked(userId, pkgName);
|
||||
for (int n = 0; n < size; ++n) {
|
||||
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
|
||||
note.recalculateCosts(economicPolicy, userId, pkgName);
|
||||
final boolean isAffordable =
|
||||
isAffordableLocked(newBalance,
|
||||
note.getCachedModifiedPrice(), note.getCtp());
|
||||
if (note.isCurrentlyAffordable() != isAffordable) {
|
||||
note.setNewAffordability(isAffordable);
|
||||
mIrs.postAffordabilityChanged(userId, pkgName, note);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
void stopOngoingActionLocked(final int userId, @NonNull final String pkgName, final int eventId,
|
||||
@Nullable String tag, final long nowElapsed, final long now) {
|
||||
@@ -416,9 +370,9 @@ class Agent {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param updateBalanceCheck Whether or not to reschedule the affordability/balance
|
||||
* @param updateBalanceCheck Whether to reschedule the affordability/balance
|
||||
* check alarm.
|
||||
* @param notifyOnAffordabilityChange Whether or not to evaluate the app's ability to afford
|
||||
* @param notifyOnAffordabilityChange Whether to evaluate the app's ability to afford
|
||||
* registered bills and notify listeners about any changes.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
@@ -453,9 +407,11 @@ class Agent {
|
||||
if (ongoingEvent.refCount <= 0) {
|
||||
final long startElapsed = ongoingEvent.startTimeElapsed;
|
||||
final long startTime = now - (nowElapsed - startElapsed);
|
||||
final long actualDelta = getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
|
||||
final EconomicPolicy.Cost actualDelta =
|
||||
getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
|
||||
recordTransactionLocked(userId, pkgName, ledger,
|
||||
new Ledger.Transaction(startTime, now, eventId, tag, actualDelta),
|
||||
new Ledger.Transaction(startTime, now, eventId, tag, actualDelta.price,
|
||||
actualDelta.costToProduce),
|
||||
notifyOnAffordabilityChange);
|
||||
|
||||
ongoingEvents.delete(eventId, tag);
|
||||
@@ -466,17 +422,20 @@ class Agent {
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private long getActualDeltaLocked(@NonNull OngoingEvent ongoingEvent, @NonNull Ledger ledger,
|
||||
long nowElapsed, long now) {
|
||||
@NonNull
|
||||
private EconomicPolicy.Cost getActualDeltaLocked(@NonNull OngoingEvent ongoingEvent,
|
||||
@NonNull Ledger ledger, long nowElapsed, long now) {
|
||||
final long startElapsed = ongoingEvent.startTimeElapsed;
|
||||
final long durationSecs = (nowElapsed - startElapsed) / 1000;
|
||||
final long computedDelta = durationSecs * ongoingEvent.deltaPerSec;
|
||||
final long computedDelta = durationSecs * ongoingEvent.getDeltaPerSec();
|
||||
if (ongoingEvent.reward == null) {
|
||||
return computedDelta;
|
||||
return new EconomicPolicy.Cost(
|
||||
durationSecs * ongoingEvent.getCtpPerSec(), computedDelta);
|
||||
}
|
||||
final long rewardSum = ledger.get24HourSum(ongoingEvent.eventId, now);
|
||||
return Math.max(0,
|
||||
Math.min(ongoingEvent.reward.maxDailyReward - rewardSum, computedDelta));
|
||||
return new EconomicPolicy.Cost(0,
|
||||
Math.max(0,
|
||||
Math.min(ongoingEvent.reward.maxDailyReward - rewardSum, computedDelta)));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -494,22 +453,6 @@ class Agent {
|
||||
return;
|
||||
}
|
||||
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
|
||||
final long maxCirculationAllowed = mIrs.getMaxCirculationLocked();
|
||||
final long curNarcsInCirculation = mScribe.getNarcsInCirculationLocked();
|
||||
final long newArcsInCirculation = curNarcsInCirculation + transaction.delta;
|
||||
if (transaction.delta > 0 && newArcsInCirculation > maxCirculationAllowed) {
|
||||
// Set lower bound at 0 so we don't accidentally take away credits when we were trying
|
||||
// to _give_ the app credits.
|
||||
final long newDelta = Math.max(0, maxCirculationAllowed - curNarcsInCirculation);
|
||||
Slog.i(TAG, "Would result in too many credits in circulation. Decreasing transaction "
|
||||
+ eventToString(transaction.eventId)
|
||||
+ (transaction.tag == null ? "" : ":" + transaction.tag)
|
||||
+ " for " + appToString(userId, pkgName)
|
||||
+ " by " + narcToString(transaction.delta - newDelta));
|
||||
transaction = new Ledger.Transaction(
|
||||
transaction.startTimeMs, transaction.endTimeMs,
|
||||
transaction.eventId, transaction.tag, newDelta);
|
||||
}
|
||||
final long originalBalance = ledger.getCurrentBalance();
|
||||
if (transaction.delta > 0
|
||||
&& originalBalance + transaction.delta > economicPolicy.getMaxSatiatedBalance()) {
|
||||
@@ -524,10 +467,10 @@ class Agent {
|
||||
+ " by " + narcToString(transaction.delta - newDelta));
|
||||
transaction = new Ledger.Transaction(
|
||||
transaction.startTimeMs, transaction.endTimeMs,
|
||||
transaction.eventId, transaction.tag, newDelta);
|
||||
transaction.eventId, transaction.tag, newDelta, transaction.ctp);
|
||||
}
|
||||
ledger.recordTransaction(transaction);
|
||||
mScribe.adjustNarcsInCirculationLocked(transaction.delta);
|
||||
mScribe.adjustRemainingConsumableNarcsLocked(-transaction.ctp);
|
||||
if (transaction.delta != 0 && notifyOnAffordabilityChange) {
|
||||
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
|
||||
mActionAffordabilityNotes.get(userId, pkgName);
|
||||
@@ -535,7 +478,9 @@ class Agent {
|
||||
final long newBalance = ledger.getCurrentBalance();
|
||||
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
|
||||
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
|
||||
final boolean isAffordable = newBalance >= note.getCachedModifiedPrice();
|
||||
final boolean isAffordable =
|
||||
isAffordableLocked(newBalance,
|
||||
note.getCachedModifiedPrice(), note.getCtp());
|
||||
if (note.isCurrentlyAffordable() != isAffordable) {
|
||||
note.setNewAffordability(isAffordable);
|
||||
mIrs.postAffordabilityChanged(userId, pkgName, note);
|
||||
@@ -543,6 +488,10 @@ class Agent {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (transaction.ctp != 0) {
|
||||
mHandler.sendEmptyMessage(MSG_CHECK_ALL_AFFORDABILITY);
|
||||
mIrs.maybePerformQuantitativeEasingLocked();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -599,8 +548,8 @@ class Agent {
|
||||
}
|
||||
|
||||
recordTransactionLocked(userId, pkgName, ledger,
|
||||
new Ledger.Transaction(
|
||||
now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim),
|
||||
new Ledger.Transaction(now, now, REGULATION_WEALTH_RECLAMATION,
|
||||
null, -toReclaim, 0),
|
||||
true);
|
||||
}
|
||||
}
|
||||
@@ -648,7 +597,7 @@ class Agent {
|
||||
final long now = getCurrentTimeMillis();
|
||||
final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
|
||||
recordTransactionLocked(userId, pkgName, ledger,
|
||||
new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim),
|
||||
new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim, 0),
|
||||
true);
|
||||
}
|
||||
}
|
||||
@@ -665,10 +614,13 @@ class Agent {
|
||||
return !mIrs.isSystem(userId, packageInfo.packageName);
|
||||
}
|
||||
|
||||
void onCreditSupplyChanged() {
|
||||
mHandler.sendEmptyMessage(MSG_CHECK_ALL_AFFORDABILITY);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
void distributeBasicIncomeLocked(int batteryLevel) {
|
||||
List<PackageInfo> pkgs = mIrs.getInstalledPackages();
|
||||
pkgs.sort(mPackageDistributionComparator);
|
||||
|
||||
final long now = getCurrentTimeMillis();
|
||||
for (int i = 0; i < pkgs.size(); ++i) {
|
||||
@@ -686,7 +638,7 @@ class Agent {
|
||||
if (shortfall > 0) {
|
||||
recordTransactionLocked(userId, pkgName, ledger,
|
||||
new Ledger.Transaction(now, now, REGULATION_BASIC_INCOME,
|
||||
null, (long) (perc * shortfall)), true);
|
||||
null, (long) (perc * shortfall), 0), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -705,12 +657,8 @@ class Agent {
|
||||
@GuardedBy("mLock")
|
||||
void grantBirthrightsLocked(final int userId) {
|
||||
final List<PackageInfo> pkgs = mIrs.getInstalledPackages(userId);
|
||||
final long maxBirthright =
|
||||
mIrs.getMaxCirculationLocked() / mIrs.getInstalledPackages().size();
|
||||
final long now = getCurrentTimeMillis();
|
||||
|
||||
pkgs.sort(mPackageDistributionComparator);
|
||||
|
||||
for (int i = 0; i < pkgs.size(); ++i) {
|
||||
final PackageInfo packageInfo = pkgs.get(i);
|
||||
if (!shouldGiveCredits(packageInfo)) {
|
||||
@@ -726,7 +674,7 @@ class Agent {
|
||||
|
||||
recordTransactionLocked(userId, pkgName, ledger,
|
||||
new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null,
|
||||
Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))),
|
||||
mIrs.getMinBalanceLocked(userId, pkgName), 0),
|
||||
true);
|
||||
}
|
||||
}
|
||||
@@ -740,14 +688,11 @@ class Agent {
|
||||
return;
|
||||
}
|
||||
|
||||
List<PackageInfo> pkgs = mIrs.getInstalledPackages();
|
||||
final int numPackages = pkgs.size();
|
||||
final long maxBirthright = mIrs.getMaxCirculationLocked() / numPackages;
|
||||
final long now = getCurrentTimeMillis();
|
||||
|
||||
recordTransactionLocked(userId, pkgName, ledger,
|
||||
new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null,
|
||||
Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))), true);
|
||||
mIrs.getMinBalanceLocked(userId, pkgName), 0), true);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@@ -762,7 +707,7 @@ class Agent {
|
||||
final long now = getCurrentTimeMillis();
|
||||
|
||||
recordTransactionLocked(userId, pkgName, ledger,
|
||||
new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing), true);
|
||||
new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing, 0), true);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@@ -779,7 +724,7 @@ class Agent {
|
||||
private void reclaimAssetsLocked(final int userId, @NonNull final String pkgName) {
|
||||
final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
|
||||
if (ledger.getCurrentBalance() != 0) {
|
||||
mScribe.adjustNarcsInCirculationLocked(-ledger.getCurrentBalance());
|
||||
mScribe.adjustRemainingConsumableNarcsLocked(-ledger.getCurrentBalance());
|
||||
}
|
||||
mScribe.discardLedgerLocked(userId, pkgName);
|
||||
mCurrentOngoingEvents.delete(userId, pkgName);
|
||||
@@ -803,6 +748,7 @@ class Agent {
|
||||
static final long WILL_NOT_CROSS_THRESHOLD = -1;
|
||||
|
||||
private long mCurBalance;
|
||||
private long mRemainingConsumableCredits;
|
||||
/**
|
||||
* The maximum change in credits per second towards the upper threshold
|
||||
* {@link #mUpperThreshold}. A value of 0 means the current ongoing events will never
|
||||
@@ -815,15 +761,25 @@ class Agent {
|
||||
* result in the app crossing the lower threshold.
|
||||
*/
|
||||
private long mMaxDeltaPerSecToLowerThreshold;
|
||||
/**
|
||||
* The maximum change in credits per second towards the highest CTP threshold below the
|
||||
* remaining consumable credits (cached in {@link #mCtpThreshold}). A value of 0 means
|
||||
* the current ongoing events will never result in the app crossing the lower threshold.
|
||||
*/
|
||||
private long mMaxDeltaPerSecToCtpThreshold;
|
||||
private long mUpperThreshold;
|
||||
private long mLowerThreshold;
|
||||
private long mCtpThreshold;
|
||||
|
||||
void reset(long curBalance,
|
||||
void reset(long curBalance, long remainingConsumableCredits,
|
||||
@Nullable ArraySet<ActionAffordabilityNote> actionAffordabilityNotes) {
|
||||
mCurBalance = curBalance;
|
||||
mRemainingConsumableCredits = remainingConsumableCredits;
|
||||
mMaxDeltaPerSecToUpperThreshold = mMaxDeltaPerSecToLowerThreshold = 0;
|
||||
mMaxDeltaPerSecToCtpThreshold = 0;
|
||||
mUpperThreshold = Long.MIN_VALUE;
|
||||
mLowerThreshold = Long.MAX_VALUE;
|
||||
mCtpThreshold = 0;
|
||||
if (actionAffordabilityNotes != null) {
|
||||
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
|
||||
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
|
||||
@@ -835,6 +791,10 @@ class Agent {
|
||||
mUpperThreshold = (mUpperThreshold == Long.MIN_VALUE)
|
||||
? price : Math.min(mUpperThreshold, price);
|
||||
}
|
||||
final long ctp = note.getCtp();
|
||||
if (ctp <= mRemainingConsumableCredits) {
|
||||
mCtpThreshold = Math.max(mCtpThreshold, ctp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -847,13 +807,23 @@ class Agent {
|
||||
* threshold.
|
||||
*/
|
||||
long getTimeToCrossLowerThresholdMs() {
|
||||
if (mMaxDeltaPerSecToLowerThreshold == 0) {
|
||||
// Will never cross upper threshold based on current events.
|
||||
if (mMaxDeltaPerSecToLowerThreshold == 0 && mMaxDeltaPerSecToCtpThreshold == 0) {
|
||||
// Will never cross lower threshold based on current events.
|
||||
return WILL_NOT_CROSS_THRESHOLD;
|
||||
}
|
||||
// deltaPerSec is a negative value, so do threshold-balance to cancel out the negative.
|
||||
final long minSeconds =
|
||||
(mLowerThreshold - mCurBalance) / mMaxDeltaPerSecToLowerThreshold;
|
||||
long minSeconds = Long.MAX_VALUE;
|
||||
if (mMaxDeltaPerSecToLowerThreshold != 0) {
|
||||
// deltaPerSec is a negative value, so do threshold-balance to cancel out the
|
||||
// negative.
|
||||
minSeconds = (mLowerThreshold - mCurBalance) / mMaxDeltaPerSecToLowerThreshold;
|
||||
}
|
||||
if (mMaxDeltaPerSecToCtpThreshold != 0) {
|
||||
minSeconds = Math.min(minSeconds,
|
||||
// deltaPerSec is a negative value, so do threshold-balance to cancel
|
||||
// out the negative.
|
||||
(mCtpThreshold - mRemainingConsumableCredits)
|
||||
/ mMaxDeltaPerSecToCtpThreshold);
|
||||
}
|
||||
return minSeconds * 1000;
|
||||
}
|
||||
|
||||
@@ -876,10 +846,15 @@ class Agent {
|
||||
|
||||
@Override
|
||||
public void accept(OngoingEvent ongoingEvent) {
|
||||
if (mCurBalance >= mLowerThreshold && ongoingEvent.deltaPerSec < 0) {
|
||||
mMaxDeltaPerSecToLowerThreshold += ongoingEvent.deltaPerSec;
|
||||
} else if (mCurBalance < mUpperThreshold && ongoingEvent.deltaPerSec > 0) {
|
||||
mMaxDeltaPerSecToUpperThreshold += ongoingEvent.deltaPerSec;
|
||||
final long deltaPerSec = ongoingEvent.getDeltaPerSec();
|
||||
if (mCurBalance >= mLowerThreshold && deltaPerSec < 0) {
|
||||
mMaxDeltaPerSecToLowerThreshold += deltaPerSec;
|
||||
} else if (mCurBalance < mUpperThreshold && deltaPerSec > 0) {
|
||||
mMaxDeltaPerSecToUpperThreshold += deltaPerSec;
|
||||
}
|
||||
final long ctpPerSec = ongoingEvent.getCtpPerSec();
|
||||
if (mRemainingConsumableCredits >= mCtpThreshold && deltaPerSec < 0) {
|
||||
mMaxDeltaPerSecToCtpThreshold -= ctpPerSec;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -896,8 +871,9 @@ class Agent {
|
||||
mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
|
||||
return;
|
||||
}
|
||||
mTrendCalculator.reset(
|
||||
getBalanceLocked(userId, pkgName), mActionAffordabilityNotes.get(userId, pkgName));
|
||||
mTrendCalculator.reset(getBalanceLocked(userId, pkgName),
|
||||
mScribe.getRemainingConsumableNarcsLocked(),
|
||||
mActionAffordabilityNotes.get(userId, pkgName));
|
||||
ongoingEvents.forEach(mTrendCalculator);
|
||||
final long lowerTimeMs = mTrendCalculator.getTimeToCrossLowerThresholdMs();
|
||||
final long upperTimeMs = mTrendCalculator.getTimeToCrossUpperThresholdMs();
|
||||
@@ -931,20 +907,79 @@ class Agent {
|
||||
public final String tag;
|
||||
@Nullable
|
||||
public final EconomicPolicy.Reward reward;
|
||||
public final long deltaPerSec;
|
||||
@Nullable
|
||||
public final EconomicPolicy.Cost actionCost;
|
||||
public int refCount;
|
||||
|
||||
OngoingEvent(int eventId, @Nullable String tag,
|
||||
@Nullable EconomicPolicy.Reward reward, long startTimeElapsed, long deltaPerSec) {
|
||||
OngoingEvent(int eventId, @Nullable String tag, long startTimeElapsed,
|
||||
@NonNull EconomicPolicy.Reward reward) {
|
||||
this.startTimeElapsed = startTimeElapsed;
|
||||
this.eventId = eventId;
|
||||
this.tag = tag;
|
||||
this.reward = reward;
|
||||
this.deltaPerSec = deltaPerSec;
|
||||
this.actionCost = null;
|
||||
refCount = 1;
|
||||
}
|
||||
|
||||
OngoingEvent(int eventId, @Nullable String tag, long startTimeElapsed,
|
||||
@NonNull EconomicPolicy.Cost actionCost) {
|
||||
this.startTimeElapsed = startTimeElapsed;
|
||||
this.eventId = eventId;
|
||||
this.tag = tag;
|
||||
this.reward = null;
|
||||
this.actionCost = actionCost;
|
||||
refCount = 1;
|
||||
}
|
||||
|
||||
long getDeltaPerSec() {
|
||||
if (actionCost != null) {
|
||||
return -actionCost.price;
|
||||
}
|
||||
if (reward != null) {
|
||||
return reward.ongoingRewardPerSecond;
|
||||
}
|
||||
Slog.wtfStack(TAG, "No action or reward in ongoing event?!??!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
long getCtpPerSec() {
|
||||
if (actionCost != null) {
|
||||
return actionCost.costToProduce;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private class OngoingEventUpdater implements Consumer<OngoingEvent> {
|
||||
private int mUserId;
|
||||
private String mPkgName;
|
||||
private long mNow;
|
||||
private long mNowElapsed;
|
||||
|
||||
private void reset(int userId, String pkgName, long now, long nowElapsed) {
|
||||
mUserId = userId;
|
||||
mPkgName = pkgName;
|
||||
mNow = now;
|
||||
mNowElapsed = nowElapsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(OngoingEvent ongoingEvent) {
|
||||
// Disable balance check & affordability notifications here because
|
||||
// we're in the middle of updating ongoing action costs/prices and
|
||||
// sending out notifications or rescheduling the balance check alarm
|
||||
// would be a waste since we'll have to redo them again after all of
|
||||
// our internal state is updated.
|
||||
final boolean updateBalanceCheck = false;
|
||||
stopOngoingActionLocked(mUserId, mPkgName, ongoingEvent.eventId, ongoingEvent.tag,
|
||||
mNowElapsed, mNow, updateBalanceCheck, /* notifyOnAffordabilityChange */ false);
|
||||
noteOngoingEventLocked(mUserId, mPkgName, ongoingEvent.eventId, ongoingEvent.tag,
|
||||
mNowElapsed, updateBalanceCheck);
|
||||
}
|
||||
}
|
||||
|
||||
private final OngoingEventUpdater mOngoingEventUpdater = new OngoingEventUpdater();
|
||||
|
||||
private static final class Package {
|
||||
public final String packageName;
|
||||
public final int userId;
|
||||
@@ -996,7 +1031,8 @@ class Agent {
|
||||
protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
|
||||
for (int i = 0; i < expired.size(); ++i) {
|
||||
Package p = expired.valueAt(i);
|
||||
mHandler.obtainMessage(MSG_CHECK_BALANCE, p.userId, 0, p.packageName)
|
||||
mHandler.obtainMessage(
|
||||
MSG_CHECK_INDIVIDUAL_AFFORDABILITY, p.userId, 0, p.packageName)
|
||||
.sendToTarget();
|
||||
}
|
||||
}
|
||||
@@ -1023,9 +1059,10 @@ class Agent {
|
||||
note.setNewAffordability(true);
|
||||
return;
|
||||
}
|
||||
note.recalculateModifiedPrice(economicPolicy, userId, pkgName);
|
||||
note.recalculateCosts(economicPolicy, userId, pkgName);
|
||||
note.setNewAffordability(
|
||||
getBalanceLocked(userId, pkgName) >= note.getCachedModifiedPrice());
|
||||
isAffordableLocked(getBalanceLocked(userId, pkgName),
|
||||
note.getCachedModifiedPrice(), note.getCtp()));
|
||||
mIrs.postAffordabilityChanged(userId, pkgName, note);
|
||||
// Update ongoing alarm
|
||||
scheduleBalanceCheckLocked(userId, pkgName);
|
||||
@@ -1052,6 +1089,7 @@ class Agent {
|
||||
static final class ActionAffordabilityNote {
|
||||
private final EconomyManagerInternal.ActionBill mActionBill;
|
||||
private final EconomyManagerInternal.AffordabilityChangeListener mListener;
|
||||
private long mCtp;
|
||||
private long mModifiedPrice;
|
||||
private boolean mIsAffordable;
|
||||
|
||||
@@ -1086,22 +1124,29 @@ class Agent {
|
||||
return mModifiedPrice;
|
||||
}
|
||||
|
||||
private long getCtp() {
|
||||
return mCtp;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
long recalculateModifiedPrice(@NonNull EconomicPolicy economicPolicy,
|
||||
void recalculateCosts(@NonNull EconomicPolicy economicPolicy,
|
||||
int userId, @NonNull String pkgName) {
|
||||
long modifiedPrice = 0;
|
||||
long ctp = 0;
|
||||
final List<EconomyManagerInternal.AnticipatedAction> anticipatedActions =
|
||||
mActionBill.getAnticipatedActions();
|
||||
for (int i = 0; i < anticipatedActions.size(); ++i) {
|
||||
final EconomyManagerInternal.AnticipatedAction aa = anticipatedActions.get(i);
|
||||
|
||||
final long actionCost =
|
||||
final EconomicPolicy.Cost actionCost =
|
||||
economicPolicy.getCostOfAction(aa.actionId, userId, pkgName);
|
||||
modifiedPrice += actionCost * aa.numInstantaneousCalls
|
||||
+ actionCost * (aa.ongoingDurationMs / 1000);
|
||||
modifiedPrice += actionCost.price * aa.numInstantaneousCalls
|
||||
+ actionCost.price * (aa.ongoingDurationMs / 1000);
|
||||
ctp += actionCost.costToProduce * aa.numInstantaneousCalls
|
||||
+ actionCost.costToProduce * (aa.ongoingDurationMs / 1000);
|
||||
}
|
||||
mModifiedPrice = modifiedPrice;
|
||||
return modifiedPrice;
|
||||
mCtp = ctp;
|
||||
}
|
||||
|
||||
boolean isCurrentlyAffordable() {
|
||||
@@ -1138,7 +1183,15 @@ class Agent {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_CHECK_BALANCE: {
|
||||
case MSG_CHECK_ALL_AFFORDABILITY: {
|
||||
synchronized (mLock) {
|
||||
removeMessages(MSG_CHECK_ALL_AFFORDABILITY);
|
||||
onAnythingChangedLocked(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MSG_CHECK_INDIVIDUAL_AFFORDABILITY: {
|
||||
final int userId = msg.arg1;
|
||||
final String pkgName = (String) msg.obj;
|
||||
synchronized (mLock) {
|
||||
@@ -1151,8 +1204,8 @@ class Agent {
|
||||
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
|
||||
final ActionAffordabilityNote note =
|
||||
actionAffordabilityNotes.valueAt(i);
|
||||
final boolean isAffordable =
|
||||
newBalance >= note.getCachedModifiedPrice();
|
||||
final boolean isAffordable = isAffordableLocked(
|
||||
newBalance, note.getCachedModifiedPrice(), note.getCtp());
|
||||
if (note.isCurrentlyAffordable() != isAffordable) {
|
||||
note.setNewAffordability(isAffordable);
|
||||
mIrs.postAffordabilityChanged(userId, pkgName, note);
|
||||
@@ -1207,7 +1260,12 @@ class Agent {
|
||||
pw.print(" runtime=");
|
||||
TimeUtils.formatDuration(nowElapsed - ongoingEvent.startTimeElapsed, pw);
|
||||
pw.print(" delta/sec=");
|
||||
pw.print(ongoingEvent.deltaPerSec);
|
||||
pw.print(narcToString(ongoingEvent.getDeltaPerSec()));
|
||||
final long ctp = ongoingEvent.getCtpPerSec();
|
||||
if (ctp != 0) {
|
||||
pw.print(" ctp/sec=");
|
||||
pw.print(narcToString(ongoingEvent.getCtpPerSec()));
|
||||
}
|
||||
pw.print(" refCount=");
|
||||
pw.print(ongoingEvent.refCount);
|
||||
pw.println();
|
||||
|
||||
@@ -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:");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -69,6 +69,7 @@ import com.android.internal.util.DumpUtils;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.pm.UserManagerInternal;
|
||||
import com.android.server.tare.EconomicPolicy.Cost;
|
||||
import com.android.server.tare.EconomyManagerInternal.TareStateChangeListener;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
@@ -100,6 +101,11 @@ public class InternalResourceService extends SystemService {
|
||||
private static final long MIN_UNUSED_TIME_MS = 3 * DAY_IN_MILLIS;
|
||||
/** The amount of time to delay reclamation by after boot. */
|
||||
private static final long RECLAMATION_STARTUP_DELAY_MS = 30_000L;
|
||||
/**
|
||||
* The battery level above which we may consider quantitative easing (increasing the consumption
|
||||
* limit).
|
||||
*/
|
||||
private static final int QUANTITATIVE_EASING_BATTERY_THRESHOLD = 50;
|
||||
private static final int PACKAGE_QUERY_FLAGS =
|
||||
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
|
||||
| PackageManager.MATCH_APEX;
|
||||
@@ -122,44 +128,6 @@ public class InternalResourceService extends SystemService {
|
||||
@GuardedBy("mLock")
|
||||
private CompleteEconomicPolicy mCompleteEconomicPolicy;
|
||||
|
||||
private static final class ReclamationConfig {
|
||||
/**
|
||||
* ARC circulation threshold (% circulating vs scaled maximum) above which this config
|
||||
* should come into play.
|
||||
*/
|
||||
public final double circulationPercentageThreshold;
|
||||
/** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
|
||||
public final double reclamationPercentage;
|
||||
/** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
|
||||
public final long minUsedTimeMs;
|
||||
/** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
|
||||
public final boolean scaleMinBalance;
|
||||
|
||||
ReclamationConfig(double circulationPercentageThreshold, double reclamationPercentage,
|
||||
long minUsedTimeMs, boolean scaleMinBalance) {
|
||||
this.circulationPercentageThreshold = circulationPercentageThreshold;
|
||||
this.reclamationPercentage = reclamationPercentage;
|
||||
this.minUsedTimeMs = minUsedTimeMs;
|
||||
this.scaleMinBalance = scaleMinBalance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorted list of reclamation configs used to determine how many credits to force reclaim when
|
||||
* the circulation percentage is too high. The list should *always* be sorted in descending
|
||||
* order of {@link ReclamationConfig#circulationPercentageThreshold}.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private final List<ReclamationConfig> mReclamationConfigs = List.of(
|
||||
new ReclamationConfig(2, .75, 12 * HOUR_IN_MILLIS, true),
|
||||
new ReclamationConfig(1.6, .5, DAY_IN_MILLIS, true),
|
||||
new ReclamationConfig(1.4, .25, DAY_IN_MILLIS, true),
|
||||
new ReclamationConfig(1.2, .25, 2 * DAY_IN_MILLIS, true),
|
||||
new ReclamationConfig(1, .25, MIN_UNUSED_TIME_MS, false),
|
||||
new ReclamationConfig(
|
||||
.9, DEFAULT_UNUSED_RECLAMATION_PERCENTAGE, MIN_UNUSED_TIME_MS, false)
|
||||
);
|
||||
|
||||
@NonNull
|
||||
@GuardedBy("mLock")
|
||||
private final List<PackageInfo> mPkgCache = new ArrayList<>();
|
||||
@@ -266,8 +234,7 @@ public class InternalResourceService extends SystemService {
|
||||
private static final int MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER = 0;
|
||||
private static final int MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT = 1;
|
||||
private static final int MSG_PROCESS_USAGE_EVENT = 2;
|
||||
private static final int MSG_MAYBE_FORCE_RECLAIM = 3;
|
||||
private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 4;
|
||||
private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 3;
|
||||
private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
|
||||
|
||||
/**
|
||||
@@ -362,8 +329,8 @@ public class InternalResourceService extends SystemService {
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
long getMaxCirculationLocked() {
|
||||
return mCurrentBatteryLevel * mCompleteEconomicPolicy.getMaxSatiatedCirculation() / 100;
|
||||
long getConsumptionLimitLocked() {
|
||||
return mCurrentBatteryLevel * mScribe.getSatiatedConsumptionLimitLocked() / 100;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@@ -372,6 +339,11 @@ public class InternalResourceService extends SystemService {
|
||||
/ 100;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
long getInitialSatiatedConsumptionLimitLocked() {
|
||||
return mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
|
||||
}
|
||||
|
||||
int getUid(final int userId, @NonNull final String pkgName) {
|
||||
synchronized (mPackageToUidCache) {
|
||||
Integer uid = mPackageToUidCache.get(userId, pkgName);
|
||||
@@ -403,12 +375,15 @@ public class InternalResourceService extends SystemService {
|
||||
void onBatteryLevelChanged() {
|
||||
synchronized (mLock) {
|
||||
final int newBatteryLevel = getCurrentBatteryLevel();
|
||||
if (newBatteryLevel > mCurrentBatteryLevel) {
|
||||
final boolean increased = newBatteryLevel > mCurrentBatteryLevel;
|
||||
if (increased) {
|
||||
mAgent.distributeBasicIncomeLocked(newBatteryLevel);
|
||||
} else if (newBatteryLevel < mCurrentBatteryLevel) {
|
||||
mHandler.obtainMessage(MSG_MAYBE_FORCE_RECLAIM).sendToTarget();
|
||||
} else if (newBatteryLevel == mCurrentBatteryLevel) {
|
||||
Slog.wtf(TAG, "Battery level stayed the same");
|
||||
return;
|
||||
}
|
||||
mCurrentBatteryLevel = newBatteryLevel;
|
||||
adjustCreditSupplyLocked(increased);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,6 +521,31 @@ public class InternalResourceService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to increase the consumption limit if apps are reaching the current limit too quickly.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
void maybePerformQuantitativeEasingLocked() {
|
||||
// We don't need to increase the limit if the device runs out of consumable credits
|
||||
// when the battery is low.
|
||||
final long remainingConsumableNarcs = mScribe.getRemainingConsumableNarcsLocked();
|
||||
if (mCurrentBatteryLevel <= QUANTITATIVE_EASING_BATTERY_THRESHOLD
|
||||
|| remainingConsumableNarcs > 0) {
|
||||
return;
|
||||
}
|
||||
final long currentConsumptionLimit = mScribe.getSatiatedConsumptionLimitLocked();
|
||||
final long shortfall = (mCurrentBatteryLevel - QUANTITATIVE_EASING_BATTERY_THRESHOLD)
|
||||
* currentConsumptionLimit / 100;
|
||||
final long newConsumptionLimit = Math.min(currentConsumptionLimit + shortfall,
|
||||
mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit());
|
||||
if (newConsumptionLimit != currentConsumptionLimit) {
|
||||
Slog.i(TAG, "Increasing consumption limit from " + narcToString(currentConsumptionLimit)
|
||||
+ " to " + narcToString(newConsumptionLimit));
|
||||
mScribe.setConsumptionLimitLocked(newConsumptionLimit);
|
||||
adjustCreditSupplyLocked(/* allowIncrease */ true);
|
||||
}
|
||||
}
|
||||
|
||||
void postAffordabilityChanged(final int userId, @NonNull final String pkgName,
|
||||
@NonNull Agent.ActionAffordabilityNote affordabilityNote) {
|
||||
if (DEBUG) {
|
||||
@@ -559,6 +559,23 @@ public class InternalResourceService extends SystemService {
|
||||
mHandler.obtainMessage(MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER, args).sendToTarget();
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void adjustCreditSupplyLocked(boolean allowIncrease) {
|
||||
final long newLimit = getConsumptionLimitLocked();
|
||||
final long remainingConsumableNarcs = mScribe.getRemainingConsumableNarcsLocked();
|
||||
if (remainingConsumableNarcs == newLimit) {
|
||||
return;
|
||||
}
|
||||
if (remainingConsumableNarcs > newLimit) {
|
||||
mScribe.adjustRemainingConsumableNarcsLocked(newLimit - remainingConsumableNarcs);
|
||||
} else if (allowIncrease) {
|
||||
final double perc = mCurrentBatteryLevel / 100d;
|
||||
final long shortfall = newLimit - remainingConsumableNarcs;
|
||||
mScribe.adjustRemainingConsumableNarcsLocked((long) (perc * shortfall));
|
||||
}
|
||||
mAgent.onCreditSupplyChanged();
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void processUsageEventLocked(final int userId, @NonNull UsageEvents.Event event) {
|
||||
if (!mIsEnabled) {
|
||||
@@ -650,48 +667,6 @@ public class InternalResourceService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reclaim unused ARCs above apps' minimum balances if there are too many credits currently
|
||||
* in circulation.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private void maybeForceReclaimLocked() {
|
||||
final long maxCirculation = getMaxCirculationLocked();
|
||||
if (maxCirculation == 0) {
|
||||
Slog.wtf(TAG, "Max scaled circulation is 0...");
|
||||
mAgent.reclaimUnusedAssetsLocked(1, HOUR_IN_MILLIS, true);
|
||||
mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
|
||||
scheduleUnusedWealthReclamationLocked();
|
||||
return;
|
||||
}
|
||||
final long curCirculation = mScribe.getNarcsInCirculationLocked();
|
||||
final double circulationPerc = 1.0 * curCirculation / maxCirculation;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Circulation %: " + circulationPerc);
|
||||
}
|
||||
final int numConfigs = mReclamationConfigs.size();
|
||||
if (numConfigs == 0) {
|
||||
return;
|
||||
}
|
||||
// The configs are sorted in descending order of circulationPercentageThreshold, so we can
|
||||
// short-circuit if the current circulation is lower than the lowest threshold.
|
||||
if (circulationPerc
|
||||
< mReclamationConfigs.get(numConfigs - 1).circulationPercentageThreshold) {
|
||||
return;
|
||||
}
|
||||
// TODO: maybe exclude apps we think will be launched in the next few hours
|
||||
for (int i = 0; i < numConfigs; ++i) {
|
||||
final ReclamationConfig config = mReclamationConfigs.get(i);
|
||||
if (circulationPerc >= config.circulationPercentageThreshold) {
|
||||
mAgent.reclaimUnusedAssetsLocked(
|
||||
config.reclamationPercentage, config.minUsedTimeMs, config.scaleMinBalance);
|
||||
mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
|
||||
scheduleUnusedWealthReclamationLocked();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerListeners() {
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED);
|
||||
@@ -731,8 +706,18 @@ public class InternalResourceService extends SystemService {
|
||||
final boolean isFirstSetup = !mScribe.recordExists();
|
||||
if (isFirstSetup) {
|
||||
mAgent.grantBirthrightsLocked();
|
||||
mScribe.setConsumptionLimitLocked(
|
||||
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
|
||||
} else {
|
||||
mScribe.loadFromDiskLocked();
|
||||
if (mScribe.getSatiatedConsumptionLimitLocked()
|
||||
< mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
|
||||
|| mScribe.getSatiatedConsumptionLimitLocked()
|
||||
> mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
|
||||
// Reset the consumption limit since several factors may have changed.
|
||||
mScribe.setConsumptionLimitLocked(
|
||||
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
|
||||
}
|
||||
}
|
||||
scheduleUnusedWealthReclamationLocked();
|
||||
}
|
||||
@@ -787,14 +772,6 @@ public class InternalResourceService extends SystemService {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_MAYBE_FORCE_RECLAIM: {
|
||||
removeMessages(MSG_MAYBE_FORCE_RECLAIM);
|
||||
synchronized (mLock) {
|
||||
maybeForceReclaimLocked();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: {
|
||||
final SomeArgs args = (SomeArgs) msg.obj;
|
||||
final int userId = args.argi1;
|
||||
@@ -936,12 +913,13 @@ public class InternalResourceService extends SystemService {
|
||||
synchronized (mLock) {
|
||||
for (int i = 0; i < projectedActions.size(); ++i) {
|
||||
AnticipatedAction action = projectedActions.get(i);
|
||||
final long cost = mCompleteEconomicPolicy.getCostOfAction(
|
||||
final Cost cost = mCompleteEconomicPolicy.getCostOfAction(
|
||||
action.actionId, userId, pkgName);
|
||||
requiredBalance += cost * action.numInstantaneousCalls
|
||||
+ cost * (action.ongoingDurationMs / 1000);
|
||||
requiredBalance += cost.price * action.numInstantaneousCalls
|
||||
+ cost.price * (action.ongoingDurationMs / 1000);
|
||||
}
|
||||
return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance;
|
||||
return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance
|
||||
&& mScribe.getRemainingConsumableNarcsLocked() >= requiredBalance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,14 +938,17 @@ public class InternalResourceService extends SystemService {
|
||||
synchronized (mLock) {
|
||||
for (int i = 0; i < projectedActions.size(); ++i) {
|
||||
AnticipatedAction action = projectedActions.get(i);
|
||||
final long cost = mCompleteEconomicPolicy.getCostOfAction(
|
||||
final Cost cost = mCompleteEconomicPolicy.getCostOfAction(
|
||||
action.actionId, userId, pkgName);
|
||||
totalCostPerSecond += cost;
|
||||
totalCostPerSecond += cost.price;
|
||||
}
|
||||
if (totalCostPerSecond == 0) {
|
||||
return FOREVER_MS;
|
||||
}
|
||||
return mAgent.getBalanceLocked(userId, pkgName) * 1000 / totalCostPerSecond;
|
||||
final long minBalance = Math.min(
|
||||
mAgent.getBalanceLocked(userId, pkgName),
|
||||
mScribe.getRemainingConsumableNarcsLocked());
|
||||
return minBalance * 1000 / totalCostPerSecond;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1085,10 +1066,20 @@ public class InternalResourceService extends SystemService {
|
||||
|
||||
private void updateEconomicPolicy() {
|
||||
synchronized (mLock) {
|
||||
final long initialLimit =
|
||||
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
|
||||
final long hardLimit = mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit();
|
||||
mCompleteEconomicPolicy.tearDown();
|
||||
mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this);
|
||||
if (mIsEnabled && mBootPhase >= PHASE_SYSTEM_SERVICES_READY) {
|
||||
mCompleteEconomicPolicy.setup();
|
||||
if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
|
||||
|| hardLimit
|
||||
!= mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
|
||||
// Reset the consumption limit since several factors may have changed.
|
||||
mScribe.setConsumptionLimitLocked(
|
||||
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
|
||||
}
|
||||
mAgent.onPricingChangedLocked();
|
||||
}
|
||||
}
|
||||
@@ -1110,18 +1101,23 @@ public class InternalResourceService extends SystemService {
|
||||
pw.print("Current battery level: ");
|
||||
pw.println(mCurrentBatteryLevel);
|
||||
|
||||
final long maxCirculation = getMaxCirculationLocked();
|
||||
pw.print("Max circulation (current/satiated): ");
|
||||
pw.print(narcToString(maxCirculation));
|
||||
final long consumptionLimit = getConsumptionLimitLocked();
|
||||
pw.print("Consumption limit (current/initial-satiated/current-satiated): ");
|
||||
pw.print(narcToString(consumptionLimit));
|
||||
pw.print("/");
|
||||
pw.println(narcToString(mCompleteEconomicPolicy.getMaxSatiatedCirculation()));
|
||||
pw.print(narcToString(mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()));
|
||||
pw.print("/");
|
||||
pw.println(narcToString(mScribe.getSatiatedConsumptionLimitLocked()));
|
||||
|
||||
final long currentCirculation = mScribe.getNarcsInCirculationLocked();
|
||||
pw.print("Current GDP: ");
|
||||
pw.print(narcToString(currentCirculation));
|
||||
final long remainingConsumable = mScribe.getRemainingConsumableNarcsLocked();
|
||||
pw.print("Goods remaining: ");
|
||||
pw.print(narcToString(remainingConsumable));
|
||||
pw.print(" (");
|
||||
pw.print(String.format("%.2f", 100f * currentCirculation / maxCirculation));
|
||||
pw.println("% of current max)");
|
||||
pw.print(String.format("%.2f", 100f * remainingConsumable / consumptionLimit));
|
||||
pw.println("% of current limit)");
|
||||
|
||||
pw.print("Device wealth: ");
|
||||
pw.println(narcToString(mScribe.getNarcsInCirculationForLoggingLocked()));
|
||||
|
||||
pw.println();
|
||||
pw.print("Exempted apps", mExemptedApps);
|
||||
|
||||
@@ -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:");
|
||||
|
||||
@@ -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(")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ public class Scribe {
|
||||
private static final String XML_TAG_TRANSACTION = "transaction";
|
||||
private static final String XML_TAG_USER = "user";
|
||||
|
||||
private static final String XML_ATTR_CTP = "ctp";
|
||||
private static final String XML_ATTR_DELTA = "delta";
|
||||
private static final String XML_ATTR_EVENT_ID = "eventId";
|
||||
private static final String XML_ATTR_TAG = "tag";
|
||||
@@ -83,6 +84,8 @@ public class Scribe {
|
||||
private static final String XML_ATTR_USER_ID = "userId";
|
||||
private static final String XML_ATTR_VERSION = "version";
|
||||
private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime";
|
||||
private static final String XML_ATTR_REMAINING_CONSUMABLE_NARCS = "remainingConsumableNarcs";
|
||||
private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
|
||||
|
||||
/** Version of the file schema. */
|
||||
private static final int STATE_FILE_VERSION = 0;
|
||||
@@ -95,7 +98,9 @@ public class Scribe {
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
private long mLastReclamationTime;
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
private long mNarcsInCirculation;
|
||||
private long mSatiatedConsumptionLimit;
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
private long mRemainingConsumableNarcs;
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>();
|
||||
|
||||
@@ -117,10 +122,10 @@ public class Scribe {
|
||||
}
|
||||
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
void adjustNarcsInCirculationLocked(long delta) {
|
||||
void adjustRemainingConsumableNarcsLocked(long delta) {
|
||||
if (delta != 0) {
|
||||
// No point doing any work if the change is 0.
|
||||
mNarcsInCirculation += delta;
|
||||
mRemainingConsumableNarcs += delta;
|
||||
postWrite();
|
||||
}
|
||||
}
|
||||
@@ -131,6 +136,11 @@ public class Scribe {
|
||||
postWrite();
|
||||
}
|
||||
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
long getSatiatedConsumptionLimitLocked() {
|
||||
return mSatiatedConsumptionLimit;
|
||||
}
|
||||
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
long getLastReclamationTimeLocked() {
|
||||
return mLastReclamationTime;
|
||||
@@ -153,19 +163,37 @@ public class Scribe {
|
||||
return mLedgers;
|
||||
}
|
||||
|
||||
/** Returns the total amount of narcs currently allocated to apps. */
|
||||
/**
|
||||
* Returns the sum of credits granted to all apps on the system. This is expensive so don't
|
||||
* call it for normal operation.
|
||||
*/
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
long getNarcsInCirculationLocked() {
|
||||
return mNarcsInCirculation;
|
||||
long getNarcsInCirculationForLoggingLocked() {
|
||||
long sum = 0;
|
||||
for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) {
|
||||
for (int pIdx = mLedgers.numElementsForKeyAt(uIdx) - 1; pIdx >= 0; --pIdx) {
|
||||
sum += mLedgers.valueAt(uIdx, pIdx).getCurrentBalance();
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/** Returns the total amount of narcs that remain to be consumed. */
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
long getRemainingConsumableNarcsLocked() {
|
||||
return mRemainingConsumableNarcs;
|
||||
}
|
||||
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
void loadFromDiskLocked() {
|
||||
mLedgers.clear();
|
||||
mNarcsInCirculation = 0;
|
||||
if (!recordExists()) {
|
||||
mSatiatedConsumptionLimit = mIrs.getInitialSatiatedConsumptionLimitLocked();
|
||||
mRemainingConsumableNarcs = mIrs.getConsumptionLimitLocked();
|
||||
return;
|
||||
}
|
||||
mSatiatedConsumptionLimit = 0;
|
||||
mRemainingConsumableNarcs = 0;
|
||||
|
||||
final SparseArray<ArraySet<String>> installedPackagesPerUser = new SparseArray<>();
|
||||
final List<PackageInfo> installedPackages = mIrs.getInstalledPackages();
|
||||
@@ -222,6 +250,13 @@ public class Scribe {
|
||||
case XML_TAG_HIGH_LEVEL_STATE:
|
||||
mLastReclamationTime =
|
||||
parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME);
|
||||
mSatiatedConsumptionLimit =
|
||||
parser.getAttributeLong(null, XML_ATTR_CONSUMPTION_LIMIT,
|
||||
mIrs.getInitialSatiatedConsumptionLimitLocked());
|
||||
final long consumptionLimit = mIrs.getConsumptionLimitLocked();
|
||||
mRemainingConsumableNarcs = Math.min(consumptionLimit,
|
||||
parser.getAttributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_NARCS,
|
||||
consumptionLimit));
|
||||
break;
|
||||
case XML_TAG_USER:
|
||||
earliestEndTime = Math.min(earliestEndTime,
|
||||
@@ -248,6 +283,18 @@ public class Scribe {
|
||||
return mStateFile.exists();
|
||||
}
|
||||
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
void setConsumptionLimitLocked(long limit) {
|
||||
if (mRemainingConsumableNarcs > limit) {
|
||||
mRemainingConsumableNarcs = limit;
|
||||
} else if (limit > mSatiatedConsumptionLimit) {
|
||||
final long diff = mSatiatedConsumptionLimit - mRemainingConsumableNarcs;
|
||||
mRemainingConsumableNarcs = (limit - diff);
|
||||
}
|
||||
mSatiatedConsumptionLimit = limit;
|
||||
postWrite();
|
||||
}
|
||||
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
void setLastReclamationTimeLocked(long time) {
|
||||
mLastReclamationTime = time;
|
||||
@@ -259,7 +306,8 @@ public class Scribe {
|
||||
TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
|
||||
TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
|
||||
mLedgers.clear();
|
||||
mNarcsInCirculation = 0;
|
||||
mRemainingConsumableNarcs = 0;
|
||||
mSatiatedConsumptionLimit = 0;
|
||||
mLastReclamationTime = 0;
|
||||
}
|
||||
|
||||
@@ -339,13 +387,14 @@ public class Scribe {
|
||||
final long endTime = parser.getAttributeLong(null, XML_ATTR_END_TIME);
|
||||
final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
|
||||
final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
|
||||
final long ctp = parser.getAttributeLong(null, XML_ATTR_CTP);
|
||||
if (endTime <= endTimeCutoff) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Skipping event because it's too old.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta));
|
||||
transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta, ctp));
|
||||
}
|
||||
|
||||
if (!isInstalled) {
|
||||
@@ -395,7 +444,6 @@ public class Scribe {
|
||||
final Ledger ledger = ledgerData.second;
|
||||
if (ledger != null) {
|
||||
mLedgers.add(curUser, ledgerData.first, ledger);
|
||||
mNarcsInCirculation += Math.max(0, ledger.getCurrentBalance());
|
||||
final Ledger.Transaction transaction = ledger.getEarliestTransaction();
|
||||
if (transaction != null) {
|
||||
earliestEndTime = Math.min(earliestEndTime, transaction.endTimeMs);
|
||||
@@ -442,6 +490,9 @@ public class Scribe {
|
||||
|
||||
out.startTag(null, XML_TAG_HIGH_LEVEL_STATE);
|
||||
out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime);
|
||||
out.attributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mSatiatedConsumptionLimit);
|
||||
out.attributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_NARCS,
|
||||
mRemainingConsumableNarcs);
|
||||
out.endTag(null, XML_TAG_HIGH_LEVEL_STATE);
|
||||
|
||||
for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) {
|
||||
@@ -505,6 +556,7 @@ public class Scribe {
|
||||
out.attribute(null, XML_ATTR_TAG, transaction.tag);
|
||||
}
|
||||
out.attributeLong(null, XML_ATTR_DELTA, transaction.delta);
|
||||
out.attributeLong(null, XML_ATTR_CTP, transaction.ctp);
|
||||
out.endTag(null, XML_TAG_TRANSACTION);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -65,7 +65,12 @@ public class AgentTrendCalculatorTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
long getMaxSatiatedCirculation() {
|
||||
long getInitialSatiatedConsumptionLimit() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
long getHardSatiatedConsumptionLimit() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -102,7 +107,7 @@ public class AgentTrendCalculatorTest {
|
||||
TrendCalculator trendCalculator = new TrendCalculator();
|
||||
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, 20);
|
||||
|
||||
trendCalculator.reset(0, null);
|
||||
trendCalculator.reset(0, 0, null);
|
||||
assertEquals("Expected not to cross lower threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
@@ -115,10 +120,10 @@ public class AgentTrendCalculatorTest {
|
||||
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
for (ActionAffordabilityNote note : affordabilityNotes) {
|
||||
note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
|
||||
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
|
||||
}
|
||||
|
||||
trendCalculator.reset(1234, affordabilityNotes);
|
||||
trendCalculator.reset(1234, 1234, affordabilityNotes);
|
||||
assertEquals("Expected not to cross lower threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
@@ -133,32 +138,28 @@ public class AgentTrendCalculatorTest {
|
||||
|
||||
OngoingEvent[] events = new OngoingEvent[]{
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
|
||||
null, 1, 1),
|
||||
1, new EconomicPolicy.Cost(1, 4)),
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
|
||||
null, 2, 3),
|
||||
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "3",
|
||||
null, 3, -3),
|
||||
2, new EconomicPolicy.Cost(3, 6)),
|
||||
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "3", 3,
|
||||
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 0, 3, 3)),
|
||||
};
|
||||
|
||||
trendCalculator.reset(0, null);
|
||||
trendCalculator.reset(0, 100, null);
|
||||
for (OngoingEvent event : events) {
|
||||
trendCalculator.accept(event);
|
||||
}
|
||||
assertEquals("Expected not to cross lower threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals(25_000, trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals("Expected not to cross upper threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
|
||||
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
|
||||
trendCalculator.reset(1234, affordabilityNotes);
|
||||
trendCalculator.reset(1234, 1234, affordabilityNotes);
|
||||
for (OngoingEvent event : events) {
|
||||
trendCalculator.accept(event);
|
||||
}
|
||||
assertEquals("Expected not to cross lower threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals(308_000, trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals("Expected not to cross upper threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
@@ -174,18 +175,19 @@ public class AgentTrendCalculatorTest {
|
||||
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING, 0, 1000))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
for (ActionAffordabilityNote note : affordabilityNotes) {
|
||||
note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
|
||||
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
|
||||
}
|
||||
|
||||
// Balance is already above threshold and events are all positive delta.
|
||||
// There should be no time to report.
|
||||
trendCalculator.reset(1234, affordabilityNotes);
|
||||
trendCalculator.reset(1234, 1234, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
|
||||
null, 1, 1));
|
||||
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
|
||||
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
|
||||
null, 2, 3));
|
||||
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
|
||||
new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION,
|
||||
3, 3, 3)));
|
||||
|
||||
assertEquals("Expected not to cross lower threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
@@ -196,16 +198,16 @@ public class AgentTrendCalculatorTest {
|
||||
|
||||
// Balance is already below threshold and events are all negative delta.
|
||||
// There should be no time to report.
|
||||
trendCalculator.reset(1, affordabilityNotes);
|
||||
trendCalculator.reset(1, 0, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
|
||||
null, 1, -1));
|
||||
1, new EconomicPolicy.Cost(1, 1)));
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
|
||||
null, 2, -3));
|
||||
2, new EconomicPolicy.Cost(3, 3)));
|
||||
|
||||
assertEquals("Expected not to cross lower threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
0,
|
||||
trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals("Expected not to cross upper threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
@@ -213,7 +215,7 @@ public class AgentTrendCalculatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleTrendToThreshold() {
|
||||
public void testSimpleTrendToThreshold_Balance() {
|
||||
TrendCalculator trendCalculator = new TrendCalculator();
|
||||
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
|
||||
|
||||
@@ -222,18 +224,19 @@ public class AgentTrendCalculatorTest {
|
||||
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
for (ActionAffordabilityNote note : affordabilityNotes) {
|
||||
note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
|
||||
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
|
||||
}
|
||||
|
||||
// Balance is below threshold and events are all positive delta.
|
||||
// Should report the correct time to the upper threshold.
|
||||
trendCalculator.reset(0, affordabilityNotes);
|
||||
trendCalculator.reset(0, 1000, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
|
||||
null, 1, 1));
|
||||
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
|
||||
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2",
|
||||
null, 2, 3));
|
||||
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
|
||||
new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION,
|
||||
3, 3, 3)));
|
||||
|
||||
assertEquals("Expected not to cross lower threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
@@ -242,13 +245,13 @@ public class AgentTrendCalculatorTest {
|
||||
|
||||
// Balance is above the threshold and events are all negative delta.
|
||||
// Should report the correct time to the lower threshold.
|
||||
trendCalculator.reset(40, affordabilityNotes);
|
||||
trendCalculator.reset(40, 100, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
|
||||
null, 1, -1));
|
||||
1, new EconomicPolicy.Cost(1, 1)));
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
|
||||
null, 2, -3));
|
||||
2, new EconomicPolicy.Cost(3, 3)));
|
||||
|
||||
assertEquals(5_000, trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals("Expected not to cross upper threshold",
|
||||
@@ -256,6 +259,123 @@ public class AgentTrendCalculatorTest {
|
||||
trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectCorrectThreshold_Balance() {
|
||||
TrendCalculator trendCalculator = new TrendCalculator();
|
||||
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
|
||||
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 15);
|
||||
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START, 10);
|
||||
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 5);
|
||||
|
||||
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
|
||||
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
|
||||
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
|
||||
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
|
||||
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
|
||||
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
for (ActionAffordabilityNote note : affordabilityNotes) {
|
||||
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
|
||||
}
|
||||
|
||||
// Balance is below threshold and events are all positive delta.
|
||||
// Should report the correct time to the correct upper threshold.
|
||||
trendCalculator.reset(0, 10_000, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
|
||||
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
|
||||
|
||||
assertEquals("Expected not to cross lower threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals(5_000, trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
|
||||
// Balance is above the threshold and events are all negative delta.
|
||||
// Should report the correct time to the correct lower threshold.
|
||||
trendCalculator.reset(30, 500, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
|
||||
1, new EconomicPolicy.Cost(1, 1)));
|
||||
|
||||
assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals("Expected not to cross upper threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrendsToBothThresholds_Balance() {
|
||||
TrendCalculator trendCalculator = new TrendCalculator();
|
||||
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
|
||||
mEconomicPolicy.mEventCosts.put(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 50);
|
||||
|
||||
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
|
||||
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
|
||||
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
|
||||
new AnticipatedAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
for (ActionAffordabilityNote note : affordabilityNotes) {
|
||||
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
|
||||
}
|
||||
|
||||
// Balance is between both thresholds and events are mixed positive/negative delta.
|
||||
// Should report the correct time to each threshold.
|
||||
trendCalculator.reset(35, 10_000, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
|
||||
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 3, 3, 3)));
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
|
||||
new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, 2,
|
||||
2, 2)));
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING, "3",
|
||||
3, new EconomicPolicy.Cost(2, 2)));
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "4",
|
||||
4, new EconomicPolicy.Cost(3, 3)));
|
||||
|
||||
assertEquals(3_000, trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals(3_000, trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleTrendToThreshold_ConsumptionLimit() {
|
||||
TrendCalculator trendCalculator = new TrendCalculator();
|
||||
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
|
||||
|
||||
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
|
||||
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
|
||||
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
for (ActionAffordabilityNote note : affordabilityNotes) {
|
||||
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
|
||||
}
|
||||
|
||||
// Events are all negative delta. Consumable credits will run out before app's balance.
|
||||
// Should report the correct time to the lower threshold.
|
||||
trendCalculator.reset(10000, 40, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
|
||||
1, new EconomicPolicy.Cost(1, 10)));
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
|
||||
2, new EconomicPolicy.Cost(3, 40)));
|
||||
|
||||
assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals("Expected not to cross upper threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectCorrectThreshold() {
|
||||
TrendCalculator trendCalculator = new TrendCalculator();
|
||||
@@ -278,68 +398,45 @@ public class AgentTrendCalculatorTest {
|
||||
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
for (ActionAffordabilityNote note : affordabilityNotes) {
|
||||
note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
|
||||
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
|
||||
}
|
||||
|
||||
// Balance is below threshold and events are all positive delta.
|
||||
// Should report the correct time to the correct upper threshold.
|
||||
trendCalculator.reset(0, affordabilityNotes);
|
||||
// Balance is above threshold, consumable credits is 0, and events are all positive delta.
|
||||
// There should be no time to the upper threshold since consumable credits is the limiting
|
||||
// factor.
|
||||
trendCalculator.reset(10_000, 0, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
|
||||
null, 1, 1));
|
||||
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
|
||||
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
|
||||
|
||||
assertEquals("Expected not to cross lower threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals(5_000, trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
assertEquals("Expected not to cross upper threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
|
||||
// Balance is above the threshold and events are all negative delta.
|
||||
// Should report the correct time to the correct lower threshold.
|
||||
trendCalculator.reset(30, affordabilityNotes);
|
||||
// Balance is above threshold, consumable credits is low, and events are all negative delta.
|
||||
trendCalculator.reset(10_000, 4, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
|
||||
null, 1, -1));
|
||||
1, new EconomicPolicy.Cost(1, 10)));
|
||||
|
||||
assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals(4000, trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals("Expected not to cross upper threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
|
||||
// Balance is above threshold, consumable credits is 0, and events are all negative delta.
|
||||
// Time to the lower threshold should be 0 since consumable credits is already 0.
|
||||
trendCalculator.reset(10_000, 0, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
|
||||
1, new EconomicPolicy.Cost(1, 10)));
|
||||
|
||||
assertEquals(0, trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals("Expected not to cross upper threshold",
|
||||
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
|
||||
trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrendsToBothThresholds() {
|
||||
TrendCalculator trendCalculator = new TrendCalculator();
|
||||
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
|
||||
mEconomicPolicy.mEventCosts.put(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 50);
|
||||
|
||||
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
|
||||
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
|
||||
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
|
||||
new AnticipatedAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 1, 0))),
|
||||
mock(AffordabilityChangeListener.class), mEconomicPolicy));
|
||||
for (ActionAffordabilityNote note : affordabilityNotes) {
|
||||
note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
|
||||
}
|
||||
|
||||
// Balance is between both thresholds and events are mixed positive/negative delta.
|
||||
// Should report the correct time to each threshold.
|
||||
trendCalculator.reset(35, affordabilityNotes);
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
|
||||
null, 1, 3));
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2",
|
||||
null, 2, 2));
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING, "3",
|
||||
null, 3, -2));
|
||||
trendCalculator.accept(
|
||||
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "4",
|
||||
null, 4, -3));
|
||||
|
||||
assertEquals(3_000, trendCalculator.getTimeToCrossLowerThresholdMs());
|
||||
assertEquals(3_000, trendCalculator.getTimeToCrossUpperThresholdMs());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user