diff --git a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java index c8a7bc35869..b5cc6ea07b5 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 mAppListPrefGroup; + + 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(); + mAppListPrefGroup = screen.findPreference(mPreferenceKey); } @Override @@ -80,7 +108,74 @@ 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 if (entryList != null && entryList.isEmpty()) { + Log.e(TAG, "abnormal entry list in the timestamp:" + + ConvertUtils.utcToLocalTime(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..4ddee3252ef 100644 --- a/src/com/android/settings/fuelgauge/ConvertUtils.java +++ b/src/com/android/settings/fuelgauge/ConvertUtils.java @@ -25,13 +25,19 @@ 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"; + /** 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, @@ -47,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) { @@ -77,29 +88,42 @@ 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)); return values; } - private ConvertUtils() {} + /** Converts UTC timestamp to human readable local time string. */ + public static String utcToLocalTime(long 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)); + } } 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 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;