From 9c962b03e9f23afbb1b274a35993e383fa3e0582 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Mon, 22 Aug 2022 14:09:24 +0800 Subject: [PATCH 1/4] Support accessibility for battery chart (1) Remove the logic of disabling clickable when accessability is on in battery chartview. Bug: 242989585 Test: manual Change-Id: I92ce0ff5aac5220d686d600dbdf1d5738fe2c385 Merged-In: I92ce0ff5aac5220d686d600dbdf1d5738fe2c385 --- .../batteryusage/BatteryChartView.java | 153 ++---------------- .../batteryusage/BatteryChartViewTest.java | 152 ----------------- 2 files changed, 12 insertions(+), 293 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java index b51eacb90d2..fc6daf70c8b 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 com.android.settings.Utils.formatPercentage; 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,13 +28,11 @@ import android.graphics.CornerPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; -import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; -import android.view.accessibility.AccessibilityManager; import android.widget.TextView; import androidx.annotation.NonNull; @@ -43,20 +40,15 @@ 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 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 +59,31 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick 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 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 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 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); @@ -261,66 +236,6 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick 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); - } - invalidate(); - } - - @Override - public void setClickable(boolean clickable) { - super.setClickable(mIsSlotsClickabled && clickable); - } - - @VisibleForTesting - void setClickableForce(boolean clickable) { - super.setClickable(clickable); - } - private void initializeTrapezoidSlots(int count) { mTrapezoidSlots = new TrapezoidSlot[count]; for (int index = 0; index < mTrapezoidSlots.length; index++) { @@ -545,19 +460,14 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick 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( @@ -574,22 +484,6 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick 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; } } @@ -645,29 +539,6 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick 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 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; - } - } - } - return false; - } - // A container class for each trapezoid left and right location. @VisibleForTesting static final class TrapezoidSlot { 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 8a430875614..7e423e0ccab 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 @@ package com.android.settings.fuelgauge.batteryusage; 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.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -53,10 +45,6 @@ public final class BatteryChartViewTest { private FakeFeatureFactory mFeatureFactory; private PowerUsageFeatureProvider mPowerUsageFeatureProvider; - @Mock - private AccessibilityServiceInfo mMockAccessibilityServiceInfo; - @Mock - private AccessibilityManager mMockAccessibilityManager; @Mock private View mMockView; @@ -69,34 +57,6 @@ public final class BatteryChartViewTest { 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()) - .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 @@ -130,116 +90,4 @@ public final class BatteryChartViewTest { 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()) - .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 levels = new ArrayList(); - final List texts = new ArrayList(); - 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); - } } From 266ddbf9b41d8de23ad3c52049db14a881574dff Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Mon, 22 Aug 2022 16:02:46 +0800 Subject: [PATCH 2/4] Support accessibility for battery chart (2) Support virtual accessbility children in battery usage chart. Please see the following screen record with sound: VoiceAccess by name: https://drive.google.com/file/d/15pEEU0OJsyCRbqR4nkALlIgHue4KVEvL/view?usp=sharing&resourcekey=0-ed-u-IWSDlODiYyJIEmVng VoiceAccess by number: https://drive.google.com/file/d/1mBNjbpPGsw4nYU_krG8283RVPaYGZMO3/view?usp=sharing&resourcekey=0-3aIhbcCzJuEpsbDkaPAcWg SwitchAccess: https://drive.google.com/file/d/1rr8sHMGCbP0kglsp7rWwOVQV5kcgEZHa/view?usp=sharing&resourcekey=0-GW2525dHtzDWvzS2uhu8Yg TalkBack: https://drive.google.com/file/d/1daxwHQE3BwySuSIptvO9wCJwnjVehsLE/view?usp=sharing&resourcekey=0-DWo0TuhAfz_9Qaf9_orIWA MouseConnected: https://drive.google.com/file/d/1DzJq5tJsNneNsRbRIZptXfK1l_wR0Kdz/view?usp=sharing&resourcekey=0-npq7ekR1glpofEKMRcJzFQ The following is the orignal broken behaviors: Original VoiceAccess: https://drive.google.com/file/d/1FtQJoVVWnq2xZyUaxW5_h1o0y7jTm9zd/view?usp=sharing&resourcekey=0-BVfk0nzpC2RSx9vGKmfogQ Original TalkBack: https://drive.google.com/file/d/1jMuDo8Lu0uGRSm3OWVBCbm7lXVJnpMn4/view?usp=sharing&resourcekey=0-ozUs4bN14fMPrbvHUtogpw Bug: 242989585 Fix: 242989585 Test: manual Change-Id: I18fe63f75c2438e80b244050608a7ccb2b52c37b --- .../batteryusage/BatteryChartView.java | 133 ++++++++++++++++-- 1 file changed, 124 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java index fc6daf70c8b..40e3167c68b 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java @@ -28,14 +28,21 @@ import android.graphics.CornerPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; +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; @@ -77,6 +84,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick private Paint mDividerPaint; private Paint mTrapezoidPaint; private Paint mTextPaint; + private AccessibilityNodeProvider mAccessibilityNodeProvider; private BatteryChartView.OnSelectListener mOnSelectListener; @VisibleForTesting @@ -200,10 +208,23 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick 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 @@ -221,21 +242,53 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick 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); } + private boolean sendAccessibilityEvent(int virtualDescendantId, int eventType) { + ViewParent parent = getParent(); + if (parent == null || !AccessibilityManager.getInstance(mContext).isEnabled()) { + return false; + } + AccessibilityEvent accessibilityEvent = new AccessibilityEvent(eventType); + accessibilityEvent.setSource(this, virtualDescendantId); + accessibilityEvent.setEnabled(true); + accessibilityEvent.setClassName(getAccessibilityClassName()); + accessibilityEvent.setPackageName(getContext().getPackageName()); + return parent.requestSendAccessibilityEvent(this, accessibilityEvent); + } + + private void sendAccessibilityEventForHover(int eventType) { + if (isTrapezoidIndexValid(mViewModel, mHoveredIndex)) { + sendAccessibilityEvent(mHoveredIndex, eventType); + } + } + private void initializeTrapezoidSlots(int count) { mTrapezoidSlots = new TrapezoidSlot[count]; for (int index = 0; index < mTrapezoidSlots.length; index++) { @@ -515,10 +568,15 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick && viewModel.levels().get(trapezoidIndex + 1) != null; } - private static boolean isValidToDraw(BatteryChartViewModel viewModel, int trapezoidIndex) { + private static boolean isTrapezoidIndexValid( + @NonNull BatteryChartViewModel viewModel, int trapezoidIndex) { return viewModel != null && trapezoidIndex >= 0 - && trapezoidIndex < viewModel.size() - 1 + && trapezoidIndex < viewModel.size() - 1; + } + + private static boolean isValidToDraw(BatteryChartViewModel viewModel, int trapezoidIndex) { + return isTrapezoidIndexValid(viewModel, trapezoidIndex) && isTrapezoidValid(viewModel, trapezoidIndex); } @@ -539,6 +597,63 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick formatPercentage(/*percentage=*/ 0, /*round=*/ 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.texts().get(index)); + childInfo.setContentDescription(mViewModel.texts().get(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); + } + } + } + // A container class for each trapezoid left and right location. @VisibleForTesting static final class TrapezoidSlot { From 79311805b63e2fa592cd9bde0ef96d4ae7ae2316 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Mon, 29 Aug 2022 18:04:14 +0800 Subject: [PATCH 3/4] 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 --- .../BatteryChartPreferenceController.java | 90 ++++++++++--------- .../batteryusage/BatteryChartView.java | 16 ++-- .../batteryusage/BatteryChartViewModel.java | 72 +++++++++++---- .../BatteryChartPreferenceControllerTest.java | 69 +++++++++++--- .../batteryusage/BatteryChartViewTest.java | 4 +- 5 files changed, 167 insertions(+), 84 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index 6d2c1a1232d..64d7b1c36b9 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -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 mDailyTimestampFullTexts; private BatteryChartViewModel mDailyViewModel; private List 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 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 generateTimestampDayOfWeekTexts(@NonNull final Context context, - @NonNull final List timestamps, final boolean isAbbreviation) { - final ArrayList texts = new ArrayList<>(); - for (Long timestamp : timestamps) { - texts.add(ConvertUtils.utcToLocalTimeDayOfWeek(context, timestamp, isAbbreviation)); - } - return texts; - } - - private static List generateTimestampHourTexts( - @NonNull final Context context, @NonNull final List timestamps) { - final boolean is24HourFormat = DateFormat.is24HourFormat(context); - final ArrayList texts = new ArrayList<>(); - for (Long timestamp : timestamps) { - texts.add(ConvertUtils.utcToLocalTimeHour(context, timestamp, is24HourFormat)); - } - return texts; - } - /** Used for {@link AppBatteryPreferenceController}. */ public static List 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 timestamps, int index) { + return ConvertUtils.utcToLocalTimeDayOfWeek(mContext, + timestamps.get(index), /* isAbbreviation= */ true); + } + + @Override + public String generateFullText(List timestamps, int index) { + return ConvertUtils.utcToLocalTimeDayOfWeek(mContext, + timestamps.get(index), /* isAbbreviation= */ false); + } + } + + private final class HourlyChartLabelTextGenerator implements + BatteryChartViewModel.LabelTextGenerator { + @Override + public String generateText(List timestamps, int index) { + return ConvertUtils.utcToLocalTimeHour(mContext, timestamps.get(index), + mIs24HourFormat); + } + + @Override + public String generateFullText(List 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 40e3167c68b..f84ced76d88 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java @@ -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); diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java index ac01bfd645b..f58d2415e19 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java @@ -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 timestamps, int index); + + /** Generate the full text for accessibility. */ + String generateFullText(List timestamps, int index); + } + private final List mLevels; - private final List mTexts; + private final List 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 levels, @NonNull List texts, - @NonNull AxisLabelPosition axisLabelPosition) { + BatteryChartViewModel(@NonNull List levels, @NonNull List 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 levels() { - return mLevels; + public Integer getLevel(int index) { + return mLevels.get(index); } - public List 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(); } } 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 20af849dcde..26e0f5074ed 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 @@ package com.android.settings.fuelgauge.batteryusage; 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 @@ public final class BatteryChartPreferenceControllerTest { @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 @@ public final class BatteryChartPreferenceControllerTest { 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 @@ public final class BatteryChartPreferenceControllerTest { 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 @@ public final class BatteryChartPreferenceControllerTest { 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 7e423e0ccab..52131996e5e 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewTest.java @@ -63,8 +63,8 @@ public final class BatteryChartViewTest { 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++) { From 525fd2dc3bb96958c841469eec5875e3416f82fb Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Thu, 1 Sep 2022 14:47:51 +0800 Subject: [PATCH 4/4] Support accessibility for battery chart (4) According to accessibility suggestion, when users double clicked a time slot in battery usage chart with TalkBack on, jump the accessibility focus to the app list category title to let users know what happened after click. screen record: https://drive.google.com/file/d/13vvA2Il5lz9kvHegH9Tmbq9hJYFGVwJJ/view?usp=sharing&resourcekey=0-g3A29rVsd4NU37SwBD1uzQ Bug: 242989585 Fix: 242989585 Test: manual Change-Id: I8ab3b5f1364247121e43b0b8d51e8aa3743b5c2b --- .../BatteryChartPreferenceController.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index 64d7b1c36b9..9a634e04bb8 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -28,6 +28,7 @@ import android.text.format.DateFormat; 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; @@ -106,6 +107,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll private boolean mIs24HourFormat; private boolean mIsFooterPrefAdded = false; private View mBatteryChartViewGroup; + private View mCategoryTitleView; private PreferenceScreen mPreferenceScreen; private FooterPreference mFooterPreference; private BatteryChartViewModel mDailyViewModel; @@ -327,6 +329,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mDailyChartIndex = trapezoidIndex; mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; refreshUi(); + requestAccessibilityFocusForCategoryTitle(mDailyChartView); mMetricsFeatureProvider.action( mPrefContext, trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL @@ -342,6 +345,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll Log.d(TAG, "onHourlyChartSelect:" + trapezoidIndex); mHourlyChartIndex = trapezoidIndex; refreshUi(); + requestAccessibilityFocusForCategoryTitle(mHourlyChartView); mMetricsFeatureProvider.action( mPrefContext, trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL @@ -525,6 +529,18 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } } + 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.