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
This commit is contained in:
@@ -28,14 +28,21 @@ import android.graphics.CornerPathEffect;
|
|||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
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 android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.appcompat.widget.AppCompatImageView;
|
import androidx.appcompat.widget.AppCompatImageView;
|
||||||
|
|
||||||
@@ -77,6 +84,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
private Paint mDividerPaint;
|
private Paint mDividerPaint;
|
||||||
private Paint mTrapezoidPaint;
|
private Paint mTrapezoidPaint;
|
||||||
private Paint mTextPaint;
|
private Paint mTextPaint;
|
||||||
|
private AccessibilityNodeProvider mAccessibilityNodeProvider;
|
||||||
private BatteryChartView.OnSelectListener mOnSelectListener;
|
private BatteryChartView.OnSelectListener mOnSelectListener;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -200,10 +208,23 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
if (mHoveredIndex != trapezoidIndex) {
|
if (mHoveredIndex != trapezoidIndex) {
|
||||||
mHoveredIndex = trapezoidIndex;
|
mHoveredIndex = trapezoidIndex;
|
||||||
invalidate();
|
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
|
@Override
|
||||||
@@ -221,21 +242,53 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
Log.w(TAG, "invalid motion event for onClick() callback");
|
Log.w(TAG, "invalid motion event for onClick() callback");
|
||||||
return;
|
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.
|
// Ignores the click event if the level is zero.
|
||||||
if (trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID
|
if (!isValidToDraw(mViewModel, index)) {
|
||||||
|| !isValidToDraw(mViewModel, trapezoidIndex)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mOnSelectListener != null) {
|
if (mOnSelectListener != null) {
|
||||||
// Selects all if users click the same trapezoid item two times.
|
// Selects all if users click the same trapezoid item two times.
|
||||||
mOnSelectListener.onSelect(
|
mOnSelectListener.onSelect(
|
||||||
trapezoidIndex == mViewModel.selectedIndex()
|
index == mViewModel.selectedIndex()
|
||||||
? BatteryChartViewModel.SELECTED_INDEX_ALL : trapezoidIndex);
|
? BatteryChartViewModel.SELECTED_INDEX_ALL : index);
|
||||||
}
|
}
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
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) {
|
private void initializeTrapezoidSlots(int count) {
|
||||||
mTrapezoidSlots = new TrapezoidSlot[count];
|
mTrapezoidSlots = new TrapezoidSlot[count];
|
||||||
for (int index = 0; index < mTrapezoidSlots.length; index++) {
|
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;
|
&& 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
|
return viewModel != null
|
||||||
&& trapezoidIndex >= 0
|
&& 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);
|
&& isTrapezoidValid(viewModel, trapezoidIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,6 +597,63 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
formatPercentage(/*percentage=*/ 0, /*round=*/ true)};
|
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.
|
// A container class for each trapezoid left and right location.
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final class TrapezoidSlot {
|
static final class TrapezoidSlot {
|
||||||
|
|||||||
Reference in New Issue
Block a user