diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java index 50804422e92f5..97f24ccecaee2 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -72,12 +72,16 @@ public final class BatteryUsageStatsQuery implements Parcelable { @NonNull private final int[] mUserIds; private final long mMaxStatsAgeMs; + private long mFromTimestamp; + private long mToTimestamp; private BatteryUsageStatsQuery(@NonNull Builder builder) { mFlags = builder.mFlags; mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray() : new int[]{UserHandle.USER_ALL}; mMaxStatsAgeMs = builder.mMaxStatsAgeMs; + mFromTimestamp = builder.mFromTimestamp; + mToTimestamp = builder.mToTimestamp; } @BatteryUsageStatsFlags @@ -112,11 +116,30 @@ public final class BatteryUsageStatsQuery implements Parcelable { return mMaxStatsAgeMs; } + /** + * Returns the exclusive lower bound of the stored snapshot timestamps that should be included + * in the aggregation. Ignored if {@link #getToTimestamp()} is zero. + */ + public long getFromTimestamp() { + return mFromTimestamp; + } + + /** + * Returns the inclusive upper bound of the stored snapshot timestamps that should + * be included in the aggregation. The default is to include only the current stats + * accumulated since the latest battery reset. + */ + public long getToTimestamp() { + return mToTimestamp; + } + private BatteryUsageStatsQuery(Parcel in) { mFlags = in.readInt(); mUserIds = new int[in.readInt()]; in.readIntArray(mUserIds); mMaxStatsAgeMs = in.readLong(); + mFromTimestamp = in.readLong(); + mToTimestamp = in.readLong(); } @Override @@ -125,6 +148,8 @@ public final class BatteryUsageStatsQuery implements Parcelable { dest.writeInt(mUserIds.length); dest.writeIntArray(mUserIds); dest.writeLong(mMaxStatsAgeMs); + dest.writeLong(mFromTimestamp); + dest.writeLong(mToTimestamp); } @Override @@ -153,6 +178,8 @@ public final class BatteryUsageStatsQuery implements Parcelable { private int mFlags; private IntArray mUserIds; private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS; + private long mFromTimestamp; + private long mToTimestamp; /** * Builds a read-only BatteryUsageStatsQuery object. @@ -203,6 +230,17 @@ public final class BatteryUsageStatsQuery implements Parcelable { return this; } + /** + * Requests to aggregate stored snapshots between the two supplied timestamps + * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis() + * @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis() + */ + public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) { + mFromTimestamp = fromTimestamp; + mToTimestamp = toTimestamp; + return this; + } + /** * Set the client's tolerance for stale battery stats. The data may be up to * this many milliseconds out-of-date. diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index a7c2bbd7b1ca9..945a6ab118565 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -10749,6 +10749,10 @@ public class BatteryStatsImpl extends BatteryStats { } } + PowerProfile getPowerProfile() { + return mPowerProfile; + } + /** * Starts tracking CPU time-in-state for threads of the system server process, * keeping a separate account of threads receiving incoming binder calls. diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index 3aaccdd718442..8943db6c8a1e6 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -38,14 +38,24 @@ import java.util.Map; public class BatteryUsageStatsProvider { private final Context mContext; private final BatteryStats mStats; + private final BatteryUsageStatsStore mBatteryUsageStatsStore; private final PowerProfile mPowerProfile; private final Object mLock = new Object(); private List mPowerCalculators; public BatteryUsageStatsProvider(Context context, BatteryStats stats) { + this(context, stats, null); + } + + @VisibleForTesting + public BatteryUsageStatsProvider(Context context, BatteryStats stats, + BatteryUsageStatsStore batteryUsageStatsStore) { mContext = context; mStats = stats; - mPowerProfile = new PowerProfile(mContext); + mBatteryUsageStatsStore = batteryUsageStatsStore; + mPowerProfile = stats instanceof BatteryStatsImpl + ? ((BatteryStatsImpl) stats).getPowerProfile() + : new PowerProfile(context); } private List getPowerCalculators() { @@ -126,6 +136,15 @@ public class BatteryUsageStatsProvider { private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query, long currentTimeMs) { + if (query.getToTimestamp() == 0) { + return getCurrentBatteryUsageStats(query, currentTimeMs); + } else { + return getAggregatedBatteryUsageStats(query); + } + } + + private BatteryUsageStats getCurrentBatteryUsageStats(BatteryUsageStatsQuery query, + long currentTimeMs) { final long realtimeUs = elapsedRealtime() * 1000; final long uptimeUs = uptimeMillis() * 1000; @@ -209,6 +228,25 @@ public class BatteryUsageStatsProvider { BatteryStats.STATS_SINCE_CHARGED) / 1000; } + private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryUsageStatsQuery query) { + final boolean includePowerModels = (query.getFlags() + & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; + + final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( + mStats.getCustomEnergyConsumerNames(), includePowerModels); + final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); + for (long timestamp : timestamps) { + if (timestamp > query.getFromTimestamp() && timestamp <= query.getToTimestamp()) { + final BatteryUsageStats snapshot = + mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp); + if (snapshot != null) { + builder.add(snapshot); + } + } + } + return builder.build(); + } + private long elapsedRealtime() { if (mStats instanceof BatteryStatsImpl) { return ((BatteryStatsImpl) mStats).mClocks.elapsedRealtime(); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java index d83645d6e0a5c..cbd67c8324f4c 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java @@ -20,10 +20,14 @@ import static com.google.common.truth.Truth.assertThat; import android.app.ActivityManager; import android.content.Context; +import android.os.BatteryConsumer; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.Parcel; import android.os.Process; import android.os.UidBatteryConsumer; @@ -36,6 +40,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; import java.util.List; @SmallTest @@ -45,7 +50,8 @@ public class BatteryUsageStatsProviderTest { private static final long MINUTE_IN_MS = 60 * 1000; @Rule - public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(12345); + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(12345) + .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0); @Test public void test_getBatteryUsageStats() { @@ -187,4 +193,84 @@ public class BatteryUsageStatsProviderTest { mStatsRule.setTime(11500, 0); assertThat(provider.shouldUpdateStats(queries, 10000)).isTrue(); } + + @Test + public void testAggregateBatteryStats() { + Context context = InstrumentationRegistry.getContext(); + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + mStatsRule.setCurrentTime(5 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + BatteryUsageStatsStore batteryUsageStatsStore = new BatteryUsageStatsStore(context, + batteryStats, new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"), + new TestHandler(), Integer.MAX_VALUE); + + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, + batteryStats, batteryUsageStatsStore); + + batteryStats.noteFlashlightOnLocked(APP_UID, + 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(25 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + batteryStats.noteFlashlightOnLocked(APP_UID, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(55 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + // This section should be ignored because the timestamp is out or range + batteryStats.noteFlashlightOnLocked(APP_UID, + 60 * MINUTE_IN_MS, 60 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(75 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + // This section should be ignored because it represents the current stats session + batteryStats.noteFlashlightOnLocked(APP_UID, + 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(95 * MINUTE_IN_MS); + + // Include the first and the second snapshot, but not the third or current + BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() + .aggregateSnapshots(20 * MINUTE_IN_MS, 60 * MINUTE_IN_MS) + .build(); + final BatteryUsageStats stats = provider.getBatteryUsageStats(query); + + assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS); + assertThat(stats.getStatsEndTimestamp()).isEqualTo(55 * MINUTE_IN_MS); + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isWithin(0.0001) + .of(180.0); // 360 mA * 0.5 hours + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isEqualTo((10 + 20) * MINUTE_IN_MS); + final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream() + .filter(uid -> uid.getUid() == APP_UID).findFirst().get(); + assertThat(uidBatteryConsumer + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isWithin(0.1) + .of(180.0); + } + + private static class TestHandler extends Handler { + TestHandler() { + super(Looper.getMainLooper()); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + msg.getCallback().run(); + return true; + } + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java index 7570a8e32de44..9b9c8e3fa5c1d 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java @@ -18,6 +18,8 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + import android.content.Context; import android.os.BatteryManager; import android.os.BatteryUsageStats; @@ -56,6 +58,7 @@ public class BatteryUsageStatsStoreTest { mMockClocks.currentTime = 123; mBatteryStats = new MockBatteryStatsImpl(mMockClocks); mBatteryStats.setNoAutoReset(true); + mBatteryStats.setPowerProfile(mock(PowerProfile.class)); Context context = InstrumentationRegistry.getContext();