Resolve locale not update issues in the chart view

- read locale from configuration rather than Locale.getDefault
- refine 12-24 format to align the current status bar short style
- resolve locale change not update chart percentage label
- extend timestamp label in the chart graph from 4 to 5 labels

Bug: 190150515
Bug: 190422902
Bug: 190226837
Test: make SettingsRoboTests
Change-Id: I5347964900123a6d112dbc37c2af87eb7d73f1d2
diff --git a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java
index ffbd2d9..3c9cbaa 100644
--- a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java
@@ -272,7 +272,7 @@
             final Map<String, BatteryHistEntry> entryMap = batteryHistoryMap.get(timestamp);
             if (entryMap == null || entryMap.isEmpty()) {
                 Log.e(TAG, "abnormal entry list in the timestamp:"
-                    + ConvertUtils.utcToLocalTime(timestamp));
+                    + ConvertUtils.utcToLocalTime(mPrefContext, timestamp));
                 continue;
             }
             // Averages the battery level in each time slot to avoid corner conditions.
@@ -287,7 +287,7 @@
         Log.d(TAG, String.format(
             "setBatteryHistoryMap() size=%d\nkeys=%s\nlevels=%s",
             batteryHistoryMap.size(),
-            utcToLocalTime(mBatteryHistoryKeys),
+            utcToLocalTime(mPrefContext, mBatteryHistoryKeys),
             Arrays.toString(mBatteryHistoryLevels)));
 
         // Loads item icon and label in the background.
@@ -496,9 +496,9 @@
         if (mTrapezoidIndex < 0) {
             return null;
         }
-        final String fromHour = ConvertUtils.utcToLocalTimeHour(
+        final String fromHour = ConvertUtils.utcToLocalTimeHour(mPrefContext,
             mBatteryHistoryKeys[mTrapezoidIndex * 2], mIs24HourFormat);
-        final String toHour = ConvertUtils.utcToLocalTimeHour(
+        final String toHour = ConvertUtils.utcToLocalTimeHour(mPrefContext,
             mBatteryHistoryKeys[(mTrapezoidIndex + 1) * 2], mIs24HourFormat);
         return String.format("%s - %s", fromHour, toHour);
     }
@@ -584,11 +584,11 @@
         mHandler.post(() -> mPreferenceScreen.addPreference(mFooterPreference));
     }
 
-    private static String utcToLocalTime(long[] timestamps) {
+    private static String utcToLocalTime(Context context, long[] timestamps) {
         final StringBuilder builder = new StringBuilder();
         for (int index = 0; index < timestamps.length; index++) {
             builder.append(String.format("%s| ",
-                  ConvertUtils.utcToLocalTime(timestamps[index])));
+                  ConvertUtils.utcToLocalTime(context, timestamps[index])));
         }
         return builder.toString();
     }
diff --git a/src/com/android/settings/fuelgauge/BatteryChartView.java b/src/com/android/settings/fuelgauge/BatteryChartView.java
index ed64177..b721f14 100644
--- a/src/com/android/settings/fuelgauge/BatteryChartView.java
+++ b/src/com/android/settings/fuelgauge/BatteryChartView.java
@@ -56,14 +56,8 @@
     private static final List<String> ACCESSIBILITY_SERVICE_NAMES =
         Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService");
 
-    // For drawing the percentage information.
-    private static final String[] PERCENTAGES = new String[] {
-            formatPercentage(/*percentage=*/ 100, /*round=*/ true),
-            formatPercentage(/*percentage=*/ 50, /*round=*/ true),
-            formatPercentage(/*percentage=*/ 0, /*round=*/ true)};
-
     private static final int DEFAULT_TRAPEZOID_COUNT = 12;
-    private static final int DEFAULT_TIMESTAMP_COUNT = 4;
+    private static final int DEFAULT_TIMESTAMP_COUNT = 5;
     private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
     private static final long UPDATE_STATE_DELAYED_TIME = 500L;
 
@@ -82,6 +76,7 @@
     private float mTrapezoidVOffset;
     private float mTrapezoidHOffset;
     private boolean mIsSlotsClickabled;
+    private String[] mPercentages = getPercentages();
 
     @VisibleForTesting int mSelectedIndex;
     @VisibleForTesting String[] mTimestamps;
