[QPR1] Add time unit into battery usage chart and refine some UI

- add time unit into battery chart for 12/24 hours
- refine the radius size for chart bar
- use fixed font size for timestamp and percentage

screenshots:
24-hour format: https://screenshot.googleplex.com/3VNedE2nAx6LrXu
12-hour format: https://screenshot.googleplex.com/3tpTXjYfrasrenW
fixed-size: https://screenshot.googleplex.com/nPRKm7XCkmFzW6K

Bug: 194836607
Test: make SettingsRoboTests
Change-Id: I5b35b0fa808eb8a5f607962ee973c2896529b078
Merged-In: I5b35b0fa808eb8a5f607962ee973c2896529b078
This commit is contained in:
ykhung
2021-08-09 23:02:29 +08:00
committed by YUKAI HUNG
parent 36a42b630b
commit 0791d77df0
8 changed files with 31 additions and 178 deletions

View File

@@ -40,12 +40,14 @@
android:contentDescription="@string/battery_usage_chart" android:contentDescription="@string/battery_usage_chart"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
settings:textColor="?android:attr/textColorSecondary" /> settings:textColor="?android:attr/textColorSecondary" />
<!-- Use non-scalable text size from text_size_small_material -->
<TextView <TextView
android:id="@+id/companion_text" android:id="@+id/companion_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone" android:visibility="gone"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"/> android:textColor="?android:attr/textColorSecondary"
android:textSize="14dp" />
</LinearLayout> </LinearLayout>

View File

@@ -437,7 +437,7 @@
<dimen name="chartview_text_padding">6dp</dimen> <dimen name="chartview_text_padding">6dp</dimen>
<dimen name="chartview_divider_width">1dp</dimen> <dimen name="chartview_divider_width">1dp</dimen>
<dimen name="chartview_divider_height">4dp</dimen> <dimen name="chartview_divider_height">4dp</dimen>
<dimen name="chartview_trapezoid_radius">3dp</dimen> <dimen name="chartview_trapezoid_radius">5dp</dimen>
<dimen name="chartview_trapezoid_margin_start">1dp</dimen> <dimen name="chartview_trapezoid_margin_start">1dp</dimen>
<dimen name="chartview_trapezoid_margin_bottom">2dp</dimen> <dimen name="chartview_trapezoid_margin_bottom">2dp</dimen>
</resources> </resources>

View File

@@ -6614,10 +6614,6 @@
<string name="battery_system_usage_for">System usage for <xliff:g id="slot">%s</xliff:g></string> <string name="battery_system_usage_for">System usage for <xliff:g id="slot">%s</xliff:g></string>
<!-- [CHAR_LIMIT=NONE] Battery app usage section header --> <!-- [CHAR_LIMIT=NONE] Battery app usage section header -->
<string name="battery_app_usage_for">App usage for <xliff:g id="slot">%s</xliff:g></string> <string name="battery_app_usage_for">App usage for <xliff:g id="slot">%s</xliff:g></string>
<!-- [CHAR_LIMIT=NONE] Battery usage section header for a specific time slot -->
<string name="battery_usage_time_am">am</string>
<!-- [CHAR_LIMIT=NONE] Battery usage section header for a specific time slot -->
<string name="battery_usage_time_pm">pm</string>
<!-- [CHAR_LIMIT=NONE] Battery usage item for total usage time less than a minute --> <!-- [CHAR_LIMIT=NONE] Battery usage item for total usage time less than a minute -->
<string name="battery_usage_total_less_than_one_minute">Total: less than a min</string> <string name="battery_usage_total_less_than_one_minute">Total: less than a min</string>
<!-- [CHAR_LIMIT=NONE] Battery usage item for total background time less than a minute --> <!-- [CHAR_LIMIT=NONE] Battery usage item for total background time less than a minute -->

View File

