summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/Analyst.java141
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java99
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/README.md21
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/Scribe.java28
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl5
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java20
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/tare/AnalystTest.java54
8 files changed, 356 insertions, 20 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
index bc6fe7e5a535..f27da4a1a4b2 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
@@ -16,6 +16,8 @@
package com.android.server.tare;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+
import static com.android.server.tare.EconomicPolicy.TYPE_ACTION;
import static com.android.server.tare.EconomicPolicy.TYPE_REGULATION;
import static com.android.server.tare.EconomicPolicy.TYPE_REWARD;
@@ -23,9 +25,16 @@ import static com.android.server.tare.EconomicPolicy.getEventType;
import static com.android.server.tare.TareUtils.cakeToString;
import android.annotation.NonNull;
+import android.os.BatteryManagerInternal;
+import android.os.RemoteException;
import android.util.IndentingPrintWriter;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+
import java.util.ArrayList;
import java.util.List;
@@ -38,6 +47,8 @@ public class Analyst {
|| Log.isLoggable(TAG, Log.DEBUG);
private static final int NUM_PERIODS_TO_RETAIN = 8;
+ @VisibleForTesting
+ static final long MIN_REPORT_DURATION_FOR_RESET = 24 * HOUR_IN_MILLIS;
static final class Report {
/** How much the battery was discharged over the tracked period. */
@@ -73,6 +84,22 @@ public class Analyst {
public long cumulativeNegativeRegulations = 0;
public int numNegativeRegulations = 0;
+ /**
+ * The approximate amount of time the screen has been off while on battery while this
+ * report has been active.
+ */
+ public long screenOffDurationMs = 0;
+ /**
+ * The approximate amount of battery discharge while this report has been active.
+ */
+ public long screenOffDischargeMah = 0;
+ /** The offset used to get the delta when polling the screen off time from BatteryStats. */
+ private long bsScreenOffRealtimeBase = 0;
+ /**
+ * The offset used to get the delta when polling the screen off discharge from BatteryStats.
+ */
+ private long bsScreenOffDischargeMahBase = 0;
+
private void clear() {
cumulativeBatteryDischarge = 0;
currentBatteryLevel = 0;
@@ -86,13 +113,27 @@ public class Analyst {
numPositiveRegulations = 0;
cumulativeNegativeRegulations = 0;
numNegativeRegulations = 0;
+ screenOffDurationMs = 0;
+ screenOffDischargeMah = 0;
+ bsScreenOffRealtimeBase = 0;
+ bsScreenOffDischargeMahBase = 0;
}
}
+ private final IBatteryStats mIBatteryStats;
+
private int mPeriodIndex = 0;
/** How much the battery was discharged over the tracked period. */
private final Report[] mReports = new Report[NUM_PERIODS_TO_RETAIN];
+ Analyst() {
+ this(BatteryStatsService.getService());
+ }
+
+ @VisibleForTesting Analyst(IBatteryStats iBatteryStats) {
+ mIBatteryStats = iBatteryStats;
+ }
+
/** Returns the list of most recent reports, with the oldest report first. */
@NonNull
List<Report> getReports() {
@@ -107,13 +148,35 @@ public class Analyst {
return list;
}
+ long getBatteryScreenOffDischargeMah() {
+ long discharge = 0;
+ for (Report report : mReports) {
+ if (report == null) {
+ continue;
+ }
+ discharge += report.screenOffDischargeMah;
+ }
+ return discharge;
+ }
+
+ long getBatteryScreenOffDurationMs() {
+ long duration = 0;
+ for (Report report : mReports) {
+ if (report == null) {
+ continue;
+ }
+ duration += report.screenOffDurationMs;
+ }
+ return duration;
+ }
+
/**
* Tracks the given reports instead of whatever is currently saved. Reports should be ordered
* oldest to most recent.
*/
void loadReports(@NonNull List<Report> reports) {
final int numReports = reports.size();
- mPeriodIndex = Math.max(0, numReports - 1);
+ mPeriodIndex = Math.max(0, Math.min(NUM_PERIODS_TO_RETAIN, numReports) - 1);
for (int i = 0; i < NUM_PERIODS_TO_RETAIN; ++i) {
if (i < numReports) {
mReports[i] = reports.get(i);
@@ -121,22 +184,38 @@ public class Analyst {
mReports[i] = null;
}
}
+ final Report latest = mReports[mPeriodIndex];
+ if (latest != null) {
+ latest.bsScreenOffRealtimeBase = getLatestBatteryScreenOffRealtimeMs();
+ latest.bsScreenOffDischargeMahBase = getLatestScreenOffDischargeMah();
+ }
}
void noteBatteryLevelChange(int newBatteryLevel) {
- if (newBatteryLevel == 100 && mReports[mPeriodIndex] != null
- && mReports[mPeriodIndex].currentBatteryLevel < newBatteryLevel) {
+ final boolean deviceDischargedEnough = mReports[mPeriodIndex] != null
+ && newBatteryLevel >= 90
+ // Battery level is increasing, so device is charging.
+ && mReports[mPeriodIndex].currentBatteryLevel < newBatteryLevel
+ && mReports[mPeriodIndex].cumulativeBatteryDischarge >= 25;
+ final boolean reportLongEnough = mReports[mPeriodIndex] != null
+ // Battery level is increasing, so device is charging.
+ && mReports[mPeriodIndex].currentBatteryLevel < newBatteryLevel
+ && mReports[mPeriodIndex].screenOffDurationMs >= MIN_REPORT_DURATION_FOR_RESET;
+ final boolean shouldStartNewReport = deviceDischargedEnough || reportLongEnough;
+ if (shouldStartNewReport) {
mPeriodIndex = (mPeriodIndex + 1) % NUM_PERIODS_TO_RETAIN;
if (mReports[mPeriodIndex] != null) {
final Report report = mReports[mPeriodIndex];
report.clear();
report.currentBatteryLevel = newBatteryLevel;
+ report.bsScreenOffRealtimeBase = getLatestBatteryScreenOffRealtimeMs();
+ report.bsScreenOffDischargeMahBase = getLatestScreenOffDischargeMah();
return;
}
}
if (mReports[mPeriodIndex] == null) {
- Report report = new Report();
+ Report report = initializeReport();
mReports[mPeriodIndex] = report;
report.currentBatteryLevel = newBatteryLevel;
return;
@@ -145,13 +224,27 @@ public class Analyst {
final Report report = mReports[mPeriodIndex];
if (newBatteryLevel < report.currentBatteryLevel) {
report.cumulativeBatteryDischarge += (report.currentBatteryLevel - newBatteryLevel);
+
+ final long latestScreenOffRealtime = getLatestBatteryScreenOffRealtimeMs();
+ final long latestScreenOffDischargeMah = getLatestScreenOffDischargeMah();
+ if (report.bsScreenOffRealtimeBase > latestScreenOffRealtime) {
+ // BatteryStats reset
+ report.bsScreenOffRealtimeBase = 0;
+ report.bsScreenOffDischargeMahBase = 0;
+ }
+ report.screenOffDurationMs +=
+ (latestScreenOffRealtime - report.bsScreenOffRealtimeBase);
+ report.screenOffDischargeMah +=
+ (latestScreenOffDischargeMah - report.bsScreenOffDischargeMahBase);
+ report.bsScreenOffRealtimeBase = latestScreenOffRealtime;
+ report.bsScreenOffDischargeMahBase = latestScreenOffDischargeMah;
}
report.currentBatteryLevel = newBatteryLevel;
}
void noteTransaction(@NonNull Ledger.Transaction transaction) {
if (mReports[mPeriodIndex] == null) {
- mReports[mPeriodIndex] = new Report();
+ mReports[mPeriodIndex] = initializeReport();
}
final Report report = mReports[mPeriodIndex];
switch (getEventType(transaction.eventId)) {
@@ -191,6 +284,32 @@ public class Analyst {
mPeriodIndex = 0;
}
+ private long getLatestBatteryScreenOffRealtimeMs() {
+ try {
+ return mIBatteryStats.computeBatteryScreenOffRealtimeMs();
+ } catch (RemoteException e) {
+ // Shouldn't happen
+ return 0;
+ }
+ }
+
+ private long getLatestScreenOffDischargeMah() {
+ try {
+ return mIBatteryStats.getScreenOffDischargeMah();
+ } catch (RemoteException e) {
+ // Shouldn't happen
+ return 0;
+ }
+ }
+
+ @NonNull
+ private Report initializeReport() {
+ final Report report = new Report();
+ report.bsScreenOffRealtimeBase = getLatestBatteryScreenOffRealtimeMs();
+ report.bsScreenOffDischargeMahBase = getLatestScreenOffDischargeMah();
+ return report;
+ }
+
@NonNull
private String padStringWithSpaces(@NonNull String text, int targetLength) {
// Make sure to have at least one space on either side.
@@ -199,6 +318,8 @@ public class Analyst {
}
void dump(IndentingPrintWriter pw) {
+ final BatteryManagerInternal bmi = LocalServices.getService(BatteryManagerInternal.class);
+ final long batteryCapacityMah = bmi.getBatteryFullCharge() / 1000;
pw.println("Reports:");
pw.increaseIndent();
pw.print(" Total Discharge");
@@ -208,6 +329,7 @@ public class Analyst {
pw.print(padStringWithSpaces("Rewards (avg/reward : avg/discharge)", statColsLength));
pw.print(padStringWithSpaces("+Regs (avg/reg : avg/discharge)", statColsLength));
pw.print(padStringWithSpaces("-Regs (avg/reg : avg/discharge)", statColsLength));
+ pw.print(padStringWithSpaces("Bg drain estimate", statColsLength));
pw.println();
for (int r = 0; r < NUM_PERIODS_TO_RETAIN; ++r) {
final int idx = (mPeriodIndex - r + NUM_PERIODS_TO_RETAIN) % NUM_PERIODS_TO_RETAIN;
@@ -283,6 +405,15 @@ public class Analyst {
} else {
pw.print(padStringWithSpaces("N/A", statColsLength));
}
+ if (report.screenOffDurationMs > 0) {
+ pw.print(padStringWithSpaces(String.format("%d mAh (%.2f%%/hr)",
+ report.screenOffDischargeMah,
+ 1.0 * report.screenOffDischargeMah * HOUR_IN_MILLIS
+ / (batteryCapacityMah * report.screenOffDurationMs)),
+ statColsLength));
+ } else {
+ pw.print(padStringWithSpaces("N/A", statColsLength));
+ }
pw.println();
}
pw.decreaseIndent();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 4a26d213a48a..ca0b65c2e6c9 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -112,6 +112,19 @@ public class InternalResourceService extends SystemService {
* limit).
*/
private static final int QUANTITATIVE_EASING_BATTERY_THRESHOLD = 50;
+ /**
+ * The battery level above which we may consider adjusting the desired stock level.
+ */
+ private static final int STOCK_RECALCULATION_BATTERY_THRESHOLD = 80;
+ /**
+ * The amount of time to wait before considering recalculating the desired stock level.
+ */
+ private static final long STOCK_RECALCULATION_DELAY_MS = 16 * HOUR_IN_MILLIS;
+ /**
+ * The minimum amount of time we must have background drain for before considering
+ * recalculating the desired stock level.
+ */
+ private static final long STOCK_RECALCULATION_MIN_DATA_DURATION_MS = 8 * HOUR_IN_MILLIS;
private static final int PACKAGE_QUERY_FLAGS =
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_APEX;
@@ -177,6 +190,9 @@ public class InternalResourceService extends SystemService {
@GuardedBy("mLock")
private int mCurrentBatteryLevel;
+ // TODO(250007395): make configurable per device
+ private final int mTargetBackgroundBatteryLifeHours;
+
private final IAppOpsCallback mApbListener = new IAppOpsCallback.Stub() {
@Override
public void opChanged(int op, int uid, String packageName) {
@@ -316,6 +332,11 @@ public class InternalResourceService extends SystemService {
mConfigObserver = new ConfigObserver(mHandler, context);
+ mTargetBackgroundBatteryLifeHours =
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+ ? 200 // ~ 0.5%/hr
+ : 100; // ~ 1%/hr
+
publishLocalService(EconomyManagerInternal.class, new LocalService());
}
@@ -461,6 +482,9 @@ public class InternalResourceService extends SystemService {
mAnalyst.noteBatteryLevelChange(newBatteryLevel);
final boolean increased = newBatteryLevel > mCurrentBatteryLevel;
if (increased) {
+ if (newBatteryLevel >= STOCK_RECALCULATION_BATTERY_THRESHOLD) {
+ maybeAdjustDesiredStockLevelLocked();
+ }
mAgent.distributeBasicIncomeLocked(newBatteryLevel);
} else if (newBatteryLevel == mCurrentBatteryLevel) {
// The broadcast is also sent when the plug type changes...
@@ -623,6 +647,10 @@ public class InternalResourceService extends SystemService {
*/
@GuardedBy("mLock")
void maybePerformQuantitativeEasingLocked() {
+ if (mConfigObserver.ENABLE_TIP3) {
+ maybeAdjustDesiredStockLevelLocked();
+ return;
+ }
// We don't need to increase the limit if the device runs out of consumable credits
// when the battery is low.
final long remainingConsumableCakes = mScribe.getRemainingConsumableCakesLocked();
@@ -643,6 +671,68 @@ public class InternalResourceService extends SystemService {
}
}
+ /**
+ * Adjust the consumption limit based on historical data and the target battery drain.
+ */
+ @GuardedBy("mLock")
+ void maybeAdjustDesiredStockLevelLocked() {
+ if (!mConfigObserver.ENABLE_TIP3) {
+ return;
+ }
+ // Don't adjust the limit too often or while the battery is low.
+ final long now = getCurrentTimeMillis();
+ if ((now - mScribe.getLastStockRecalculationTimeLocked()) < STOCK_RECALCULATION_DELAY_MS
+ || mCurrentBatteryLevel <= STOCK_RECALCULATION_BATTERY_THRESHOLD) {
+ return;
+ }
+
+ // For now, use screen off battery drain as a proxy for background battery drain.
+ // TODO: get more accurate background battery drain numbers
+ final long totalScreenOffDurationMs = mAnalyst.getBatteryScreenOffDurationMs();
+ if (totalScreenOffDurationMs < STOCK_RECALCULATION_MIN_DATA_DURATION_MS) {
+ return;
+ }
+ final long totalDischargeMah = mAnalyst.getBatteryScreenOffDischargeMah();
+ final long batteryCapacityMah = mBatteryManagerInternal.getBatteryFullCharge() / 1000;
+ final long estimatedLifeHours = batteryCapacityMah * totalScreenOffDurationMs
+ / totalDischargeMah / HOUR_IN_MILLIS;
+ final long percentageOfTarget =
+ 100 * estimatedLifeHours / mTargetBackgroundBatteryLifeHours;
+ if (DEBUG) {
+ Slog.d(TAG, "maybeAdjustDesiredStockLevelLocked:"
+ + " screenOffMs=" + totalScreenOffDurationMs
+ + " dischargeMah=" + totalDischargeMah
+ + " capacityMah=" + batteryCapacityMah
+ + " estimatedLifeHours=" + estimatedLifeHours
+ + " %ofTarget=" + percentageOfTarget);
+ }
+ final long currentConsumptionLimit = mScribe.getSatiatedConsumptionLimitLocked();
+ final long newConsumptionLimit;
+ if (percentageOfTarget > 105) {
+ // The stock is too low. We're doing pretty well. We can increase the stock slightly
+ // to let apps do more work in the background.
+ newConsumptionLimit = Math.min((long) (currentConsumptionLimit * 1.01),
+ mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit());
+ } else if (percentageOfTarget < 100) {
+ // The stock is too high IMO. We're below the target. Decrease the stock to reduce
+ // background work.
+ newConsumptionLimit = Math.max((long) (currentConsumptionLimit * .98),
+ mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ } else {
+ // The stock is just right.
+ return;
+ }
+ // TODO(250007191): calculate and log implied service level
+ if (newConsumptionLimit != currentConsumptionLimit) {
+ Slog.i(TAG, "Adjusting consumption limit from " + cakeToString(currentConsumptionLimit)
+ + " to " + cakeToString(newConsumptionLimit)
+ + " because drain was " + percentageOfTarget + "% of target");
+ mScribe.setConsumptionLimitLocked(newConsumptionLimit);
+ adjustCreditSupplyLocked(/* allowIncrease */ true);
+ mScribe.setLastStockRecalculationTimeLocked(now);
+ }
+ }
+
void postAffordabilityChanged(final int userId, @NonNull final String pkgName,
@NonNull Agent.ActionAffordabilityNote affordabilityNote) {
if (DEBUG) {
@@ -1214,6 +1304,12 @@ public class InternalResourceService extends SystemService {
private class ConfigObserver extends ContentObserver
implements DeviceConfig.OnPropertiesChangedListener {
private static final String KEY_DC_ENABLE_TARE = "enable_tare";
+ private static final String KEY_ENABLE_TIP3 = "enable_tip3";
+
+ private static final boolean DEFAULT_ENABLE_TIP3 = true;
+
+ /** Use a target background battery drain rate to determine consumption limits. */
+ public boolean ENABLE_TIP3 = DEFAULT_ENABLE_TIP3;
private final ContentResolver mContentResolver;
@@ -1264,6 +1360,9 @@ public class InternalResourceService extends SystemService {
case KEY_DC_ENABLE_TARE:
updateEnabledStatus();
break;
+ case KEY_ENABLE_TIP3:
+ ENABLE_TIP3 = properties.getBoolean(name, DEFAULT_ENABLE_TIP3);
+ break;
default:
if (!economicPolicyUpdated
&& (name.startsWith("am") || name.startsWith("js"))) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/README.md b/apex/jobscheduler/service/java/com/android/server/tare/README.md
index e338ed1c6987..8d25ecce8431 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/README.md
+++ b/apex/jobscheduler/service/java/com/android/server/tare/README.md
@@ -80,9 +80,9 @@ consumption limit, then the available resources are decreased to match the scale
Regulations are unique events invoked by the ~~government~~ system in order to get the whole economy
moving smoothly.
-# Previous Implementations
+# Significant Changes
-## V0
+## Tare Improvement Proposal #1 (TIP1)
The initial implementation/proposal combined the supply of resources with the allocation in a single
mechanism. It defined the maximum number of resources (ARCs) available at a time, and then divided
@@ -98,10 +98,25 @@ allocated as part of the rewards. There were several problems with that mechanis
These problems effectively meant that misallocation was a big problem, demand wasn't well reflected,
and some apps may not have been able to perform work even though they otherwise should have been.
-Tare Improvement Proposal #1 (TIP1) separated allocation (to apps) from supply (by the system) and
+TIP1 separated allocation (to apps) from supply (by the system) and
allowed apps to accrue credits as appropriate while still limiting the total number of credits
consumed.
+## Tare Improvement Proposal #3 (TIP3)
+
+TIP1 introduced Consumption Limits, which control the total number of ARCs that can be used to
+perform actions, based on the production costs of each action. The Consumption Limits were initially
+determined manually, but could increase in the system if apps used the full consumption limit before
+the device had drained to 50% battery. As with any system that relies on manually deciding
+parameters, the only mechanism to identify an optimal value is through experimentation, which can
+take many iterations and requires extended periods of time to observe results. The limits are also
+chosen and adjusted without consideration of the resulting battery drain of each possible value. In
+addition, having the system potentially increase the limit without considering a decrease introduced
+potential for battery life to get worse as time goes on and the user installed more background-work
+demanding apps.
+
+TIP3 uses a target background battery drain rate to dynamically adjust the Consumption Limit.
+
# Potential Future Changes
These are some ideas for further changes. There's no guarantee that they'll be implemented.
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index bd4fd72b78ce..27d00b76f452 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -84,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_LAST_STOCK_RECALCULATION_TIME =
+ "lastStockRecalculationTime";
private static final String XML_ATTR_REMAINING_CONSUMABLE_CAKES = "remainingConsumableCakes";
private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
private static final String XML_ATTR_PR_DISCHARGE = "discharge";
@@ -98,6 +100,8 @@ public class Scribe {
private static final String XML_ATTR_PR_NUM_POS_REGULATIONS = "numPosRegulations";
private static final String XML_ATTR_PR_NEG_REGULATIONS = "negRegulations";
private static final String XML_ATTR_PR_NUM_NEG_REGULATIONS = "numNegRegulations";
+ private static final String XML_ATTR_PR_SCREEN_OFF_DURATION_MS = "screenOffDurationMs";
+ private static final String XML_ATTR_PR_SCREEN_OFF_DISCHARGE_MAH = "screenOffDischargeMah";
/** Version of the file schema. */
private static final int STATE_FILE_VERSION = 0;
@@ -111,6 +115,8 @@ public class Scribe {
@GuardedBy("mIrs.getLock()")
private long mLastReclamationTime;
@GuardedBy("mIrs.getLock()")
+ private long mLastStockRecalculationTime;
+ @GuardedBy("mIrs.getLock()")
private long mSatiatedConsumptionLimit;
@GuardedBy("mIrs.getLock()")
private long mRemainingConsumableCakes;
@@ -173,6 +179,11 @@ public class Scribe {
}
@GuardedBy("mIrs.getLock()")
+ long getLastStockRecalculationTimeLocked() {
+ return mLastStockRecalculationTime;
+ }
+
+ @GuardedBy("mIrs.getLock()")
@NonNull
Ledger getLedgerLocked(final int userId, @NonNull final String pkgName) {
Ledger ledger = mLedgers.get(userId, pkgName);
@@ -281,6 +292,8 @@ public class Scribe {
case XML_TAG_HIGH_LEVEL_STATE:
mLastReclamationTime =
parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME);
+ mLastStockRecalculationTime = parser.getAttributeLong(null,
+ XML_ATTR_LAST_STOCK_RECALCULATION_TIME, 0);
mSatiatedConsumptionLimit =
parser.getAttributeLong(null, XML_ATTR_CONSUMPTION_LIMIT,
mIrs.getInitialSatiatedConsumptionLimitLocked());
@@ -337,6 +350,12 @@ public class Scribe {
}
@GuardedBy("mIrs.getLock()")
+ void setLastStockRecalculationTimeLocked(long time) {
+ mLastStockRecalculationTime = time;
+ postWrite();
+ }
+
+ @GuardedBy("mIrs.getLock()")
void tearDownLocked() {
TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
@@ -504,7 +523,6 @@ public class Scribe {
return earliestEndTime;
}
-
/**
* @param parser Xml parser at the beginning of a {@link #XML_TAG_PERIOD_REPORT} tag. The next
* "parser.next()" call will take the parser into the body of the report tag.
@@ -531,6 +549,10 @@ public class Scribe {
parser.getAttributeLong(null, XML_ATTR_PR_NEG_REGULATIONS);
report.numNegativeRegulations =
parser.getAttributeInt(null, XML_ATTR_PR_NUM_NEG_REGULATIONS);
+ report.screenOffDurationMs =
+ parser.getAttributeLong(null, XML_ATTR_PR_SCREEN_OFF_DURATION_MS, 0);
+ report.screenOffDischargeMah =
+ parser.getAttributeLong(null, XML_ATTR_PR_SCREEN_OFF_DISCHARGE_MAH, 0);
return report;
}
@@ -606,6 +628,8 @@ 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_LAST_STOCK_RECALCULATION_TIME, mLastStockRecalculationTime);
out.attributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mSatiatedConsumptionLimit);
out.attributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_CAKES,
mRemainingConsumableCakes);
@@ -718,6 +742,8 @@ public class Scribe {
out.attributeInt(null, XML_ATTR_PR_NUM_POS_REGULATIONS, report.numPositiveRegulations);
out.attributeLong(null, XML_ATTR_PR_NEG_REGULATIONS, report.cumulativeNegativeRegulations);
out.attributeInt(null, XML_ATTR_PR_NUM_NEG_REGULATIONS, report.numNegativeRegulations);
+ out.attributeLong(null, XML_ATTR_PR_SCREEN_OFF_DURATION_MS, report.screenOffDurationMs);
+ out.attributeLong(null, XML_ATTR_PR_SCREEN_OFF_DISCHARGE_MAH, report.screenOffDischargeMah);
out.endTag(null, XML_TAG_PERIOD_REPORT);
}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 3732ea5abaa5..787b594af6bb 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -84,6 +84,11 @@ interface IBatteryStats {
@RequiresNoPermission
long computeChargeTimeRemaining();
+ @EnforcePermission("BATTERY_STATS")
+ long computeBatteryScreenOffRealtimeMs();
+ @EnforcePermission("BATTERY_STATS")
+ long getScreenOffDischargeMah();
+
@EnforcePermission("UPDATE_DEVICE_STATS")
void noteEvent(int code, String name, int uid);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index a41a311bd643..d9d29d650f03 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -831,6 +831,26 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
@Override
+ @EnforcePermission(BATTERY_STATS)
+ public long computeBatteryScreenOffRealtimeMs() {
+ synchronized (mStats) {
+ final long curTimeUs = SystemClock.elapsedRealtimeNanos() / 1000;
+ long timeUs = mStats.computeBatteryScreenOffRealtime(curTimeUs,
+ BatteryStats.STATS_SINCE_CHARGED);
+ return timeUs / 1000;
+ }
+ }
+
+ @Override
+ @EnforcePermission(BATTERY_STATS)
+ public long getScreenOffDischargeMah() {
+ synchronized (mStats) {
+ long dischargeUah = mStats.getUahDischargeScreenOff(BatteryStats.STATS_SINCE_CHARGED);
+ return dischargeUah / 1000;
+ }
+ }
+
+ @Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteEvent(final int code, final String name, final int uid) {
if (name == null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index 4ce268f0dc39..ddfa05cf5a2e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -140,6 +140,8 @@ public class ScribeTest {
report1.numPositiveRegulations = 10;
report1.cumulativeNegativeRegulations = 11;
report1.numNegativeRegulations = 12;
+ report1.screenOffDurationMs = 13;
+ report1.screenOffDischargeMah = 14;
mReports.add(report1);
mScribeUnderTest.writeImmediatelyForTesting();
mScribeUnderTest.loadFromDiskLocked();
@@ -160,6 +162,8 @@ public class ScribeTest {
report2.numPositiveRegulations = 100;
report2.cumulativeNegativeRegulations = 110;
report2.numNegativeRegulations = 120;
+ report2.screenOffDurationMs = 130;
+ report2.screenOffDischargeMah = 140;
mReports.add(report2);
mScribeUnderTest.writeImmediatelyForTesting();
mScribeUnderTest.loadFromDiskLocked();
@@ -385,6 +389,10 @@ public class ScribeTest {
eReport.cumulativeNegativeRegulations, aReport.cumulativeNegativeRegulations);
assertEquals("Reports #" + i + " numNegativeRegulations are not equal",
eReport.numNegativeRegulations, aReport.numNegativeRegulations);
+ assertEquals("Reports #" + i + " screenOffDurationMs are not equal",
+ eReport.screenOffDurationMs, aReport.screenOffDurationMs);
+ assertEquals("Reports #" + i + " screenOffDischargeMah are not equal",
+ eReport.screenOffDischargeMah, aReport.screenOffDischargeMah);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/tare/AnalystTest.java b/services/tests/servicestests/src/com/android/server/tare/AnalystTest.java
index 2b527a261ae1..a603b93ab307 100644
--- a/services/tests/servicestests/src/com/android/server/tare/AnalystTest.java
+++ b/services/tests/servicestests/src/com/android/server/tare/AnalystTest.java
@@ -19,10 +19,14 @@ package com.android.server.tare;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.app.IBatteryStats;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,20 +49,20 @@ public class AnalystTest {
final Analyst analyst = new Analyst();
Analyst.Report expected = new Analyst.Report();
- expected.currentBatteryLevel = 55;
- analyst.noteBatteryLevelChange(55);
+ expected.currentBatteryLevel = 75;
+ analyst.noteBatteryLevelChange(75);
assertEquals(1, analyst.getReports().size());
assertReportsEqual(expected, analyst.getReports().get(0));
// Discharging
analyst.noteBatteryLevelChange(54);
expected.currentBatteryLevel = 54;
- expected.cumulativeBatteryDischarge = 1;
+ expected.cumulativeBatteryDischarge = 21;
assertEquals(1, analyst.getReports().size());
assertReportsEqual(expected, analyst.getReports().get(0));
analyst.noteBatteryLevelChange(50);
expected.currentBatteryLevel = 50;
- expected.cumulativeBatteryDischarge = 5;
+ expected.cumulativeBatteryDischarge = 25;
assertEquals(1, analyst.getReports().size());
assertReportsEqual(expected, analyst.getReports().get(0));
@@ -87,27 +91,53 @@ public class AnalystTest {
}
@Test
- public void testTransaction_PeriodChange() {
- final Analyst analyst = new Analyst();
+ public void testTransaction_PeriodChange() throws Exception {
+ IBatteryStats iBatteryStats = mock(IBatteryStats.class);
+ final Analyst analyst = new Analyst(iBatteryStats);
+ // Reset from enough discharge.
Analyst.Report expected = new Analyst.Report();
- expected.currentBatteryLevel = 55;
- analyst.noteBatteryLevelChange(55);
+ expected.currentBatteryLevel = 75;
+ analyst.noteBatteryLevelChange(75);
runTestTransactions(analyst, expected, 1);
expected.currentBatteryLevel = 49;
- expected.cumulativeBatteryDischarge = 6;
+ expected.cumulativeBatteryDischarge = 26;
analyst.noteBatteryLevelChange(49);
runTestTransactions(analyst, expected, 1);
expected = new Analyst.Report();
- expected.currentBatteryLevel = 100;
- analyst.noteBatteryLevelChange(100);
+ expected.currentBatteryLevel = 90;
+ analyst.noteBatteryLevelChange(90);
expected.cumulativeBatteryDischarge = 0;
runTestTransactions(analyst, expected, 2);
+
+ // Reset from report being long enough.
+ doReturn(Analyst.MIN_REPORT_DURATION_FOR_RESET)
+ .when(iBatteryStats).computeBatteryScreenOffRealtimeMs();
+ expected.currentBatteryLevel = 85;
+ analyst.noteBatteryLevelChange(85);
+ expected.cumulativeBatteryDischarge = 5;
+ expected.screenOffDurationMs = Analyst.MIN_REPORT_DURATION_FOR_RESET;
+
+ runTestTransactions(analyst, expected, 2);
+
+ expected.currentBatteryLevel = 79;
+ analyst.noteBatteryLevelChange(79);
+ expected.cumulativeBatteryDischarge = 11;
+
+ runTestTransactions(analyst, expected, 2);
+
+ expected = new Analyst.Report();
+ expected.currentBatteryLevel = 80;
+ analyst.noteBatteryLevelChange(80);
+ expected.cumulativeBatteryDischarge = 0;
+ expected.screenOffDurationMs = 0;
+
+ runTestTransactions(analyst, expected, 3);
}
private void runTestTransactions(Analyst analyst, Analyst.Report lastExpectedReport,
@@ -223,6 +253,8 @@ public class AnalystTest {
assertEquals(expected.numPositiveRegulations, actual.numPositiveRegulations);
assertEquals(expected.cumulativeNegativeRegulations, actual.cumulativeNegativeRegulations);
assertEquals(expected.numNegativeRegulations, actual.numNegativeRegulations);
+ assertEquals(expected.screenOffDurationMs, actual.screenOffDurationMs);
+ assertEquals(expected.screenOffDischargeMah, actual.screenOffDischargeMah);
}
private void assertReportListsEqual(List<Analyst.Report> expected,