Loading src/com/android/settings/fuelgauge/BatteryInfo.java +33 −22 Original line number Diff line number Diff line Loading @@ -30,10 +30,10 @@ import android.text.format.Formatter; import android.util.SparseIntArray; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.R; import com.android.settings.Utils; import com.android.settings.graph.UsageView; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.R; public class BatteryInfo { Loading @@ -54,18 +54,20 @@ public class BatteryInfo { } public void bindHistory(final UsageView view, BatteryDataParser... parsers) { final Context context = view.getContext(); BatteryDataParser parser = new BatteryDataParser() { SparseIntArray points = new SparseIntArray(); long startTime; int lastTime = -1; byte lastLevel; int maxTime; @Override public void onParsingStarted(long startTime, long endTime) { this.maxTime = (int) (endTime - startTime); timePeriod = maxTime - (remainingTimeUs / 1000); this.startTime = startTime; timePeriod = endTime - startTime; view.clearPaths(); view.configureGraph(maxTime, 100); // Initially configure the graph for history only. view.configureGraph((int) timePeriod, 100); } @Override Loading @@ -87,10 +89,27 @@ public class BatteryInfo { public void onParsingDone() { onDataGap(); // Add linear projection if (lastTime >= 0 && remainingTimeUs != 0) { // Add projection if we have an estimate. if (remainingTimeUs != 0) { PowerUsageFeatureProvider provider = FeatureFactory.getFactory(context) .getPowerUsageFeatureProvider(context); if (!mCharging && provider.isEnhancedBatteryPredictionEnabled(context)) { points = provider.getEnhancedBatteryPredictionCurve(context, startTime); } else { // Linear extrapolation. if (lastTime >= 0) { points.put(lastTime, lastLevel); points.put(maxTime, mCharging ? 100 : 0); points.put((int) (timePeriod + BatteryUtils.convertUsToMs(remainingTimeUs)), mCharging ? 100 : 0); } } } // If we have a projection, reconfigure the graph to show it. if (points != null && points.size() > 0) { int maxTime = points.keyAt(points.size() - 1); view.configureGraph(maxTime, 100); view.addProjectedPath(points); } } Loading @@ -100,8 +119,7 @@ public class BatteryInfo { parserList[i] = parsers[i]; } parserList[parsers.length] = parser; parse(mStats, remainingTimeUs, parserList); final Context context = view.getContext(); parse(mStats, parserList); String timeString = context.getString(R.string.charge_length_format, Formatter.formatShortElapsedTime(context, timePeriod)); String remaining = ""; Loading Loading @@ -249,14 +267,11 @@ public class BatteryInfo { void onParsingDone(); } private static void parse(BatteryStats stats, long remainingTimeUs, BatteryDataParser... parsers) { private static void parse(BatteryStats stats, BatteryDataParser... parsers) { long startWalltime = 0; long endDateWalltime = 0; long endWalltime = 0; long historyStart = 0; long historyEnd = 0; byte lastLevel = -1; long curWalltime = startWalltime; long lastWallTime = 0; long lastRealtime = 0; Loading Loading @@ -292,17 +307,13 @@ public class BatteryInfo { } } if (rec.isDeltaData()) { if (rec.batteryLevel != lastLevel || pos == 1) { lastLevel = rec.batteryLevel; } lastInteresting = pos; historyEnd = rec.time; } } } stats.finishIteratingHistoryLocked(); endDateWalltime = lastWallTime + historyEnd - lastRealtime; endWalltime = endDateWalltime + (remainingTimeUs / 1000); endWalltime = lastWallTime + historyEnd - lastRealtime; int i = 0; final int N = lastInteresting; Loading @@ -310,7 +321,7 @@ public class BatteryInfo { for (int j = 0; j < parsers.length; j++) { parsers[j].onParsingStarted(startWalltime, endWalltime); } if (endDateWalltime > startWalltime && stats.startIteratingHistoryLocked()) { if (endWalltime > startWalltime && stats.startIteratingHistoryLocked()) { final HistoryItem rec = new HistoryItem(); while (stats.getNextHistoryLocked(rec) && i < N) { if (rec.isDeltaData()) { Loading src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java +8 −1 Original line number Diff line number Diff line Loading @@ -20,8 +20,9 @@ import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.util.SparseIntArray; import com.android.internal.os.BatterySipper; import com.android.settings.fuelgauge.anomaly.Anomaly; /** * Feature Provider used in power usage Loading Loading @@ -67,6 +68,12 @@ public interface PowerUsageFeatureProvider { */ long getEnhancedBatteryPrediction(Context context); /** * Returns an improved projection curve for future battery level. * @param zeroTime timestamps (array keys) are shifted by this amount */ SparseIntArray getEnhancedBatteryPredictionCurve(Context context, long zeroTime); /** * Checks whether the toggle for enhanced battery predictions is enabled. */ Loading src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java +7 −1 Original line number Diff line number Diff line Loading @@ -22,9 +22,10 @@ import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Process; import android.util.SparseIntArray; import com.android.internal.os.BatterySipper; import com.android.internal.util.ArrayUtils; import com.android.settings.fuelgauge.anomaly.Anomaly; public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider { Loading Loading @@ -93,6 +94,11 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider return -1; } @Override public SparseIntArray getEnhancedBatteryPredictionCurve(Context context, long zeroTime) { return null; } @Override public boolean isEnhancedBatteryPredictionEnabled(Context context) { return false; Loading src/com/android/settings/graph/UsageGraph.java +8 −1 Original line number Diff line number Diff line Loading @@ -110,6 +110,8 @@ public class UsageGraph extends View { void setMax(int maxX, int maxY) { mMaxX = maxX; mMaxY = maxY; calculateLocalPaths(); postInvalidate(); } void setDividerLoc(int height) { Loading Loading @@ -151,6 +153,10 @@ public class UsageGraph extends View { protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); updateGradient(); calculateLocalPaths(); } private void calculateLocalPaths() { calculateLocalPaths(mPaths, mLocalPaths); calculateLocalPaths(mProjectedPaths, mLocalProjectedPaths); } Loading Loading @@ -222,9 +228,10 @@ public class UsageGraph extends View { mMiddleDividerTint); drawDivider(canvas.getHeight() - mDividerSize, canvas, -1); if (mLocalPaths.size() == 0 && mProjectedPaths.size() == 0) { if (mLocalPaths.size() == 0 && mLocalProjectedPaths.size() == 0) { return; } drawLinePath(canvas, mLocalProjectedPaths, mDottedPaint); drawFilledPath(canvas, mLocalPaths, mFillPaint); drawLinePath(canvas, mLocalPaths, mLinePaint); Loading tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java +140 −10 Original line number Diff line number Diff line Loading @@ -16,34 +16,45 @@ package com.android.settings.fuelgauge; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.SystemClock; import android.util.SparseIntArray; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.graph.UsageView; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import java.util.concurrent.TimeUnit; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) Loading @@ -62,6 +73,7 @@ public class BatteryInfoTest { private Intent mDisChargingBatteryBroadcast; private Intent mChargingBatteryBroadcast; private Context mContext; private FakeFeatureFactory mFeatureFactory; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private BatteryStats mBatteryStats; Loading @@ -73,7 +85,7 @@ public class BatteryInfoTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); FakeFeatureFactory.setupForTest(mContext); mFeatureFactory = FakeFeatureFactory.setupForTest(mContext); mDisChargingBatteryBroadcast = new Intent(); mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED, 0); Loading Loading @@ -175,4 +187,122 @@ public class BatteryInfoTest { assertThat(info.chargeLabel).isEqualTo("100%"); } // Make our battery stats return a sequence of battery events. private void mockBatteryStatsHistory() { // Mock out new data every time start...Locked is called. doAnswer(invocation -> { doAnswer(new Answer() { private int count = 0; private long[] times = {1000, 1500, 2000}; private byte[] levels = {99, 98, 97}; @Override public Object answer(InvocationOnMock invocation) throws Throwable { if (count == times.length) { return false; } BatteryStats.HistoryItem record = invocation.getArgument(0); record.cmd = BatteryStats.HistoryItem.CMD_UPDATE; record.time = times[count]; record.batteryLevel = levels[count]; count++; return true; } }).when(mBatteryStats).getNextHistoryLocked(any(BatteryStats.HistoryItem.class)); return true; }).when(mBatteryStats).startIteratingHistoryLocked(); } private void assertOnlyHistory(BatteryInfo info) { mockBatteryStatsHistory(); UsageView view = mock(UsageView.class); doReturn(mContext).when(view).getContext(); info.bindHistory(view); verify(view, times(1)).configureGraph(anyInt(), anyInt()); verify(view, times(1)).addPath(any(SparseIntArray.class)); verify(view, never()).addProjectedPath(any(SparseIntArray.class)); } private void assertHistoryAndLinearProjection(BatteryInfo info) { mockBatteryStatsHistory(); UsageView view = mock(UsageView.class); doReturn(mContext).when(view).getContext(); info.bindHistory(view); verify(view, times(2)).configureGraph(anyInt(), anyInt()); verify(view, times(1)).addPath(any(SparseIntArray.class)); ArgumentCaptor<SparseIntArray> pointsActual = ArgumentCaptor.forClass(SparseIntArray.class); verify(view, times(1)).addProjectedPath(pointsActual.capture()); // Check that we have two points and the first is correct. assertThat(pointsActual.getValue().size()).isEqualTo(2); assertThat(pointsActual.getValue().keyAt(0)).isEqualTo(2000); assertThat(pointsActual.getValue().valueAt(0)).isEqualTo(97); } private void assertHistoryAndEnhancedProjection(BatteryInfo info) { mockBatteryStatsHistory(); UsageView view = mock(UsageView.class); doReturn(mContext).when(view).getContext(); SparseIntArray pointsExpected = new SparseIntArray(); pointsExpected.append(2000, 96); pointsExpected.append(2500, 95); pointsExpected.append(3000, 94); doReturn(pointsExpected).when(mFeatureFactory.powerUsageFeatureProvider) .getEnhancedBatteryPredictionCurve(any(Context.class), anyLong()); info.bindHistory(view); verify(view, times(2)).configureGraph(anyInt(), anyInt()); verify(view, times(1)).addPath(any(SparseIntArray.class)); ArgumentCaptor<SparseIntArray> pointsActual = ArgumentCaptor.forClass(SparseIntArray.class); verify(view, times(1)).addProjectedPath(pointsActual.capture()); assertThat(pointsActual.getValue()).isEqualTo(pointsExpected); } private BatteryInfo getBatteryInfo(boolean charging, boolean enhanced, boolean estimate) { if (charging && estimate) { doReturn(1000L).when(mBatteryStats).computeChargeTimeRemaining(anyLong()); } else { doReturn(0L).when(mBatteryStats).computeChargeTimeRemaining(anyLong()); } BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, charging ? mChargingBatteryBroadcast : mDisChargingBatteryBroadcast, mBatteryStats, SystemClock.elapsedRealtime() * 1000, false, estimate ? 1000 : 0 /* drainTimeUs */, false); doReturn(enhanced).when(mFeatureFactory.powerUsageFeatureProvider) .isEnhancedBatteryPredictionEnabled(mContext); return info; } @Test public void testBindHistory() { BatteryInfo info; info = getBatteryInfo(false /* charging */, false /* enhanced */, false /* estimate */); assertOnlyHistory(info); info = getBatteryInfo(false /* charging */, false /* enhanced */, true /* estimate */); assertHistoryAndLinearProjection(info); info = getBatteryInfo(false /* charging */, true /* enhanced */, false /* estimate */); assertOnlyHistory(info); info = getBatteryInfo(false /* charging */, true /* enhanced */, true /* estimate */); assertHistoryAndEnhancedProjection(info); info = getBatteryInfo(true /* charging */, false /* enhanced */, false /* estimate */); assertOnlyHistory(info); info = getBatteryInfo(true /* charging */, false /* enhanced */, true /* estimate */); assertHistoryAndLinearProjection(info); info = getBatteryInfo(true /* charging */, true /* enhanced */, false /* estimate */); assertOnlyHistory(info); // Linear projection for charging even in enhanced mode. info = getBatteryInfo(true /* charging */, true /* enhanced */, true /* estimate */); assertHistoryAndLinearProjection(info); } } Loading
src/com/android/settings/fuelgauge/BatteryInfo.java +33 −22 Original line number Diff line number Diff line Loading @@ -30,10 +30,10 @@ import android.text.format.Formatter; import android.util.SparseIntArray; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.R; import com.android.settings.Utils; import com.android.settings.graph.UsageView; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.R; public class BatteryInfo { Loading @@ -54,18 +54,20 @@ public class BatteryInfo { } public void bindHistory(final UsageView view, BatteryDataParser... parsers) { final Context context = view.getContext(); BatteryDataParser parser = new BatteryDataParser() { SparseIntArray points = new SparseIntArray(); long startTime; int lastTime = -1; byte lastLevel; int maxTime; @Override public void onParsingStarted(long startTime, long endTime) { this.maxTime = (int) (endTime - startTime); timePeriod = maxTime - (remainingTimeUs / 1000); this.startTime = startTime; timePeriod = endTime - startTime; view.clearPaths(); view.configureGraph(maxTime, 100); // Initially configure the graph for history only. view.configureGraph((int) timePeriod, 100); } @Override Loading @@ -87,10 +89,27 @@ public class BatteryInfo { public void onParsingDone() { onDataGap(); // Add linear projection if (lastTime >= 0 && remainingTimeUs != 0) { // Add projection if we have an estimate. if (remainingTimeUs != 0) { PowerUsageFeatureProvider provider = FeatureFactory.getFactory(context) .getPowerUsageFeatureProvider(context); if (!mCharging && provider.isEnhancedBatteryPredictionEnabled(context)) { points = provider.getEnhancedBatteryPredictionCurve(context, startTime); } else { // Linear extrapolation. if (lastTime >= 0) { points.put(lastTime, lastLevel); points.put(maxTime, mCharging ? 100 : 0); points.put((int) (timePeriod + BatteryUtils.convertUsToMs(remainingTimeUs)), mCharging ? 100 : 0); } } } // If we have a projection, reconfigure the graph to show it. if (points != null && points.size() > 0) { int maxTime = points.keyAt(points.size() - 1); view.configureGraph(maxTime, 100); view.addProjectedPath(points); } } Loading @@ -100,8 +119,7 @@ public class BatteryInfo { parserList[i] = parsers[i]; } parserList[parsers.length] = parser; parse(mStats, remainingTimeUs, parserList); final Context context = view.getContext(); parse(mStats, parserList); String timeString = context.getString(R.string.charge_length_format, Formatter.formatShortElapsedTime(context, timePeriod)); String remaining = ""; Loading Loading @@ -249,14 +267,11 @@ public class BatteryInfo { void onParsingDone(); } private static void parse(BatteryStats stats, long remainingTimeUs, BatteryDataParser... parsers) { private static void parse(BatteryStats stats, BatteryDataParser... parsers) { long startWalltime = 0; long endDateWalltime = 0; long endWalltime = 0; long historyStart = 0; long historyEnd = 0; byte lastLevel = -1; long curWalltime = startWalltime; long lastWallTime = 0; long lastRealtime = 0; Loading Loading @@ -292,17 +307,13 @@ public class BatteryInfo { } } if (rec.isDeltaData()) { if (rec.batteryLevel != lastLevel || pos == 1) { lastLevel = rec.batteryLevel; } lastInteresting = pos; historyEnd = rec.time; } } } stats.finishIteratingHistoryLocked(); endDateWalltime = lastWallTime + historyEnd - lastRealtime; endWalltime = endDateWalltime + (remainingTimeUs / 1000); endWalltime = lastWallTime + historyEnd - lastRealtime; int i = 0; final int N = lastInteresting; Loading @@ -310,7 +321,7 @@ public class BatteryInfo { for (int j = 0; j < parsers.length; j++) { parsers[j].onParsingStarted(startWalltime, endWalltime); } if (endDateWalltime > startWalltime && stats.startIteratingHistoryLocked()) { if (endWalltime > startWalltime && stats.startIteratingHistoryLocked()) { final HistoryItem rec = new HistoryItem(); while (stats.getNextHistoryLocked(rec) && i < N) { if (rec.isDeltaData()) { Loading
src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java +8 −1 Original line number Diff line number Diff line Loading @@ -20,8 +20,9 @@ import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.util.SparseIntArray; import com.android.internal.os.BatterySipper; import com.android.settings.fuelgauge.anomaly.Anomaly; /** * Feature Provider used in power usage Loading Loading @@ -67,6 +68,12 @@ public interface PowerUsageFeatureProvider { */ long getEnhancedBatteryPrediction(Context context); /** * Returns an improved projection curve for future battery level. * @param zeroTime timestamps (array keys) are shifted by this amount */ SparseIntArray getEnhancedBatteryPredictionCurve(Context context, long zeroTime); /** * Checks whether the toggle for enhanced battery predictions is enabled. */ Loading
src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java +7 −1 Original line number Diff line number Diff line Loading @@ -22,9 +22,10 @@ import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Process; import android.util.SparseIntArray; import com.android.internal.os.BatterySipper; import com.android.internal.util.ArrayUtils; import com.android.settings.fuelgauge.anomaly.Anomaly; public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider { Loading Loading @@ -93,6 +94,11 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider return -1; } @Override public SparseIntArray getEnhancedBatteryPredictionCurve(Context context, long zeroTime) { return null; } @Override public boolean isEnhancedBatteryPredictionEnabled(Context context) { return false; Loading
src/com/android/settings/graph/UsageGraph.java +8 −1 Original line number Diff line number Diff line Loading @@ -110,6 +110,8 @@ public class UsageGraph extends View { void setMax(int maxX, int maxY) { mMaxX = maxX; mMaxY = maxY; calculateLocalPaths(); postInvalidate(); } void setDividerLoc(int height) { Loading Loading @@ -151,6 +153,10 @@ public class UsageGraph extends View { protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); updateGradient(); calculateLocalPaths(); } private void calculateLocalPaths() { calculateLocalPaths(mPaths, mLocalPaths); calculateLocalPaths(mProjectedPaths, mLocalProjectedPaths); } Loading Loading @@ -222,9 +228,10 @@ public class UsageGraph extends View { mMiddleDividerTint); drawDivider(canvas.getHeight() - mDividerSize, canvas, -1); if (mLocalPaths.size() == 0 && mProjectedPaths.size() == 0) { if (mLocalPaths.size() == 0 && mLocalProjectedPaths.size() == 0) { return; } drawLinePath(canvas, mLocalProjectedPaths, mDottedPaint); drawFilledPath(canvas, mLocalPaths, mFillPaint); drawLinePath(canvas, mLocalPaths, mLinePaint); Loading
tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java +140 −10 Original line number Diff line number Diff line Loading @@ -16,34 +16,45 @@ package com.android.settings.fuelgauge; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.SystemClock; import android.util.SparseIntArray; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.graph.UsageView; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import java.util.concurrent.TimeUnit; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) Loading @@ -62,6 +73,7 @@ public class BatteryInfoTest { private Intent mDisChargingBatteryBroadcast; private Intent mChargingBatteryBroadcast; private Context mContext; private FakeFeatureFactory mFeatureFactory; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private BatteryStats mBatteryStats; Loading @@ -73,7 +85,7 @@ public class BatteryInfoTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); FakeFeatureFactory.setupForTest(mContext); mFeatureFactory = FakeFeatureFactory.setupForTest(mContext); mDisChargingBatteryBroadcast = new Intent(); mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED, 0); Loading Loading @@ -175,4 +187,122 @@ public class BatteryInfoTest { assertThat(info.chargeLabel).isEqualTo("100%"); } // Make our battery stats return a sequence of battery events. private void mockBatteryStatsHistory() { // Mock out new data every time start...Locked is called. doAnswer(invocation -> { doAnswer(new Answer() { private int count = 0; private long[] times = {1000, 1500, 2000}; private byte[] levels = {99, 98, 97}; @Override public Object answer(InvocationOnMock invocation) throws Throwable { if (count == times.length) { return false; } BatteryStats.HistoryItem record = invocation.getArgument(0); record.cmd = BatteryStats.HistoryItem.CMD_UPDATE; record.time = times[count]; record.batteryLevel = levels[count]; count++; return true; } }).when(mBatteryStats).getNextHistoryLocked(any(BatteryStats.HistoryItem.class)); return true; }).when(mBatteryStats).startIteratingHistoryLocked(); } private void assertOnlyHistory(BatteryInfo info) { mockBatteryStatsHistory(); UsageView view = mock(UsageView.class); doReturn(mContext).when(view).getContext(); info.bindHistory(view); verify(view, times(1)).configureGraph(anyInt(), anyInt()); verify(view, times(1)).addPath(any(SparseIntArray.class)); verify(view, never()).addProjectedPath(any(SparseIntArray.class)); } private void assertHistoryAndLinearProjection(BatteryInfo info) { mockBatteryStatsHistory(); UsageView view = mock(UsageView.class); doReturn(mContext).when(view).getContext(); info.bindHistory(view); verify(view, times(2)).configureGraph(anyInt(), anyInt()); verify(view, times(1)).addPath(any(SparseIntArray.class)); ArgumentCaptor<SparseIntArray> pointsActual = ArgumentCaptor.forClass(SparseIntArray.class); verify(view, times(1)).addProjectedPath(pointsActual.capture()); // Check that we have two points and the first is correct. assertThat(pointsActual.getValue().size()).isEqualTo(2); assertThat(pointsActual.getValue().keyAt(0)).isEqualTo(2000); assertThat(pointsActual.getValue().valueAt(0)).isEqualTo(97); } private void assertHistoryAndEnhancedProjection(BatteryInfo info) { mockBatteryStatsHistory(); UsageView view = mock(UsageView.class); doReturn(mContext).when(view).getContext(); SparseIntArray pointsExpected = new SparseIntArray(); pointsExpected.append(2000, 96); pointsExpected.append(2500, 95); pointsExpected.append(3000, 94); doReturn(pointsExpected).when(mFeatureFactory.powerUsageFeatureProvider) .getEnhancedBatteryPredictionCurve(any(Context.class), anyLong()); info.bindHistory(view); verify(view, times(2)).configureGraph(anyInt(), anyInt()); verify(view, times(1)).addPath(any(SparseIntArray.class)); ArgumentCaptor<SparseIntArray> pointsActual = ArgumentCaptor.forClass(SparseIntArray.class); verify(view, times(1)).addProjectedPath(pointsActual.capture()); assertThat(pointsActual.getValue()).isEqualTo(pointsExpected); } private BatteryInfo getBatteryInfo(boolean charging, boolean enhanced, boolean estimate) { if (charging && estimate) { doReturn(1000L).when(mBatteryStats).computeChargeTimeRemaining(anyLong()); } else { doReturn(0L).when(mBatteryStats).computeChargeTimeRemaining(anyLong()); } BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, charging ? mChargingBatteryBroadcast : mDisChargingBatteryBroadcast, mBatteryStats, SystemClock.elapsedRealtime() * 1000, false, estimate ? 1000 : 0 /* drainTimeUs */, false); doReturn(enhanced).when(mFeatureFactory.powerUsageFeatureProvider) .isEnhancedBatteryPredictionEnabled(mContext); return info; } @Test public void testBindHistory() { BatteryInfo info; info = getBatteryInfo(false /* charging */, false /* enhanced */, false /* estimate */); assertOnlyHistory(info); info = getBatteryInfo(false /* charging */, false /* enhanced */, true /* estimate */); assertHistoryAndLinearProjection(info); info = getBatteryInfo(false /* charging */, true /* enhanced */, false /* estimate */); assertOnlyHistory(info); info = getBatteryInfo(false /* charging */, true /* enhanced */, true /* estimate */); assertHistoryAndEnhancedProjection(info); info = getBatteryInfo(true /* charging */, false /* enhanced */, false /* estimate */); assertOnlyHistory(info); info = getBatteryInfo(true /* charging */, false /* enhanced */, true /* estimate */); assertHistoryAndLinearProjection(info); info = getBatteryInfo(true /* charging */, true /* enhanced */, false /* estimate */); assertOnlyHistory(info); // Linear projection for charging even in enhanced mode. info = getBatteryInfo(true /* charging */, true /* enhanced */, true /* estimate */); assertHistoryAndLinearProjection(info); } }