Support accessibility for battery chart (3)

Support accessibilty read out full timestamp labels instead of the short one, e.g "Sunday" instead of "Sun".

Bug: 242989585
Test: manual
Change-Id: Ica2176ef3f07849d278327b9301f8c318782c2d5
This commit is contained in:
Zaiyue Xue
2022-08-29 18:04:14 +08:00
parent 266ddbf9b4
commit 79311805b6
5 changed files with 167 additions and 84 deletions

View File

@@ -108,10 +108,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
private View mBatteryChartViewGroup;
private PreferenceScreen mPreferenceScreen;
private FooterPreference mFooterPreference;
// Daily view model only saves abbreviated day of week texts (e.g. MON). This field saves the
// full day of week texts (e.g. Monday), which is used in category title and battery detail
// page.
private List<String> mDailyTimestampFullTexts;
private BatteryChartViewModel mDailyViewModel;
private List<BatteryChartViewModel> mHourlyViewModels;
@@ -122,6 +118,13 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@VisibleForTesting
final DailyChartLabelTextGenerator mDailyChartLabelTextGenerator =
new DailyChartLabelTextGenerator();
@VisibleForTesting
final HourlyChartLabelTextGenerator mHourlyChartLabelTextGenerator =
new HourlyChartLabelTextGenerator();
// Preference cache to avoid create new instance each time.
@VisibleForTesting
final Map<String, Preference> mPreferenceCache = new HashMap<>();
@@ -279,29 +282,24 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
getTotalHours(batteryLevelData));
if (batteryLevelData == null) {
mDailyTimestampFullTexts = null;
mDailyViewModel = null;
mHourlyViewModels = null;
refreshUi();
return;
}
mDailyTimestampFullTexts = generateTimestampDayOfWeekTexts(
mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(),
/* isAbbreviation= */ false);
mDailyViewModel = new BatteryChartViewModel(
batteryLevelData.getDailyBatteryLevels().getLevels(),
generateTimestampDayOfWeekTexts(
mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(),
/* isAbbreviation= */ true),
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS);
batteryLevelData.getDailyBatteryLevels().getTimestamps(),
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS,
mDailyChartLabelTextGenerator);
mHourlyViewModels = new ArrayList<>();
for (BatteryLevelData.PeriodBatteryLevelData hourlyBatteryLevelsPerDay :
batteryLevelData.getHourlyBatteryLevelsPerDay()) {
mHourlyViewModels.add(new BatteryChartViewModel(
hourlyBatteryLevelsPerDay.getLevels(),
generateTimestampHourTexts(
mContext, hourlyBatteryLevelsPerDay.getTimestamps()),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
hourlyBatteryLevelsPerDay.getTimestamps(),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
mHourlyChartLabelTextGenerator));
}
refreshUi();
}
@@ -543,8 +541,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
@VisibleForTesting
String getSlotInformation() {
if (mDailyTimestampFullTexts == null || mDailyViewModel == null
|| mHourlyViewModels == null) {
if (mDailyViewModel == null || mHourlyViewModels == null) {
// No data
return null;
}
@@ -552,17 +549,13 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
return null;
}
final String selectedDayText = mDailyTimestampFullTexts.get(mDailyChartIndex);
final String selectedDayText = mDailyViewModel.getFullText(mDailyChartIndex);
if (mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
return selectedDayText;
}
final String fromHourText = mHourlyViewModels.get(mDailyChartIndex).texts().get(
final String selectedHourText = mHourlyViewModels.get(mDailyChartIndex).getFullText(
mHourlyChartIndex);
final String toHourText = mHourlyViewModels.get(mDailyChartIndex).texts().get(
mHourlyChartIndex + 1);
final String selectedHourText =
String.format("%s%s%s", fromHourText, mIs24HourFormat ? "-" : " - ", toHourText);
if (isBatteryLevelDataInOneDay()) {
return selectedHourText;
}
@@ -663,25 +656,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
/ DateUtils.HOUR_IN_MILLIS);
}
private static List<String> generateTimestampDayOfWeekTexts(@NonNull final Context context,
@NonNull final List<Long> timestamps, final boolean isAbbreviation) {
final ArrayList<String> texts = new ArrayList<>();
for (Long timestamp : timestamps) {
texts.add(ConvertUtils.utcToLocalTimeDayOfWeek(context, timestamp, isAbbreviation));
}
return texts;
}
private static List<String> generateTimestampHourTexts(
@NonNull final Context context, @NonNull final List<Long> timestamps) {
final boolean is24HourFormat = DateFormat.is24HourFormat(context);
final ArrayList<String> texts = new ArrayList<>();
for (Long timestamp : timestamps) {
texts.add(ConvertUtils.utcToLocalTimeHour(context, timestamp, is24HourFormat));
}
return texts;
}
/** Used for {@link AppBatteryPreferenceController}. */
public static List<BatteryDiffEntry> getAppBatteryUsageData(Context context) {
final long start = System.currentTimeMillis();
@@ -727,4 +701,36 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
return null;
}
private final class DailyChartLabelTextGenerator implements
BatteryChartViewModel.LabelTextGenerator {
@Override
public String generateText(List<Long> timestamps, int index) {
return ConvertUtils.utcToLocalTimeDayOfWeek(mContext,
timestamps.get(index), /* isAbbreviation= */ true);
}
@Override
public String generateFullText(List<Long> timestamps, int index) {
return ConvertUtils.utcToLocalTimeDayOfWeek(mContext,
timestamps.get(index), /* isAbbreviation= */ false);
}
}
private final class HourlyChartLabelTextGenerator implements
BatteryChartViewModel.LabelTextGenerator {
@Override
public String generateText(List<Long> timestamps, int index) {
return ConvertUtils.utcToLocalTimeHour(mContext, timestamps.get(index),
mIs24HourFormat);
}
@Override
public String generateFullText(List<Long> timestamps, int index) {
return index == timestamps.size() - 1
? generateText(timestamps, index)
: String.format("%s%s%s", generateText(timestamps, index),
mIs24HourFormat ? "-" : " - ", generateText(timestamps, index + 1));
}
}
}

