From 740ac9a0478abe5a4d0fe7188467d343f92e8b3b Mon Sep 17 00:00:00 2001 From: ykhung Date: Fri, 9 Apr 2021 17:40:40 +0800 Subject: [PATCH 1/2] Display the chart levels data into BatteryHistoryPreference Bug: 184807417 Bug: 180607918 Test: make SettingsRoboTests Test: make SettingsGoogleRoboTests Change-Id: I78718a59671ca6775abc725432fbfbec6c0993fe --- .../BatteryChartPreferenceController.java | 108 ++++++++++++++++-- .../settings/fuelgauge/BatteryHistEntry.java | 6 +- .../fuelgauge/BatteryHistoryPreference.java | 14 +++ .../settings/fuelgauge/ConvertUtils.java | 10 ++ .../fuelgauge/PowerUsageAdvanced.java | 13 ++- 5 files changed, 136 insertions(+), 15 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java index c8a7bc35869..56c18ba764a 100644 --- a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java @@ -20,6 +20,7 @@ package com.android.settings.fuelgauge; import android.content.Context; import android.util.Log; +import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceScreen; @@ -34,25 +35,49 @@ import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnDestroy; import com.android.settingslib.core.lifecycle.events.OnPause; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; -/** Cotrols the update for chart graph and the list items. */ +/** Controls the update for chart graph and the list items. */ public class BatteryChartPreferenceController extends AbstractPreferenceController - implements PreferenceControllerMixin, LifecycleObserver, OnPause, OnDestroy { + implements PreferenceControllerMixin, LifecycleObserver, OnPause, OnDestroy, + BatteryChartView.OnSelectListener { private static final String TAG = "BatteryChartPreferenceController"; + private static final int CHART_KEY_ARRAY_SIZE = 25; + private static final int CHART_LEVEL_ARRAY_SIZE = 13; + + @VisibleForTesting + PreferenceGroup mAppListGroup; + + private Context mPrefContext; + private BatteryChartView mBatteryChartView; + // Battery history relative data. + private int[] mBatteryHistoryLevels; + private long[] mBatteryHistoryKeys; + private Map> mBatteryHistoryMap; + + private int mTrapezoidIndex = BatteryChartView.SELECTED_INDEX_INVALID; private final String mPreferenceKey; + private final SettingsActivity mActivity; + private final InstrumentedPreferenceFragment mFragment; public BatteryChartPreferenceController( - Context context, String chartPreferenceKey, String listPreferenceKey, + Context context, String preferenceKey, Lifecycle lifecycle, SettingsActivity activity, InstrumentedPreferenceFragment fragment) { super(context); - mPreferenceKey = listPreferenceKey; + mActivity = activity; + mFragment = fragment; + mPreferenceKey = preferenceKey; + if (lifecycle != null) { + lifecycle.addObserver(this); + } } - @Override public void onPause() { } @@ -63,6 +88,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll @Override public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPrefContext = screen.getContext(); + mAppListGroup = screen.findPreference(mPreferenceKey); } @Override @@ -80,7 +108,73 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll return false; } - void refreshUi(Map> batteryHistoryMap) { - Log.d(TAG, "refreshUi:" + batteryHistoryMap.size()); + @Override + public void onSelect(int trapezoidIndex) { + Log.d(TAG, "onSelect:" + trapezoidIndex); + refreshUi(trapezoidIndex); + } + + void setBatteryHistoryMap(Map> batteryHistoryMap) { + // Assumes all timestamp data is consecutive and aligns to hourly time slot. + mBatteryHistoryMap = batteryHistoryMap; + // Generates battery history keys. + final List batteryHistoryKeyList = + new ArrayList(mBatteryHistoryMap.keySet()); + // Sorts all timestamp keys ordered by ASC from the map keys. + Collections.sort(batteryHistoryKeyList); + mBatteryHistoryKeys = new long[CHART_KEY_ARRAY_SIZE]; + final int elementSize = Math.min( + batteryHistoryKeyList.size(), CHART_KEY_ARRAY_SIZE); + final int offset = CHART_KEY_ARRAY_SIZE - elementSize; + for (int index = 0; index < elementSize; index++) { + mBatteryHistoryKeys[index + offset] = batteryHistoryKeyList.get(index); + } + // Generates the battery history levels. + mBatteryHistoryLevels = new int[CHART_LEVEL_ARRAY_SIZE]; + for (int index = 0; index < CHART_LEVEL_ARRAY_SIZE; index++) { + final Long timestamp = Long.valueOf(mBatteryHistoryKeys[index * 2]); + final List entryList = mBatteryHistoryMap.get(timestamp); + if (entryList != null && !entryList.isEmpty()) { + // All battery levels are the same in the same timestamp snapshot. + mBatteryHistoryLevels[index] = entryList.get(0).mBatteryLevel; + } else { + Log.w(TAG, "abnormal entry list in the timestamp:" + timestamp); + } + } + if (mBatteryChartView != null) { + mBatteryChartView.setLevels(mBatteryHistoryLevels); + } + Log.d(TAG, String.format("setBatteryHistoryMap() size=%d\nkeys=%s\nlevels=%s", + batteryHistoryKeyList.size(), + utcToLocalTime(mBatteryHistoryKeys), + Arrays.toString(mBatteryHistoryLevels))); + } + + void setBatteryChartView(BatteryChartView batteryChartView) { + mBatteryChartView = batteryChartView; + mBatteryChartView.setOnSelectListener(this); + if (mBatteryHistoryLevels != null) { + mBatteryChartView.setLevels(mBatteryHistoryLevels); + } + } + + private void refreshUi(int trapezoidIndex) { + // Invalid refresh condition. + if (mBatteryHistoryMap == null || mBatteryChartView == null || + mTrapezoidIndex == trapezoidIndex) { + return; + } + mTrapezoidIndex = trapezoidIndex; + Log.d(TAG, String.format("refreshUi: index=%d size=%d", + mTrapezoidIndex, mBatteryHistoryMap.size())); + } + + private static String utcToLocalTime(long[] timestamps) { + final StringBuilder builder = new StringBuilder(); + for (int index = 0; index < timestamps.length; index++) { + builder.append(String.format("%s| ", + ConvertUtils.utcToLocalTime(timestamps[index]))); + } + return builder.toString(); } } diff --git a/src/com/android/settings/fuelgauge/BatteryHistEntry.java b/src/com/android/settings/fuelgauge/BatteryHistEntry.java index 0ee9cf78b0c..fb46140aba6 100644 --- a/src/com/android/settings/fuelgauge/BatteryHistEntry.java +++ b/src/com/android/settings/fuelgauge/BatteryHistEntry.java @@ -17,12 +17,9 @@ import android.content.ContentValues; import android.database.Cursor; import android.util.Log; -import java.text.SimpleDateFormat; import java.time.Duration; -import java.util.Date; import java.util.TimeZone; - /** A container class to carry data from {@link ContentValues}. */ public final class BatteryHistEntry { private static final String TAG = "BatteryHistEntry"; @@ -117,8 +114,7 @@ public final class BatteryHistEntry { @Override public String toString() { - final String recordAtDateTime = - new SimpleDateFormat("MMM dd,yyyy HH:mm:ss").format(new Date(mTimestamp)); + final String recordAtDateTime = ConvertUtils.utcToLocalTime(mTimestamp); final StringBuilder builder = new StringBuilder() .append("\nBatteryHistEntry{") .append(String.format("\n\tpackage=%s|label=%s|uid=%d|userId=%d|isHidden=%b", diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java b/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java index cb22ff3ac72..5b9d5ec0285 100644 --- a/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java +++ b/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java @@ -39,6 +39,9 @@ public class BatteryHistoryPreference extends Preference { @VisibleForTesting BatteryInfo mBatteryInfo; + private BatteryChartView mBatteryChartView; + private BatteryChartPreferenceController mChartPreferenceController; + public BatteryHistoryPreference(Context context, AttributeSet attrs) { super(context, attrs); final boolean isChartGraphEnabled = @@ -58,6 +61,13 @@ public class BatteryHistoryPreference extends Preference { }, batteryUsageStats, false); } + void setChartPreferenceController(BatteryChartPreferenceController controller) { + mChartPreferenceController = controller; + if (mBatteryChartView != null) { + mChartPreferenceController.setBatteryChartView(mBatteryChartView); + } + } + @Override public void onBindViewHolder(PreferenceViewHolder view) { super.onBindViewHolder(view); @@ -65,6 +75,10 @@ public class BatteryHistoryPreference extends Preference { if (mBatteryInfo == null) { return; } + mBatteryChartView = (BatteryChartView) view.findViewById(R.id.battery_chart); + if (mChartPreferenceController != null) { + mChartPreferenceController.setBatteryChartView(mBatteryChartView); + } BatteryUtils.logRuntime(TAG, "onBindViewHolder", startTime); } } diff --git a/src/com/android/settings/fuelgauge/ConvertUtils.java b/src/com/android/settings/fuelgauge/ConvertUtils.java index 30ac7bf3820..5c0da636595 100644 --- a/src/com/android/settings/fuelgauge/ConvertUtils.java +++ b/src/com/android/settings/fuelgauge/ConvertUtils.java @@ -25,11 +25,16 @@ import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; import java.util.TimeZone; /** A utility class to convert data into another types. */ public final class ConvertUtils { private static final String TAG = "ConvertUtils"; + private static final SimpleDateFormat SIMPLE_DATE_FORMAT = + new SimpleDateFormat("MMM dd,yyyy HH:mm:ss", Locale.ENGLISH); /** Invalid system battery consumer drain type. */ public static final int INVALID_DRAIN_TYPE = -1; @@ -101,5 +106,10 @@ public final class ConvertUtils { return values; } + /** Converts UTC timestamp to human readable local time string. */ + public static String utcToLocalTime(long timestamp) { + return SIMPLE_DATE_FORMAT.format(new Date(timestamp)); + } + private ConvertUtils() {} } diff --git a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java index d5f443c802c..c3f299ba4d5 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java @@ -69,6 +69,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { if (!mIsChartGraphEnabled) { removePreference(KEY_BATTERY_GRAPH); } + setBatteryChartPreferenceController(); } @Override @@ -101,10 +102,10 @@ public class PowerUsageAdvanced extends PowerUsageBase { // Creates based on the chart design is enabled or not. if (mIsChartGraphEnabled) { mBatteryChartPreferenceController = - new BatteryChartPreferenceController(context, - KEY_BATTERY_GRAPH, KEY_APP_LIST, + new BatteryChartPreferenceController(context, KEY_APP_LIST, getSettingsLifecycle(), (SettingsActivity) getActivity(), this); controllers.add(mBatteryChartPreferenceController); + setBatteryChartPreferenceController(); } else { mBatteryAppListPreferenceController = new BatteryAppListPreferenceController(context, KEY_APP_LIST, @@ -131,7 +132,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { mBatteryUsageStats, /* showAllApps */true); } if (mBatteryChartPreferenceController != null && mBatteryHistoryMap != null) { - mBatteryChartPreferenceController.refreshUi(mBatteryHistoryMap); + mBatteryChartPreferenceController.setBatteryHistoryMap(mBatteryHistoryMap); } } @@ -156,6 +157,12 @@ public class PowerUsageAdvanced extends PowerUsageBase { } } + private void setBatteryChartPreferenceController() { + if (mHistPref != null && mBatteryChartPreferenceController != null) { + mHistPref.setChartPreferenceController(mBatteryChartPreferenceController); + } + } + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { @Override From bdaee1431f4329f297665b623406ebcfde9492f1 Mon Sep 17 00:00:00 2001 From: ykhung Date: Sun, 11 Apr 2021 11:03:35 +0800 Subject: [PATCH 2/2] Send fake data if there is no battey consumption entry list Sends fake data into SettingsIntelligence if there is no data in the battery consumption list, otherwise we don't have battery level data to draw into the chart graph. We should draw the battery levels if it is full-charged without any BatteryEntry data. Bug: 184807417 Test: make SettingsRoboTests Test: make SettingsGoogleRoboTests Change-Id: I759e769256f4aa0ec152afff5c265ee3d04c03da --- .../BatteryChartPreferenceController.java | 9 +-- .../settings/fuelgauge/ConvertUtils.java | 56 ++++++++++++------- .../settings/fuelgauge/ConvertUtilsTest.java | 23 ++++++++ 3 files changed, 63 insertions(+), 25 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java index 56c18ba764a..b5cc6ea07b5 100644 --- a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java @@ -50,7 +50,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll private static final int CHART_LEVEL_ARRAY_SIZE = 13; @VisibleForTesting - PreferenceGroup mAppListGroup; + PreferenceGroup mAppListPrefGroup; private Context mPrefContext; private BatteryChartView mBatteryChartView; @@ -90,7 +90,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mPrefContext = screen.getContext(); - mAppListGroup = screen.findPreference(mPreferenceKey); + mAppListPrefGroup = screen.findPreference(mPreferenceKey); } @Override @@ -137,8 +137,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll if (entryList != null && !entryList.isEmpty()) { // All battery levels are the same in the same timestamp snapshot. mBatteryHistoryLevels[index] = entryList.get(0).mBatteryLevel; - } else { - Log.w(TAG, "abnormal entry list in the timestamp:" + timestamp); + } else if (entryList != null && entryList.isEmpty()) { + Log.e(TAG, "abnormal entry list in the timestamp:" + + ConvertUtils.utcToLocalTime(timestamp)); } } if (mBatteryChartView != null) { diff --git a/src/com/android/settings/fuelgauge/ConvertUtils.java b/src/com/android/settings/fuelgauge/ConvertUtils.java index 5c0da636595..4ddee3252ef 100644 --- a/src/com/android/settings/fuelgauge/ConvertUtils.java +++ b/src/com/android/settings/fuelgauge/ConvertUtils.java @@ -33,10 +33,11 @@ import java.util.TimeZone; /** A utility class to convert data into another types. */ public final class ConvertUtils { private static final String TAG = "ConvertUtils"; - private static final SimpleDateFormat SIMPLE_DATE_FORMAT = - new SimpleDateFormat("MMM dd,yyyy HH:mm:ss", Locale.ENGLISH); + /** Invalid system battery consumer drain type. */ public static final int INVALID_DRAIN_TYPE = -1; + /** A fake package name to represent no BatteryEntry data. */ + public static final String FAKE_PACKAGE_NAME = "fake_package"; @IntDef(prefix = {"CONSUMER_TYPE"}, value = { CONSUMER_TYPE_UNKNOWN, @@ -52,6 +53,11 @@ public final class ConvertUtils { public static final int CONSUMER_TYPE_USER_BATTERY = 2; public static final int CONSUMER_TYPE_SYSTEM_BATTERY = 3; + private static String sZoneId; + private static SimpleDateFormat sSimpleDateFormat; + + private ConvertUtils() {} + /** Gets consumer type from {@link BatteryConsumer}. */ @ConsumerType public static int getConsumerType(BatteryConsumer consumer) { @@ -82,24 +88,28 @@ public final class ConvertUtils { int batteryHealth, long timestamp) { final ContentValues values = new ContentValues(); - values.put("uid", Long.valueOf(entry.getUid())); - values.put("userId", - Long.valueOf(UserHandle.getUserId(entry.getUid()))); - values.put("appLabel", entry.getLabel()); - values.put("packageName", entry.getDefaultPackageName()); - values.put("isHidden", Boolean.valueOf(entry.isHidden())); + if (entry != null && batteryUsageStats != null) { + values.put("uid", Long.valueOf(entry.getUid())); + values.put("userId", + Long.valueOf(UserHandle.getUserId(entry.getUid()))); + values.put("appLabel", entry.getLabel()); + values.put("packageName", entry.getDefaultPackageName()); + values.put("isHidden", Boolean.valueOf(entry.isHidden())); + values.put("totalPower", + Double.valueOf(batteryUsageStats.getConsumedPower())); + values.put("consumePower", Double.valueOf(entry.getConsumedPower())); + values.put("percentOfTotal", Double.valueOf(entry.percent)); + values.put("foregroundUsageTimeInMs", + Long.valueOf(entry.getTimeInForegroundMs())); + values.put("backgroundUsageTimeInMs", + Long.valueOf(entry.getTimeInBackgroundMs())); + values.put("drainType", getDrainType(entry.getBatteryConsumer())); + values.put("consumerType", getConsumerType(entry.getBatteryConsumer())); + } else { + values.put("packageName", FAKE_PACKAGE_NAME); + } values.put("timestamp", Long.valueOf(timestamp)); values.put("zoneId", TimeZone.getDefault().getID()); - values.put("totalPower", - Double.valueOf(batteryUsageStats.getConsumedPower())); - values.put("consumePower", Double.valueOf(entry.getConsumedPower())); - values.put("percentOfTotal", Double.valueOf(entry.percent)); - values.put("foregroundUsageTimeInMs", - Long.valueOf(entry.getTimeInForegroundMs())); - values.put("backgroundUsageTimeInMs", - Long.valueOf(entry.getTimeInBackgroundMs())); - values.put("drainType", getDrainType(entry.getBatteryConsumer())); - values.put("consumerType", getConsumerType(entry.getBatteryConsumer())); values.put("batteryLevel", Integer.valueOf(batteryLevel)); values.put("batteryStatus", Integer.valueOf(batteryStatus)); values.put("batteryHealth", Integer.valueOf(batteryHealth)); @@ -108,8 +118,12 @@ public final class ConvertUtils { /** Converts UTC timestamp to human readable local time string. */ public static String utcToLocalTime(long timestamp) { - return SIMPLE_DATE_FORMAT.format(new Date(timestamp)); + final String currentZoneId = TimeZone.getDefault().getID(); + if (!currentZoneId.equals(sZoneId) || sSimpleDateFormat == null) { + sZoneId = currentZoneId; + sSimpleDateFormat = + new SimpleDateFormat("MMM dd,yyyy HH:mm:ss", Locale.ENGLISH); + } + return sSimpleDateFormat.format(new Date(timestamp)); } - - private ConvertUtils() {} } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java index 70a49143985..6a5ea617b18 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java @@ -111,6 +111,29 @@ public final class ConvertUtilsTest { .isEqualTo(BatteryManager.BATTERY_HEALTH_COLD); } + @Test + public void testConvert_nullBatteryEntry_returnsExpectedContentValues() { + final ContentValues values = + ConvertUtils.convert( + /*entry=*/ null, + /*batteryUsageStats=*/ null, + /*batteryLevel=*/ 12, + /*batteryStatus=*/ BatteryManager.BATTERY_STATUS_FULL, + /*batteryHealth=*/ BatteryManager.BATTERY_HEALTH_COLD, + /*timestamp=*/ 10001L); + + assertThat(values.getAsLong("timestamp")).isEqualTo(10001L); + assertThat(values.getAsString("zoneId")) + .isEqualTo(TimeZone.getDefault().getID()); + assertThat(values.getAsInteger("batteryLevel")).isEqualTo(12); + assertThat(values.getAsInteger("batteryStatus")) + .isEqualTo(BatteryManager.BATTERY_STATUS_FULL); + assertThat(values.getAsInteger("batteryHealth")) + .isEqualTo(BatteryManager.BATTERY_HEALTH_COLD); + assertThat(values.getAsString("packageName")) + .isEqualTo(ConvertUtils.FAKE_PACKAGE_NAME); + } + @Test public void testGetDrainType_returnsExpetcedResult() { final int expectedType = 3;