@@ -57,7 +57,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService"); Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService");
private static final int DEFAULT_TRAPEZOID_COUNT = 12; private static final int DEFAULT_TRAPEZOID_COUNT = 12;
private static final int DEFAULT_TIMESTAMP_COUNT = 5; private static final int DEFAULT_TIMESTAMP_COUNT = 4;
private static final int TIMESTAMP_GAPS_COUNT = DEFAULT_TIMESTAMP_COUNT - 1;
private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5"); private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
private static final long UPDATE_STATE_DELAYED_TIME = 500L; private static final long UPDATE_STATE_DELAYED_TIME = 500L;
@@ -91,7 +92,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
new Rect[] {new Rect(), new Rect(), new Rect()}; new Rect[] {new Rect(), new Rect(), new Rect()};
// For drawing the timestamp information. // For drawing the timestamp information.
private final Rect[] mTimestampsBounds = 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 @VisibleForTesting
Handler mHandler = new Handler(); Handler mHandler = new Handler();
@@ -198,13 +199,14 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
if (mTimestamps == null) { if (mTimestamps == null) {
mTimestamps = new String[DEFAULT_TIMESTAMP_COUNT]; mTimestamps = new String[DEFAULT_TIMESTAMP_COUNT];
} }
final long timeSlotOffset = DateUtils.HOUR_IN_MILLIS * 6; final long timeSlotOffset =
DateUtils.HOUR_IN_MILLIS * (/*total 24 hours*/ 24 / TIMESTAMP_GAPS_COUNT);
final boolean is24HourFormat = DateFormat.is24HourFormat(getContext()); final boolean is24HourFormat = DateFormat.is24HourFormat(getContext());
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) { for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
mTimestamps[index] = mTimestamps[index] =
ConvertUtils.utcToLocalTimeHour( ConvertUtils.utcToLocalTimeHour(
getContext(), getContext(),
latestTimestamp - (4 - index) * timeSlotOffset, latestTimestamp - (TIMESTAMP_GAPS_COUNT - index) * timeSlotOffset,
is24HourFormat); is24HourFormat);
} }
requestLayout(); requestLayout();
@@ -426,8 +428,9 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
final float[] xOffsets = new float[DEFAULT_TIMESTAMP_COUNT]; final float[] xOffsets = new float[DEFAULT_TIMESTAMP_COUNT];
final float baselineX = mDividerWidth * .5f; final float baselineX = mDividerWidth * .5f;
final float offsetX = mDividerWidth + unitWidth; final float offsetX = mDividerWidth + unitWidth;
final int slotBarOffset = (/*total 12 bars*/ 12) / TIMESTAMP_GAPS_COUNT;
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) { for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
xOffsets[index] = baselineX + index * offsetX * 3; xOffsets[index] = baselineX + index * offsetX * slotBarOffset;
} }
drawTimestamp(canvas, xOffsets); drawTimestamp(canvas, xOffsets);
} }
@@ -439,13 +442,15 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
mTimestamps[0], mTimestamps[0],
xOffsets[0] - mTimestampsBounds[0].left, xOffsets[0] - mTimestampsBounds[0].left,
getTimestampY(0), mTextPaint); getTimestampY(0), mTextPaint);
final int latestIndex = DEFAULT_TIMESTAMP_COUNT - 1;
// Draws the last timestamp info. // Draws the last timestamp info.
canvas.drawText( canvas.drawText(
mTimestamps[4], mTimestamps[latestIndex],
xOffsets[4] - mTimestampsBounds[4].width() - mTimestampsBounds[4].left, xOffsets[latestIndex] - mTimestampsBounds[latestIndex].width()
getTimestampY(4), mTextPaint); - mTimestampsBounds[latestIndex].left,
getTimestampY(latestIndex), mTextPaint);
// Draws the rest of timestamp info since it is located in the center. // Draws the rest of timestamp info since it is located in the center.
for (int index = 1; index <= 3; index++) { for (int index = 1; index <= DEFAULT_TIMESTAMP_COUNT - 2; index++) {
canvas.drawText( canvas.drawText(
mTimestamps[index], mTimestamps[index],
xOffsets[index] - xOffsets[index] -

View File

@@ -19,6 +19,7 @@ import android.content.Context;
import android.os.BatteryUsageStats; import android.os.BatteryUsageStats;
import android.os.LocaleList; import android.os.LocaleList;
import android.os.UserHandle; import android.os.UserHandle;
import android.text.format.DateFormat;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.Log; import android.util.Log;
@@ -26,10 +27,8 @@ import androidx.annotation.VisibleForTesting;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@@ -76,19 +75,6 @@ public final class ConvertUtils {
public static final int CONSUMER_TYPE_USER_BATTERY = 2; public static final int CONSUMER_TYPE_USER_BATTERY = 2;
public static final int CONSUMER_TYPE_SYSTEM_BATTERY = 3; public static final int CONSUMER_TYPE_SYSTEM_BATTERY = 3;
// For language is changed.
@VisibleForTesting static Locale sLocale;
@VisibleForTesting static Locale sLocaleForHour;
// For time zone is changed.
@VisibleForTesting static String sZoneId;
@VisibleForTesting static String sZoneIdForHour;
private static boolean sIs24HourFormat;
@VisibleForTesting
static SimpleDateFormat sSimpleDateFormat;
@VisibleForTesting
static SimpleDateFormat sSimpleDateFormatForHour;
private ConvertUtils() {} private ConvertUtils() {}
public static ContentValues convert( public static ContentValues convert(
@@ -136,36 +122,21 @@ public final class ConvertUtils {
/** Converts UTC timestamp to human readable local time string. */ /** Converts UTC timestamp to human readable local time string. */
public static String utcToLocalTime(Context context, long timestamp) { public static String utcToLocalTime(Context context, long timestamp) {
final Locale currentLocale = getLocale(context); final Locale locale = getLocale(context);
final String currentZoneId = TimeZone.getDefault().getID(); final String pattern =
if (!currentZoneId.equals(sZoneId) DateFormat.getBestDateTimePattern(locale, "MMM dd,yyyy HH:mm:ss");
|| !currentLocale.equals(sLocale) return DateFormat.format(pattern, timestamp).toString();
|| sSimpleDateFormat == null) {
sLocale = currentLocale;
sZoneId = currentZoneId;
sSimpleDateFormat =
new SimpleDateFormat("MMM dd,yyyy HH:mm:ss", currentLocale);
}
return sSimpleDateFormat.format(new Date(timestamp));
} }
/** Converts UTC timestamp to local time hour data. */ /** Converts UTC timestamp to local time hour data. */
public static String utcToLocalTimeHour( public static String utcToLocalTimeHour(
Context context, long timestamp, boolean is24HourFormat) { Context context, long timestamp, boolean is24HourFormat) {
final Locale currentLocale = getLocale(context); final Locale locale = getLocale(context);
final String currentZoneId = TimeZone.getDefault().getID(); // e.g. for 12-hour format: 9 pm
if (!currentZoneId.equals(sZoneIdForHour) // e.g. for 24-hour format: 09:00
|| !currentLocale.equals(sLocaleForHour) final String skeleton = is24HourFormat ? "HHm" : "ha";
|| sIs24HourFormat != is24HourFormat final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton);
|| sSimpleDateFormatForHour == null) { return DateFormat.format(pattern, timestamp).toString().toLowerCase(locale);
sLocaleForHour = currentLocale;
sZoneIdForHour = currentZoneId;
sIs24HourFormat = is24HourFormat;
sSimpleDateFormatForHour = new SimpleDateFormat(
sIs24HourFormat ? "HH" : "h", currentLocale);
}
return sSimpleDateFormatForHour.format(new Date(timestamp))
.toLowerCase(currentLocale);
} }
/** Gets indexed battery usage data for each corresponding time slot. */ /** Gets indexed battery usage data for each corresponding time slot. */

View File

@@ -50,7 +50,6 @@ import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
@@ -65,7 +64,6 @@ import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TimeZone;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public final class BatteryChartPreferenceControllerTest { public final class BatteryChartPreferenceControllerTest {
@@ -125,37 +123,6 @@ public final class BatteryChartPreferenceControllerTest {
createBatteryHistoryMap()); createBatteryHistoryMap());
} }
@Ignore
@Test
public void testOnResume_uiModeIsChanged_clearBatteryDiffEntryCache() {
doReturn(mResources).when(mContext).getResources();
doReturn(mConfiguration).when(mResources).getConfiguration();
mConfiguration.uiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
// Ensures the testing environment is correct.
assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
mBatteryChartPreferenceController.onResume();
// Changes the uiMode in the configuration.
mConfiguration.uiMode = Configuration.UI_MODE_NIGHT_YES;
mBatteryChartPreferenceController.onResume();
assertThat(BatteryDiffEntry.sResourceCache).isEmpty();
}
@Ignore
@Test
public void testOnResume_uiModeIsNotChanged_notClearBatteryDiffEntryCache() {
doReturn(mResources).when(mContext).getResources();
doReturn(mConfiguration).when(mResources).getConfiguration();
mConfiguration.uiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
// Ensures the testing environment is correct.
assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
mBatteryChartPreferenceController.onResume();
assertThat(BatteryDiffEntry.sResourceCache).isNotEmpty();
verify(mMetricsFeatureProvider)
.action(mContext, SettingsEnums.OPEN_BATTERY_USAGE);
}
@Test @Test
public void testOnDestroy_activityIsChanging_clearBatteryEntryCache() { public void testOnDestroy_activityIsChanging_clearBatteryEntryCache() {
doReturn(true).when(mSettingsActivity).isChangingConfigurations(); doReturn(true).when(mSettingsActivity).isChangingConfigurations();
@@ -579,12 +546,12 @@ public final class BatteryChartPreferenceControllerTest {
// Verifies the title in the preference group. // Verifies the title in the preference group.
verify(mBatteryChartPreferenceController.mAppListPrefGroup) verify(mBatteryChartPreferenceController.mAppListPrefGroup)
.setTitle(captor.capture()); .setTitle(captor.capture());
assertThat(captor.getValue()).isEqualTo("App usage for 4 - 7"); assertThat(captor.getValue()).isNotEqualTo("App usage for past 24 hr");
// Verifies the title in the expandable divider. // Verifies the title in the expandable divider.
captor = ArgumentCaptor.forClass(String.class); captor = ArgumentCaptor.forClass(String.class);
verify(mBatteryChartPreferenceController.mExpandDividerPreference) verify(mBatteryChartPreferenceController.mExpandDividerPreference)
.setTitle(captor.capture()); .setTitle(captor.capture());
assertThat(captor.getValue()).isEqualTo("System usage for 4 - 7"); assertThat(captor.getValue()).isNotEqualTo("System usage for past 24 hr");
} }
@Test @Test
@@ -722,9 +689,6 @@ public final class BatteryChartPreferenceControllerTest {
new long[] {1619196786769L, 0L, 1619247636826L}; new long[] {1619196786769L, 0L, 1619247636826L};
ConvertUtils.utcToLocalTimeHour( ConvertUtils.utcToLocalTimeHour(
mContext, /*timestamp=*/ 0, /*is24HourFormat=*/ false); mContext, /*timestamp=*/ 0, /*is24HourFormat=*/ false);
// Simulates the locale in GMT.
ConvertUtils.sSimpleDateFormatForHour
.setTimeZone(TimeZone.getTimeZone("GMT"));
} }
private BatteryChartPreferenceController createController() { private BatteryChartPreferenceController createController() {

View File

@@ -33,7 +33,6 @@ import android.view.accessibility.AccessibilityManager;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
@@ -44,7 +43,6 @@ import org.robolectric.RuntimeEnvironment;
import java.util.Arrays; import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public final class BatteryChartViewTest { public final class BatteryChartViewTest {
@@ -233,21 +231,4 @@ public final class BatteryChartViewTest {
verify(mBatteryChartView.mHandler) verify(mBatteryChartView.mHandler)
.postDelayed(mBatteryChartView.mUpdateClickableStateRun, 500L); .postDelayed(mBatteryChartView.mUpdateClickableStateRun, 500L);
} }
@Ignore
@Test
public void testSetLatestTimestamp_generateExpectedTimestamps() {
final long timestamp = 1619196786769L;
ConvertUtils.sSimpleDateFormatForHour = null;
// Invokes the method first to create the SimpleDateFormat.
ConvertUtils.utcToLocalTimeHour(
mContext, /*timestamp=*/ 0, /*is24HourFormat=*/ false);
ConvertUtils.sSimpleDateFormatForHour
.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
final String[] expectedTimestamps = new String[] {"00", "06", "12", "18", "00"};
mBatteryChartView.setLatestTimestamp(timestamp);
assertThat(mBatteryChartView.mTimestamps).isEqualTo(expectedTimestamps);
}
} }

View File

@@ -30,7 +30,6 @@ import android.os.UserHandle;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
@@ -317,71 +316,6 @@ public final class ConvertUtilsTest {
.isEqualTo(entry.mConsumePower * ratio); .isEqualTo(entry.mConsumePower * ratio);
} }
@Ignore
@Test
public void testUtcToLocalTime_returnExpectedResult() {
ConvertUtils.sZoneId = null;
ConvertUtils.sLocale = null;
final long timestamp = 1619196786769L;
final String expectedZoneId = "America/Los_Angeles";
ConvertUtils.sSimpleDateFormat = null;
// Invokes the method first to create the SimpleDateFormat.
ConvertUtils.utcToLocalTime(mContext, /*timestamp=*/ 0);
ConvertUtils.sSimpleDateFormat
.setTimeZone(TimeZone.getTimeZone(expectedZoneId));
mContext.getResources().getConfiguration().setLocales(
new LocaleList(new Locale("en_US")));
assertThat(ConvertUtils.utcToLocalTime(mContext, timestamp))
.isEqualTo("Apr 24,2021 00:53:06");
assertThat(ConvertUtils.sZoneId).isNotEqualTo(expectedZoneId);
assertThat(ConvertUtils.sLocale).isEqualTo(new Locale("en_US"));
}
@Ignore
@Test
public void testUtcToLocalTimeHour_12HourFormat_returnExpectedResult() {
ConvertUtils.sZoneIdForHour = null;
ConvertUtils.sLocaleForHour = null;
final long timestamp = 1619000086769L;
final String expectedZoneId = "America/Los_Angeles";
ConvertUtils.sSimpleDateFormatForHour = null;
// Invokes the method first to create the SimpleDateFormat.
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(
mContext, timestamp, /*is24HourFormat=*/ false)).isEqualTo("6");
assertThat(ConvertUtils.sZoneIdForHour).isNotEqualTo(expectedZoneId);
assertThat(ConvertUtils.sLocaleForHour).isEqualTo(new Locale("en_US"));
}
@Ignore
@Test
public void testUtcToLocalTimeHour_24HourFormat_returnExpectedResult() {
ConvertUtils.sZoneIdForHour = null;
ConvertUtils.sLocaleForHour = null;
final long timestamp = 1619000086769L;
final String expectedZoneId = "America/Los_Angeles";
ConvertUtils.sSimpleDateFormatForHour = null;
// Invokes the method first to create the SimpleDateFormat.
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(
mContext, timestamp, /*is24HourFormat=*/ true)).isEqualTo("18");
assertThat(ConvertUtils.sZoneIdForHour).isNotEqualTo(expectedZoneId);
assertThat(ConvertUtils.sLocaleForHour).isEqualTo(new Locale("en_US"));
}
@Test @Test
public void getLocale_nullContext_returnDefaultLocale() { public void getLocale_nullContext_returnDefaultLocale() {
assertThat(ConvertUtils.getLocale(/*context=*/ null)) assertThat(ConvertUtils.getLocale(/*context=*/ null))