@@ -96,7 +91,7 @@
         new Rect[] {new Rect(), new Rect(), new Rect()};
     // For drawing the timestamp information.
     private final Rect[] mTimestampsBounds =
-        new Rect[] {new Rect(), new Rect(), new Rect(), new Rect()};
+        new Rect[] {new Rect(), new Rect(), new Rect(), new Rect(), new Rect()};
 
     @VisibleForTesting
     Handler mHandler = new Handler();
@@ -107,6 +102,7 @@
     private Paint mTextPaint;
     private Paint mDividerPaint;
     private Paint mTrapezoidPaint;
+
     @VisibleForTesting
     Paint mTrapezoidCurvePaint = null;
     private TrapezoidSlot[] mTrapezoidSlots;
@@ -201,12 +197,13 @@
         if (mTimestamps == null) {
             mTimestamps = new String[DEFAULT_TIMESTAMP_COUNT];
         }
-        final long timeSlotOffset = DateUtils.HOUR_IN_MILLIS * 8;
+        final long timeSlotOffset = DateUtils.HOUR_IN_MILLIS * 6;
         final boolean is24HourFormat = DateFormat.is24HourFormat(getContext());
         for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
             mTimestamps[index] =
                 ConvertUtils.utcToLocalTimeHour(
-                    latestTimestamp - (3 - index) * timeSlotOffset,
+                    getContext(),
+                    latestTimestamp - (4 - index) * timeSlotOffset,
                     is24HourFormat);
         }
         requestLayout();
@@ -217,9 +214,9 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         // Measures text bounds and updates indent configuration.
         if (mTextPaint != null) {
-            for (int index = 0; index < PERCENTAGES.length; index++) {
+            for (int index = 0; index < mPercentages.length; index++) {
                 mTextPaint.getTextBounds(
-                    PERCENTAGES[index], 0, PERCENTAGES[index].length(),
+                    mPercentages[index], 0, mPercentages[index].length(),
                     mPercentageBounds[index]);
             }
             // Updates the indent configurations.
@@ -396,7 +393,7 @@
     private void drawPercentage(Canvas canvas, int index, float offsetY) {
         if (mTextPaint != null) {
             canvas.drawText(
-                PERCENTAGES[index],
+                mPercentages[index],
                 getWidth() - mPercentageBounds[index].width() - mPercentageBounds[index].left,
                 offsetY + mPercentageBounds[index].height() *.5f,
                 mTextPaint);
@@ -429,7 +426,7 @@
             final float baselineX = mDividerWidth * .5f;
             final float offsetX = mDividerWidth + unitWidth;
             for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
-                xOffsets[index] = baselineX + index * offsetX * 4;
+                xOffsets[index] = baselineX + index * offsetX * 3;
             }
             drawTimestamp(canvas, xOffsets);
         }
@@ -443,11 +440,11 @@
             getTimestampY(0), mTextPaint);
         // Draws the last timestamp info.
         canvas.drawText(
-            mTimestamps[3],
-            xOffsets[3] - mTimestampsBounds[3].width() - mTimestampsBounds[3].left,
-            getTimestampY(3), mTextPaint);
+            mTimestamps[4],
+            xOffsets[4] - mTimestampsBounds[4].width() - mTimestampsBounds[4].left,
+            getTimestampY(4), mTextPaint);
         // Draws the rest of timestamp info since it is located in the center.
