Merge changes from topic "accessibility1" into tm-qpr-dev
* changes:
Support accessibility for battery chart (4)
Support accessibility for battery chart (3)
Support accessibility for battery chart (2)
Support accessibility for battery chart (1)
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index 26379eb..79f0880 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -30,6 +30,7 @@
import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
+import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -107,12 +108,9 @@
private boolean mIs24HourFormat;
private boolean mIsFooterPrefAdded = false;
private View mBatteryChartViewGroup;
+ private View mCategoryTitleView;
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;
@@ -127,6 +125,13 @@
private final AnimatorListenerAdapter mHourlyChartFadeOutAdapter =
createHourlyChartAnimatorListenerAdapter(/*isToShow=*/ false);
+ @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<>();
@@ -284,29 +289,24 @@
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();
}
@@ -334,6 +334,7 @@
mDailyChartIndex = trapezoidIndex;
mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
refreshUi();
+ requestAccessibilityFocusForCategoryTitle(mDailyChartView);
mMetricsFeatureProvider.action(
mPrefContext,
trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
@@ -349,6 +350,7 @@
Log.d(TAG, "onHourlyChartSelect:" + trapezoidIndex);
mHourlyChartIndex = trapezoidIndex;
refreshUi();
+ requestAccessibilityFocusForCategoryTitle(mHourlyChartView);
mMetricsFeatureProvider.action(
mPrefContext,
trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
@@ -532,6 +534,18 @@
}
}
+ private void requestAccessibilityFocusForCategoryTitle(View view) {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return;
+ }
+ if (mCategoryTitleView == null) {
+ mCategoryTitleView = view.getRootView().findViewById(com.android.internal.R.id.title);
+ }
+ if (mCategoryTitleView != null) {
+ mCategoryTitleView.requestAccessibilityFocus();
+ }
+ }
+
private String getSlotInformation(boolean isApp, String slotInformation) {
// TODO: Updates the right slot information from daily and hourly chart selection.
// Null means we show all information without a specific time slot.
@@ -548,8 +562,7 @@
@VisibleForTesting
String getSlotInformation() {
- if (mDailyTimestampFullTexts == null || mDailyViewModel == null
- || mHourlyViewModels == null) {
+ if (mDailyViewModel == null || mHourlyViewModels == null) {
// No data
return null;
}
@@ -557,17 +570,13 @@
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;
}
@@ -712,25 +721,6 @@
/ 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();
@@ -776,4 +766,36 @@
}
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));
+ }
+ }
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
index b51eacb..f84ced7 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
@@ -20,7 +20,6 @@
import static java.lang.Math.round;
import static java.util.Objects.requireNonNull;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -29,34 +28,34 @@
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
-import android.os.Handler;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.widget.AppCompatImageView;
import com.android.settings.R;
-import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Locale;
/** A widget component to draw chart graph. */
-public class BatteryChartView extends AppCompatImageView implements View.OnClickListener,
- AccessibilityManager.AccessibilityStateChangeListener {
+public class BatteryChartView extends AppCompatImageView implements View.OnClickListener {
private static final String TAG = "BatteryChartView";
- private static final List<String> ACCESSIBILITY_SERVICE_NAMES =
- Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService");
private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
private static final long UPDATE_STATE_DELAYED_TIME = 500L;
@@ -67,48 +66,32 @@
void onSelect(int trapezoidIndex);
}
- private BatteryChartViewModel mViewModel;
+ private final String[] mPercentages = getPercentages();
+ private final Rect mIndent = new Rect();
+ private final Rect[] mPercentageBounds = new Rect[]{new Rect(), new Rect(), new Rect()};
+ private final List<Rect> mAxisLabelsBounds = new ArrayList<>();
+ private BatteryChartViewModel mViewModel;
+ private int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
private int mDividerWidth;
private int mDividerHeight;
private float mTrapezoidVOffset;
private float mTrapezoidHOffset;
- private boolean mIsSlotsClickabled;
- private String[] mPercentages = getPercentages();
-
- @VisibleForTesting
- int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
-
- // Colors for drawing the trapezoid shape and dividers.
private int mTrapezoidColor;
private int mTrapezoidSolidColor;
private int mTrapezoidHoverColor;
- // For drawing the percentage information.
private int mTextPadding;
- private final Rect mIndent = new Rect();
- private final Rect[] mPercentageBounds =
- new Rect[]{new Rect(), new Rect(), new Rect()};
- // For drawing the axis label information.
- private final List<Rect> mAxisLabelsBounds = new ArrayList<>();
-
-
- @VisibleForTesting
- Handler mHandler = new Handler();
- @VisibleForTesting
- final Runnable mUpdateClickableStateRun = () -> updateClickableState();
-
- private Paint mTextPaint;
private Paint mDividerPaint;
private Paint mTrapezoidPaint;
+ private Paint mTextPaint;
+ private AccessibilityNodeProvider mAccessibilityNodeProvider;
+ private BatteryChartView.OnSelectListener mOnSelectListener;
@VisibleForTesting
- Paint mTrapezoidCurvePaint = null;
- @VisibleForTesting
TrapezoidSlot[] mTrapezoidSlots;
// Records the location to calculate selected index.
@VisibleForTesting
float mTouchUpEventX = Float.MIN_VALUE;
- private BatteryChartView.OnSelectListener mOnSelectListener;
public BatteryChartView(Context context) {
super(context, null);
@@ -175,7 +158,7 @@
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);
}
@@ -225,10 +208,23 @@
if (mHoveredIndex != trapezoidIndex) {
mHoveredIndex = trapezoidIndex;
invalidate();
+ sendAccessibilityEventForHover(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
}
- break;
+ // Ignore the super.onHoverEvent() because the hovered trapezoid has already been
+ // sent here.
+ return true;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ if (mHoveredIndex != BatteryChartViewModel.SELECTED_INDEX_INVALID) {
+ sendAccessibilityEventForHover(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset
+ invalidate();
+ }
+ // Ignore the super.onHoverEvent() because the hovered trapezoid has already been
+ // sent here.
+ return true;
+ default:
+ return super.onTouchEvent(event);
}
- return super.onHoverEvent(event);
}
@Override
@@ -246,79 +242,51 @@
Log.w(TAG, "invalid motion event for onClick() callback");
return;
}
- final int trapezoidIndex = getTrapezoidIndex(mTouchUpEventX);
+ onTrapezoidClicked(view, getTrapezoidIndex(mTouchUpEventX));
+ }
+
+ @Override
+ public AccessibilityNodeProvider getAccessibilityNodeProvider() {
+ if (mViewModel == null) {
+ return super.getAccessibilityNodeProvider();
+ }
+ if (mAccessibilityNodeProvider == null) {
+ mAccessibilityNodeProvider = new BatteryChartAccessibilityNodeProvider();
+ }
+ return mAccessibilityNodeProvider;
+ }
+
+ private void onTrapezoidClicked(View view, int index) {
// Ignores the click event if the level is zero.
- if (trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID
- || !isValidToDraw(mViewModel, trapezoidIndex)) {
+ if (!isValidToDraw(mViewModel, index)) {
return;
}
if (mOnSelectListener != null) {
// Selects all if users click the same trapezoid item two times.
mOnSelectListener.onSelect(
- trapezoidIndex == mViewModel.selectedIndex()
- ? BatteryChartViewModel.SELECTED_INDEX_ALL : trapezoidIndex);
+ index == mViewModel.selectedIndex()
+ ? BatteryChartViewModel.SELECTED_INDEX_ALL : index);
}
view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
}
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- updateClickableState();
- mContext.getSystemService(AccessibilityManager.class)
- .addAccessibilityStateChangeListener(/*listener=*/ this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mContext.getSystemService(AccessibilityManager.class)
- .removeAccessibilityStateChangeListener(/*listener=*/ this);
- mHandler.removeCallbacks(mUpdateClickableStateRun);
- }
-
- @Override
- public void onAccessibilityStateChanged(boolean enabled) {
- Log.d(TAG, "onAccessibilityStateChanged:" + enabled);
- mHandler.removeCallbacks(mUpdateClickableStateRun);
- // We should delay it a while since accessibility manager will spend
- // some times to bind with new enabled accessibility services.
- mHandler.postDelayed(
- mUpdateClickableStateRun, UPDATE_STATE_DELAYED_TIME);
- }
-
- private void updateClickableState() {
- final Context context = mContext;
- mIsSlotsClickabled =
- FeatureFactory.getFactory(context)
- .getPowerUsageFeatureProvider(context)
- .isChartGraphSlotsEnabled(context)
- && !isAccessibilityEnabled(context);
- Log.d(TAG, "isChartGraphSlotsEnabled:" + mIsSlotsClickabled);
- setClickable(isClickable());
- // Initializes the trapezoid curve paint for non-clickable case.
- if (!mIsSlotsClickabled && mTrapezoidCurvePaint == null) {
- mTrapezoidCurvePaint = new Paint();
- mTrapezoidCurvePaint.setAntiAlias(true);
- mTrapezoidCurvePaint.setColor(mTrapezoidSolidColor);
- mTrapezoidCurvePaint.setStyle(Paint.Style.STROKE);
- mTrapezoidCurvePaint.setStrokeWidth(mDividerWidth * 2);
- } else if (mIsSlotsClickabled) {
- mTrapezoidCurvePaint = null;
- // Sets view model again to force update the click state.
- setViewModel(mViewModel);
+ private boolean sendAccessibilityEvent(int virtualDescendantId, int eventType) {
+ ViewParent parent = getParent();
+ if (parent == null || !AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return false;
}
- invalidate();
+ AccessibilityEvent accessibilityEvent = new AccessibilityEvent(eventType);
+ accessibilityEvent.setSource(this, virtualDescendantId);
+ accessibilityEvent.setEnabled(true);
+ accessibilityEvent.setClassName(getAccessibilityClassName());
+ accessibilityEvent.setPackageName(getContext().getPackageName());
+ return parent.requestSendAccessibilityEvent(this, accessibilityEvent);
}
- @Override
- public void setClickable(boolean clickable) {
- super.setClickable(mIsSlotsClickabled && clickable);
- }
-
- @VisibleForTesting
- void setClickableForce(boolean clickable) {
- super.setClickable(clickable);
+ private void sendAccessibilityEventForHover(int eventType) {
+ if (isTrapezoidIndexValid(mViewModel, mHoveredIndex)) {
+ sendAccessibilityEvent(mHoveredIndex, eventType);
+ }
}
private void initializeTrapezoidSlots(int count) {
@@ -522,7 +490,7 @@
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);
@@ -545,25 +513,20 @@
for (int index = 0; index < mTrapezoidSlots.length; index++) {
// Not draws the trapezoid for corner or not initialization cases.
if (!isValidToDraw(mViewModel, index)) {
- if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) {
- canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint);
- trapezoidCurvePath = null;
- }
continue;
}
// Configures the trapezoid paint color.
- final int trapezoidColor = mIsSlotsClickabled && (mViewModel.selectedIndex() == index
+ final int trapezoidColor = (mViewModel.selectedIndex() == index
|| mViewModel.selectedIndex() == BatteryChartViewModel.SELECTED_INDEX_ALL)
? mTrapezoidSolidColor : mTrapezoidColor;
- final boolean isHoverState =
- mIsSlotsClickabled && mHoveredIndex == index
- && isValidToDraw(mViewModel, mHoveredIndex);
+ final boolean isHoverState = mHoveredIndex == index && isValidToDraw(mViewModel,
+ mHoveredIndex);
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);
@@ -574,22 +537,6 @@
trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
// Draws the trapezoid shape into canvas.
canvas.drawPath(trapezoidPath, mTrapezoidPaint);
-
- // Generates path for non-clickable trapezoid curve.
- if (mTrapezoidCurvePaint != null) {
- if (trapezoidCurvePath == null) {
- trapezoidCurvePath = new Path();
- trapezoidCurvePath.moveTo(mTrapezoidSlots[index].mLeft, leftTop);
- } else {
- trapezoidCurvePath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
- }
- trapezoidCurvePath.lineTo(mTrapezoidSlots[index].mRight, rightTop);
- }
- }
- // Draws the trapezoid curve for non-clickable case.
- if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) {
- canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint);
- trapezoidCurvePath = null;
}
}
@@ -617,14 +564,19 @@
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(
+ @NonNull BatteryChartViewModel viewModel, int trapezoidIndex) {
+ return viewModel != null
+ && trapezoidIndex >= 0
+ && trapezoidIndex < viewModel.size() - 1;
}
private static boolean isValidToDraw(BatteryChartViewModel viewModel, int trapezoidIndex) {
- return viewModel != null
- && trapezoidIndex >= 0
- && trapezoidIndex < viewModel.size() - 1
+ return isTrapezoidIndexValid(viewModel, trapezoidIndex)
&& isTrapezoidValid(viewModel, trapezoidIndex);
}
@@ -645,27 +597,61 @@
formatPercentage(/*percentage=*/ 0, /*round=*/ true)};
}
- @VisibleForTesting
- static boolean isAccessibilityEnabled(Context context) {
- final AccessibilityManager accessibilityManager =
- context.getSystemService(AccessibilityManager.class);
- if (!accessibilityManager.isEnabled()) {
- return false;
- }
- final List<AccessibilityServiceInfo> serviceInfoList =
- accessibilityManager.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_SPOKEN
- | AccessibilityServiceInfo.FEEDBACK_GENERIC);
- for (AccessibilityServiceInfo info : serviceInfoList) {
- for (String serviceName : ACCESSIBILITY_SERVICE_NAMES) {
- final String serviceId = info.getId();
- if (serviceId != null && serviceId.contains(serviceName)) {
- Log.d(TAG, "acccessibilityEnabled:" + serviceId);
- return true;
+ private class BatteryChartAccessibilityNodeProvider extends AccessibilityNodeProvider {
+ @Override
+ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+ if (virtualViewId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+ final AccessibilityNodeInfo hostInfo =
+ new AccessibilityNodeInfo(BatteryChartView.this);
+ for (int index = 0; index < mViewModel.size() - 1; index++) {
+ hostInfo.addChild(BatteryChartView.this, index);
}
+ return hostInfo;
+ }
+ final int index = virtualViewId;
+ if (!isTrapezoidIndexValid(mViewModel, index)) {
+ Log.w(TAG, "Invalid virtual view id:" + index);
+ return null;
+ }
+ final AccessibilityNodeInfo childInfo =
+ new AccessibilityNodeInfo(BatteryChartView.this, index);
+ onInitializeAccessibilityNodeInfo(childInfo);
+ childInfo.setClickable(isValidToDraw(mViewModel, index));
+ childInfo.setText(mViewModel.getFullText(index));
+ childInfo.setContentDescription(mViewModel.getFullText(index));
+
+ final Rect bounds = new Rect();
+ getBoundsOnScreen(bounds, true);
+ final int hostLeft = bounds.left;
+ bounds.left = round(hostLeft + mTrapezoidSlots[index].mLeft);
+ bounds.right = round(hostLeft + mTrapezoidSlots[index].mRight);
+ childInfo.setBoundsInScreen(bounds);
+ return childInfo;
+ }
+
+ @Override
+ public boolean performAction(int virtualViewId, int action,
+ @Nullable Bundle arguments) {
+ if (virtualViewId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+ return performAccessibilityAction(action, arguments);
+ }
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_CLICK:
+ onTrapezoidClicked(BatteryChartView.this, virtualViewId);
+ return true;
+
+ case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
+ return sendAccessibilityEvent(virtualViewId,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+
+ case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+ return sendAccessibilityEvent(virtualViewId,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+
+ default:
+ return performAccessibilityAction(action, arguments);
}
}
- return false;
}
// A container class for each trapezoid left and right location.
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java
index ac01bfd..f58d241 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java
@@ -19,6 +19,7 @@
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 @@
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 @@
@Override
public int hashCode() {
- return Objects.hash(mLevels, mTexts, mSelectedIndex, mAxisLabelPosition);
+ return Objects.hash(mLevels, mTimestamps, mSelectedIndex, mAxisLabelPosition);
}
@Override
@@ -94,16 +120,26 @@
}
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();
}
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java
index 20af849..26e0f50 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -173,22 +174,33 @@
@Test
public void setBatteryChartViewModel_6Hours() {
+ reset(mHourlyChartView);
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
verify(mDailyChartView, atLeastOnce()).setVisibility(View.GONE);
verify(mHourlyChartView, atLeastOnce()).setVisibility(View.VISIBLE);
- verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
+ // Ignore fast refresh ui from the data processor callback.
+ verify(mHourlyChartView, atLeast(0)).setViewModel(null);
+ verify(mHourlyChartView, atLeastOnce()).setViewModel(new BatteryChartViewModel(
List.of(100, 97, 95),
- List.of("8 AM", "10 AM", "12 PM"),
- BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
+ List.of(1619251200000L /* 8 AM */,
+ 1619258400000L /* 10 AM */,
+ 1619265600000L /* 12 PM */),
+ BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
+ mBatteryChartPreferenceController.mHourlyChartLabelTextGenerator));
}
@Test
public void setBatteryChartViewModel_60Hours() {
BatteryChartViewModel expectedDailyViewModel = new BatteryChartViewModel(
List.of(100, 83, 59, 41),
- List.of("Sat", "Sun", "Mon", "Mon"),
- BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS);
+ // "Sat", "Sun", "Mon", "Mon"
+ List.of(1619251200000L /* Sat */,
+ 1619308800000L /* Sun */,
+ 1619395200000L /* Mon */,
+ 1619460000000L /* Mon */),
+ BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS,
+ mBatteryChartPreferenceController.mDailyChartLabelTextGenerator);
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
@@ -208,9 +220,17 @@
verify(mDailyChartView).setViewModel(expectedDailyViewModel);
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(100, 97, 95, 93, 91, 89, 87, 85, 83),
- List.of("8 AM", "10 AM", "12 PM", "2 PM", "4 PM", "6 PM", "8 PM", "10 PM",
- "12 AM"),
- BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
+ List.of(1619251200000L /* 8 AM */,
+ 1619258400000L /* 10 AM */,
+ 1619265600000L /* 12 PM */,
+ 1619272800000L /* 2 PM */,
+ 1619280000000L /* 4 PM */,
+ 1619287200000L /* 6 PM */,
+ 1619294400000L /* 8 PM */,
+ 1619301600000L /* 10 PM */,
+ 1619308800000L /* 12 AM */),
+ BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
+ mBatteryChartPreferenceController.mHourlyChartLabelTextGenerator));
reset(mDailyChartView);
reset(mHourlyChartView);
@@ -224,9 +244,21 @@
verify(mDailyChartView).setViewModel(expectedDailyViewModel);
BatteryChartViewModel expectedHourlyViewModel = new BatteryChartViewModel(
List.of(83, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59),
- List.of("12 AM", "2 AM", "4 AM", "6 AM", "8 AM", "10 AM", "12 PM", "2 PM",
- "4 PM", "6 PM", "8 PM", "10 PM", "12 AM"),
- BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS);
+ List.of(1619308800000L /* 12 AM */,
+ 1619316000000L /* 2 AM */,
+ 1619323200000L /* 4 AM */,
+ 1619330400000L /* 6 AM */,
+ 1619337600000L /* 8 AM */,
+ 1619344800000L /* 10 AM */,
+ 1619352000000L /* 12 PM */,
+ 1619359200000L /* 2 PM */,
+ 1619366400000L /* 4 PM */,
+ 1619373600000L /* 6 PM */,
+ 1619380800000L /* 8 PM */,
+ 1619388000000L /* 10 PM */,
+ 1619395200000L /* 12 AM */),
+ BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
+ mBatteryChartPreferenceController.mHourlyChartLabelTextGenerator);
expectedHourlyViewModel.setSelectedIndex(6);
verify(mHourlyChartView).setViewModel(expectedHourlyViewModel);
@@ -243,9 +275,18 @@
verify(mDailyChartView).setViewModel(expectedDailyViewModel);
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(59, 57, 55, 53, 51, 49, 47, 45, 43, 41),
- List.of("12 AM", "2 AM", "4 AM", "6 AM", "8 AM", "10 AM", "12 PM", "2 PM",
- "4 PM", "6 PM"),
- BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
+ List.of(1619395200000L /* 12 AM */,
+ 1619402400000L /* 2 AM */,
+ 1619409600000L /* 4 AM */,
+ 1619416800000L /* 6 AM */,
+ 1619424000000L /* 8 AM */,
+ 1619431200000L /* 10 AM */,
+ 1619438400000L /* 12 PM */,
+ 1619445600000L /* 2 PM */,
+ 1619452800000L /* 4 PM */,
+ 1619460000000L /* 6 PM */),
+ BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
+ mBatteryChartPreferenceController.mHourlyChartLabelTextGenerator));
}
@Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewTest.java
index 8a43087..5213199 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewTest.java
@@ -17,17 +17,11 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.os.LocaleList;
import android.view.View;
-import android.view.accessibility.AccessibilityManager;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.testutils.FakeFeatureFactory;
@@ -40,8 +34,6 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@@ -54,10 +46,6 @@
private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
@Mock
- private AccessibilityServiceInfo mMockAccessibilityServiceInfo;
- @Mock
- private AccessibilityManager mMockAccessibilityManager;
- @Mock
private View mMockView;
@Before
@@ -69,42 +57,14 @@
mContext.getResources().getConfiguration().setLocales(
new LocaleList(new Locale("en_US")));
mBatteryChartView = new BatteryChartView(mContext);
- doReturn(mMockAccessibilityManager).when(mContext)
- .getSystemService(AccessibilityManager.class);
- doReturn("TalkBackService").when(mMockAccessibilityServiceInfo).getId();
- doReturn(Arrays.asList(mMockAccessibilityServiceInfo))
- .when(mMockAccessibilityManager)
- .getEnabledAccessibilityServiceList(anyInt());
- }
-
- @Test
- public void isAccessibilityEnabled_disable_returnFalse() {
- doReturn(false).when(mMockAccessibilityManager).isEnabled();
- assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isFalse();
- }
-
- @Test
- public void isAccessibilityEnabled_emptyInfo_returnFalse() {
- doReturn(true).when(mMockAccessibilityManager).isEnabled();
- doReturn(new ArrayList<AccessibilityServiceInfo>())
- .when(mMockAccessibilityManager)
- .getEnabledAccessibilityServiceList(anyInt());
-
- assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isFalse();
- }
-
- @Test
- public void isAccessibilityEnabled_validServiceId_returnTrue() {
- doReturn(true).when(mMockAccessibilityManager).isEnabled();
- assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isTrue();
}
@Test
public void onClick_invokesCallback() {
final int originalSelectedIndex = 2;
BatteryChartViewModel batteryChartViewModel = new BatteryChartViewModel(
- List.of(90, 80, 70, 60), List.of("", "", "", ""),
- BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS);
+ List.of(90, 80, 70, 60), List.of(0L, 0L, 0L, 0L),
+ BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS, null);
batteryChartViewModel.setSelectedIndex(originalSelectedIndex);
mBatteryChartView.setViewModel(batteryChartViewModel);
for (int i = 0; i < mBatteryChartView.mTrapezoidSlots.length; i++) {
@@ -130,116 +90,4 @@
mBatteryChartView.onClick(mMockView);
assertThat(selectedIndex[0]).isEqualTo(BatteryChartViewModel.SELECTED_INDEX_ALL);
}
-
- @Test
- public void clickable_isChartGraphSlotsEnabledIsFalse_notClickable() {
- mBatteryChartView.setClickableForce(true);
- when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
- .thenReturn(false);
-
- mBatteryChartView.onAttachedToWindow();
-
- assertThat(mBatteryChartView.isClickable()).isFalse();
- assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
- }
-
- @Test
- public void clickable_accessibilityIsDisabled_clickable() {
- mBatteryChartView.setClickableForce(true);
- when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
- .thenReturn(true);
- doReturn(false).when(mMockAccessibilityManager).isEnabled();
-
- mBatteryChartView.onAttachedToWindow();
-
- assertThat(mBatteryChartView.isClickable()).isTrue();
- assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
- }
-
- @Test
- public void clickable_accessibilityIsEnabledWithoutValidId_clickable() {
- mBatteryChartView.setClickableForce(true);
- when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
- .thenReturn(true);
- doReturn(true).when(mMockAccessibilityManager).isEnabled();
- doReturn(new ArrayList<AccessibilityServiceInfo>())
- .when(mMockAccessibilityManager)
- .getEnabledAccessibilityServiceList(anyInt());
-
- mBatteryChartView.onAttachedToWindow();
-
- assertThat(mBatteryChartView.isClickable()).isTrue();
- assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
- }
-
- @Test
- public void clickable_accessibilityIsEnabledWithValidId_notClickable() {
- mBatteryChartView.setClickableForce(true);
- when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
- .thenReturn(true);
- doReturn(true).when(mMockAccessibilityManager).isEnabled();
-
- mBatteryChartView.onAttachedToWindow();
-
- assertThat(mBatteryChartView.isClickable()).isFalse();
- assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
- }
-
- @Test
- public void clickable_restoreFromNonClickableState() {
- final List<Integer> levels = new ArrayList<Integer>();
- final List<String> texts = new ArrayList<String>();
- for (int index = 0; index < 13; index++) {
- levels.add(index + 1);
- texts.add("");
- }
- mBatteryChartView.setViewModel(new BatteryChartViewModel(levels, texts,
- BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
- mBatteryChartView.setClickableForce(true);
- when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
- .thenReturn(true);
- doReturn(true).when(mMockAccessibilityManager).isEnabled();
- mBatteryChartView.onAttachedToWindow();
- // Ensures the testing environment is correct.
- assertThat(mBatteryChartView.isClickable()).isFalse();
- // Turns off accessibility service.
- doReturn(false).when(mMockAccessibilityManager).isEnabled();
-
- mBatteryChartView.onAttachedToWindow();
-
- assertThat(mBatteryChartView.isClickable()).isTrue();
- }
-
- @Test
- public void onAttachedToWindow_addAccessibilityStateChangeListener() {
- mBatteryChartView.onAttachedToWindow();
- verify(mMockAccessibilityManager)
- .addAccessibilityStateChangeListener(mBatteryChartView);
- }
-
- @Test
- public void onDetachedFromWindow_removeAccessibilityStateChangeListener() {
- mBatteryChartView.onAttachedToWindow();
- mBatteryChartView.mHandler.postDelayed(
- mBatteryChartView.mUpdateClickableStateRun, 1000);
-
- mBatteryChartView.onDetachedFromWindow();
-
- verify(mMockAccessibilityManager)
- .removeAccessibilityStateChangeListener(mBatteryChartView);
- assertThat(mBatteryChartView.mHandler.hasCallbacks(
- mBatteryChartView.mUpdateClickableStateRun))
- .isFalse();
- }
-
- @Test
- public void onAccessibilityStateChanged_postUpdateStateRunnable() {
- mBatteryChartView.mHandler = spy(mBatteryChartView.mHandler);
- mBatteryChartView.onAccessibilityStateChanged(/*enabled=*/ true);
-
- verify(mBatteryChartView.mHandler)
- .removeCallbacks(mBatteryChartView.mUpdateClickableStateRun);
- verify(mBatteryChartView.mHandler)
- .postDelayed(mBatteryChartView.mUpdateClickableStateRun, 500L);
- }
}