View File

@@ -158,7 +158,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
if (mViewModel != null) {
int maxTop = 0;
for (int index = 0; index < mViewModel.size(); index++) {
final String text = mViewModel.texts().get(index);
final String text = mViewModel.getText(index);
mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds.get(index));
maxTop = Math.max(maxTop, -mAxisLabelsBounds.get(index).top);
}
@@ -490,7 +490,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
Canvas canvas, final int index, final Rect displayArea, final float baselineY) {
mTextPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(
mViewModel.texts().get(index),
mViewModel.getText(index),
displayArea.centerX(),
baselineY,
mTextPaint);
@@ -524,9 +524,9 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
mTrapezoidPaint.setColor(isHoverState ? mTrapezoidHoverColor : trapezoidColor);
final float leftTop = round(
trapezoidBottom - requireNonNull(mViewModel.levels().get(index)) * unitHeight);
trapezoidBottom - requireNonNull(mViewModel.getLevel(index)) * unitHeight);
final float rightTop = round(trapezoidBottom
- requireNonNull(mViewModel.levels().get(index + 1)) * unitHeight);
- requireNonNull(mViewModel.getLevel(index + 1)) * unitHeight);
trapezoidPath.reset();
trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom);
trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
@@ -564,8 +564,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private static boolean isTrapezoidValid(
@NonNull BatteryChartViewModel viewModel, int trapezoidIndex) {
return viewModel.levels().get(trapezoidIndex) != null
&& viewModel.levels().get(trapezoidIndex + 1) != null;
return viewModel.getLevel(trapezoidIndex) != null
&& viewModel.getLevel(trapezoidIndex + 1) != null;
}
private static boolean isTrapezoidIndexValid(
@@ -617,8 +617,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
new AccessibilityNodeInfo(BatteryChartView.this, index);
onInitializeAccessibilityNodeInfo(childInfo);
childInfo.setClickable(isValidToDraw(mViewModel, index));
childInfo.setText(mViewModel.texts().get(index));
childInfo.setContentDescription(mViewModel.texts().get(index));
childInfo.setText(mViewModel.getFullText(index));
childInfo.setContentDescription(mViewModel.getFullText(index));
final Rect bounds = new Rect();
getBoundsOnScreen(bounds, true);

View File

@@ -19,6 +19,7 @@ package com.android.settings.fuelgauge.batteryusage;
import androidx.annotation.NonNull;
import androidx.core.util.Preconditions;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -38,34 +39,59 @@ class BatteryChartViewModel {
CENTER_OF_TRAPEZOIDS,
}
interface LabelTextGenerator {
/** Generate the label text. The text may be abbreviated to save space. */
String generateText(List<Long> timestamps, int index);
/** Generate the full text for accessibility. */
String generateFullText(List<Long> timestamps, int index);
}
private final List<Integer> mLevels;
private final List<String> mTexts;
private final List<Long> mTimestamps;
private final AxisLabelPosition mAxisLabelPosition;
private final LabelTextGenerator mLabelTextGenerator;
private final String[] mTexts;
private final String[] mFullTexts;
private int mSelectedIndex = SELECTED_INDEX_ALL;
BatteryChartViewModel(
@NonNull List<Integer> levels, @NonNull List<String> texts,
@NonNull AxisLabelPosition axisLabelPosition) {
BatteryChartViewModel(@NonNull List<Integer> levels, @NonNull List<Long> timestamps,
@NonNull AxisLabelPosition axisLabelPosition,
@NonNull LabelTextGenerator labelTextGenerator) {
Preconditions.checkArgument(
levels.size() == texts.size() && levels.size() >= MIN_LEVELS_DATA_SIZE,
levels.size() == timestamps.size() && levels.size() >= MIN_LEVELS_DATA_SIZE,
String.format(Locale.ENGLISH,
"Invalid BatteryChartViewModel levels.size: %d, texts.size: %d.",
levels.size(), texts.size()));
"Invalid BatteryChartViewModel levels.size: %d, timestamps.size: %d.",
levels.size(), timestamps.size()));
mLevels = levels;
mTexts = texts;
mTimestamps = timestamps;
mAxisLabelPosition = axisLabelPosition;
mLabelTextGenerator = labelTextGenerator;
mTexts = new String[size()];
mFullTexts = new String[size()];
}
public int size() {
return mLevels.size();
}
public List<Integer> levels() {
return mLevels;
public Integer getLevel(int index) {
return mLevels.get(index);
}
public List<String> texts() {
return mTexts;
public String getText(int index) {
if (mTexts[index] == null) {
mTexts[index] = mLabelTextGenerator.generateText(mTimestamps, index);
}
return mTexts[index];
}
public String getFullText(int index) {
if (mFullTexts[index] == null) {
mFullTexts[index] = mLabelTextGenerator.generateFullText(mTimestamps, index);
}
return mFullTexts[index];
}
public AxisLabelPosition axisLabelPosition() {
@@ -82,7 +108,7 @@ class BatteryChartViewModel {
@Override
public int hashCode() {
return Objects.hash(mLevels, mTexts, mSelectedIndex, mAxisLabelPosition);
return Objects.hash(mLevels, mTimestamps, mSelectedIndex, mAxisLabelPosition);
}
@Override
@@ -94,16 +120,26 @@ class BatteryChartViewModel {
}
final BatteryChartViewModel batteryChartViewModel = (BatteryChartViewModel) other;
return Objects.equals(mLevels, batteryChartViewModel.mLevels)
&& Objects.equals(mTexts, batteryChartViewModel.mTexts)
&& Objects.equals(mTimestamps, batteryChartViewModel.mTimestamps)
&& mAxisLabelPosition == batteryChartViewModel.mAxisLabelPosition
&& mSelectedIndex == batteryChartViewModel.mSelectedIndex;
}
@Override
public String toString() {
return String.format(Locale.ENGLISH,
"levels: %s,\ntexts: %s,\naxisLabelPosition: %s, selectedIndex: %d",
Objects.toString(mLevels), Objects.toString(mTexts), mAxisLabelPosition,
mSelectedIndex);
// Generate all the texts and full texts.
for (int i = 0; i < size(); i++) {
getText(i);
getFullText(i);
}
return new StringBuilder()
.append("levels: " + Objects.toString(mLevels))
.append(", timestamps: " + Objects.toString(mTimestamps))
.append(", texts: " + Arrays.toString(mTexts))
.append(", fullTexts: " + Arrays.toString(mFullTexts))
.append(", axisLabelPosition: " + mAxisLabelPosition)
.append(", selectedIndex: " + mSelectedIndex)
.toString();
}
}