-        for (int index = 1; index <= 2; index++) {
+        for (int index = 1; index <= 3; index++) {
             canvas.drawText(
                 mTimestamps[index],
                 xOffsets[index] -
@@ -544,6 +541,13 @@
                 && mLevels[trapezoidIndex + 1] != 0;
     }
 
+    private static String[] getPercentages() {
+        return new String[] {
+            formatPercentage(/*percentage=*/ 100, /*round=*/ true),
+            formatPercentage(/*percentage=*/ 50, /*round=*/ true),
+            formatPercentage(/*percentage=*/ 0, /*round=*/ true)};
+    }
+
     @VisibleForTesting
     static boolean isAccessibilityEnabled(Context context) {
         final AccessibilityManager accessibilityManager =
diff --git a/src/com/android/settings/fuelgauge/BatteryHistEntry.java b/src/com/android/settings/fuelgauge/BatteryHistEntry.java
index d83d814..4c8ecee 100644
--- a/src/com/android/settings/fuelgauge/BatteryHistEntry.java
+++ b/src/com/android/settings/fuelgauge/BatteryHistEntry.java
@@ -184,7 +184,8 @@
 
     @Override
     public String toString() {
-        final String recordAtDateTime = ConvertUtils.utcToLocalTime(mTimestamp);
+        final String recordAtDateTime =
+            ConvertUtils.utcToLocalTime(/*context=*/ null, 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/ConvertUtils.java b/src/com/android/settings/fuelgauge/ConvertUtils.java
index 1980564..01f510e 100644
--- a/src/com/android/settings/fuelgauge/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/ConvertUtils.java
@@ -17,6 +17,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.os.BatteryUsageStats;
+import android.os.LocaleList;
 import android.os.UserHandle;
 import android.text.format.DateUtils;
 import android.util.Log;
@@ -133,8 +134,8 @@
     }
 
     /** Converts UTC timestamp to human readable local time string. */
-    public static String utcToLocalTime(long timestamp) {
-        final Locale currentLocale = Locale.getDefault();
+    public static String utcToLocalTime(Context context, long timestamp) {
+        final Locale currentLocale = getLocale(context);
         final String currentZoneId = TimeZone.getDefault().getID();
         if (!currentZoneId.equals(sZoneId)
                 || !currentLocale.equals(sLocale)
@@ -148,8 +149,9 @@
     }
 
     /** Converts UTC timestamp to local time hour data. */
-    public static String utcToLocalTimeHour(long timestamp, boolean is24HourFormat) {
-        final Locale currentLocale = Locale.getDefault();
+    public static String utcToLocalTimeHour(
+            Context context, long timestamp, boolean is24HourFormat) {
+        final Locale currentLocale = getLocale(context);
         final String currentZoneId = TimeZone.getDefault().getID();
         if (!currentZoneId.equals(sZoneIdForHour)
                 || !currentLocale.equals(sLocaleForHour)
@@ -159,7 +161,7 @@
             sZoneIdForHour = currentZoneId;
             sIs24HourFormat = is24HourFormat;
             sSimpleDateFormatForHour = new SimpleDateFormat(
-                    sIs24HourFormat ? "HH" : "h aa", currentLocale);
+                    sIs24HourFormat ? "HH" : "h", currentLocale);
         }
         return sSimpleDateFormatForHour.format(new Date(timestamp))
             .toLowerCase(currentLocale);
@@ -356,4 +358,15 @@
                 ? entry3 : null;
         }
     }
+
+    @VisibleForTesting
+    static Locale getLocale(Context context) {
+        if (context == null) {
+            return Locale.getDefault();
+        }
+        final LocaleList locales =
+            context.getResources().getConfiguration().getLocales();
+        return locales != null && !locales.isEmpty() ? locales.get(0)
+            : Locale.getDefault();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartPreferenceControllerTest.java
index 7894c3f..ef76eee 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartPreferenceControllerTest.java
@@ -35,6 +35,7 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.LocaleList;
 import android.text.format.DateUtils;
 import android.util.Pair;
 
@@ -100,6 +101,8 @@
         mFeatureFactory = FakeFeatureFactory.setupForTest();
         mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider;
         mContext = spy(RuntimeEnvironment.application);
+        mContext.getResources().getConfiguration().setLocales(
+            new LocaleList(new Locale("en_US")));
         mBatteryChartPreferenceController = createController();
         mBatteryChartPreferenceController.mPrefContext = mContext;
         mBatteryChartPreferenceController.mAppListPrefGroup = mAppListGroup;
@@ -573,14 +576,12 @@
         // Verifies the title in the preference group.
         verify(mBatteryChartPreferenceController.mAppListPrefGroup)
             .setTitle(captor.capture());
-        assertThat(captor.getValue())
-            .isEqualTo("App usage for 4 pm - 7 am");
+        assertThat(captor.getValue()).isEqualTo("App usage for 4 - 7");
         // Verifies the title in the expandable divider.
         captor = ArgumentCaptor.forClass(String.class);
         verify(mBatteryChartPreferenceController.mExpandDividerPreference)
             .setTitle(captor.capture());
-        assertThat(captor.getValue())
-            .isEqualTo("System usage for 4 pm - 7 am");
+        assertThat(captor.getValue()).isEqualTo("System usage for 4 - 7");
     }
 
     @Test
@@ -716,7 +717,8 @@
     private void setUpBatteryHistoryKeys() {
         mBatteryChartPreferenceController.mBatteryHistoryKeys =
             new long[] {1619196786769L, 0L, 1619247636826L};
-        ConvertUtils.utcToLocalTimeHour(/*timestamp=*/ 0, /*is24HourFormat=*/ false);
+        ConvertUtils.utcToLocalTimeHour(
+            mContext, /*timestamp=*/ 0, /*is24HourFormat=*/ false);
         // Simulates the locale in GMT.
         ConvertUtils.sSimpleDateFormatForHour
              .setTimeZone(TimeZone.getTimeZone("GMT"));
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartViewTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartViewTest.java
index 3f94456..ec77f4c 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartViewTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartViewTest.java
@@ -27,6 +27,7 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.Context;
+import android.os.LocaleList;
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.settings.testutils.FakeFeatureFactory;
@@ -41,6 +42,7 @@
 
 import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Locale;
 import java.util.TimeZone;
 
 @RunWith(RobolectricTestRunner.class)
@@ -60,6 +62,8 @@
         mFeatureFactory = FakeFeatureFactory.setupForTest();
         mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider;
         mContext = spy(RuntimeEnvironment.application);
+        mContext.getResources().getConfiguration().setLocales(
+            new LocaleList(new Locale("en_US")));
         mBatteryChartView = new BatteryChartView(mContext);
         doReturn(mockAccessibilityManager).when(mContext)
             .getSystemService(AccessibilityManager.class);
@@ -234,11 +238,11 @@
         final long timestamp = 1619196786769L;
         ConvertUtils.sSimpleDateFormatForHour = null;
         // Invokes the method first to create the SimpleDateFormat.
-        ConvertUtils.utcToLocalTimeHour(/*timestamp=*/ 0, /*is24HourFormat=*/ false);
+        ConvertUtils.utcToLocalTimeHour(
+            mContext, /*timestamp=*/ 0, /*is24HourFormat=*/ false);
         ConvertUtils.sSimpleDateFormatForHour
             .setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
-        final String[] expectedTimestamps =
-            new String[] {"9 am", "5 pm", "1 am", "9 am"};
+        final String[] expectedTimestamps = new String[] {"00", "06", "12", "18", "00"};
 
         mBatteryChartView.setLatestTimestamp(timestamp);
 
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java
index efabe44..67a60af 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.os.BatteryManager;
 import android.os.BatteryUsageStats;
+import android.os.LocaleList;
 import android.os.UserHandle;
 
 import com.android.settings.testutils.FakeFeatureFactory;
@@ -315,6 +316,7 @@
             .isEqualTo(entry.mConsumePower * ratio);
     }
 
+    @Test
     public void testUtcToLocalTime_returnExpectedResult() {
         ConvertUtils.sZoneId = null;
         ConvertUtils.sLocale = null;
@@ -322,48 +324,76 @@
         final String expectedZoneId = "America/Los_Angeles";
         ConvertUtils.sSimpleDateFormat = null;
         // Invokes the method first to create the SimpleDateFormat.
-        ConvertUtils.utcToLocalTime(/*timestamp=*/ 0);
+        ConvertUtils.utcToLocalTime(mContext, /*timestamp=*/ 0);
         ConvertUtils.sSimpleDateFormat
             .setTimeZone(TimeZone.getTimeZone(expectedZoneId));
+        mContext.getResources().getConfiguration().setLocales(
+            new LocaleList(new Locale("en_US")));
 
-        assertThat(ConvertUtils.utcToLocalTime(timestamp))
-            .isEqualTo("Apr 23,2021 09:53:06");
+        assertThat(ConvertUtils.utcToLocalTime(mContext, timestamp))
+            .isEqualTo("Apr 24,2021 00:53:06");
         assertThat(ConvertUtils.sZoneId).isNotEqualTo(expectedZoneId);
-        assertThat(ConvertUtils.sLocale).isEqualTo(Locale.getDefault());
+        assertThat(ConvertUtils.sLocale).isEqualTo(new Locale("en_US"));
     }
 
+    @Test
     public void testUtcToLocalTimeHour_12HourFormat_returnExpectedResult() {
         ConvertUtils.sZoneIdForHour = null;
         ConvertUtils.sLocaleForHour = null;
-        final long timestamp = 1619196786769L;
+        final long timestamp = 1619000086769L;
         final String expectedZoneId = "America/Los_Angeles";
         ConvertUtils.sSimpleDateFormatForHour = null;
         // Invokes the method first to create the SimpleDateFormat.
-        ConvertUtils.utcToLocalTimeHour(/*timestamp=*/ 0, /*is24HourFormat=*/ false);
+        ConvertUtils.utcToLocalTimeHour(
+            mContext, /*timestamp=*/ 0, /*is24HourFormat=*/ false);
         ConvertUtils.sSimpleDateFormatForHour
             .setTimeZone(TimeZone.getTimeZone(expectedZoneId));
+        mContext.getResources().getConfiguration().setLocales(
+            new LocaleList(new Locale("en_US")));
 
         assertThat(ConvertUtils.utcToLocalTimeHour(
-            timestamp, /*is24HourFormat=*/ false)).isEqualTo("9 am");
+            mContext, timestamp, /*is24HourFormat=*/ false)).isEqualTo("6");
         assertThat(ConvertUtils.sZoneIdForHour).isNotEqualTo(expectedZoneId);
-        assertThat(ConvertUtils.sLocaleForHour).isEqualTo(Locale.getDefault());
+        assertThat(ConvertUtils.sLocaleForHour).isEqualTo(new Locale("en_US"));
     }
 
+    @Test
     public void testUtcToLocalTimeHour_24HourFormat_returnExpectedResult() {
         ConvertUtils.sZoneIdForHour = null;
         ConvertUtils.sLocaleForHour = null;
-        final long timestamp = 1619196786769L;
+        final long timestamp = 1619000086769L;
         final String expectedZoneId = "America/Los_Angeles";
         ConvertUtils.sSimpleDateFormatForHour = null;
         // Invokes the method first to create the SimpleDateFormat.
-        ConvertUtils.utcToLocalTimeHour(/*timestamp=*/ 0, /*is24HourFormat=*/ true);
+        ConvertUtils.utcToLocalTimeHour(
+            mContext, /*timestamp=*/ 0, /*is24HourFormat=*/ false);
         ConvertUtils.sSimpleDateFormatForHour
             .setTimeZone(TimeZone.getTimeZone(expectedZoneId));
+        mContext.getResources().getConfiguration().setLocales(
+            new LocaleList(new Locale("en_US")));
 
         assertThat(ConvertUtils.utcToLocalTimeHour(
-            timestamp, /*is24HourFormat=*/ true)).isEqualTo("09");
+            mContext, timestamp, /*is24HourFormat=*/ true)).isEqualTo("18");
         assertThat(ConvertUtils.sZoneIdForHour).isNotEqualTo(expectedZoneId);
-        assertThat(ConvertUtils.sLocaleForHour).isEqualTo(Locale.getDefault());
+        assertThat(ConvertUtils.sLocaleForHour).isEqualTo(new Locale("en_US"));
+    }
+
+    @Test
+    public void getLocale_nullContext_returnDefaultLocale() {
+        assertThat(ConvertUtils.getLocale(/*context=*/ null))
+            .isEqualTo(Locale.getDefault());
+    }
+
+    @Test
+    public void getLocale_nullLocaleList_returnDefaultLocale() {
+        mContext.getResources().getConfiguration().setLocales(null);
+        assertThat(ConvertUtils.getLocale(mContext)).isEqualTo(Locale.getDefault());
+    }
+
+    @Test
+    public void getLocale_emptyLocaleList_returnDefaultLocale() {
+        mContext.getResources().getConfiguration().setLocales(new LocaleList());
+        assertThat(ConvertUtils.getLocale(mContext)).isEqualTo(Locale.getDefault());
     }
 
     private static BatteryHistEntry createBatteryHistEntry(