From fccd144930105b69fa8fd80773d85b578ebe222b Mon Sep 17 00:00:00 2001 From: Joshua McCloskey Date: Mon, 12 Sep 2022 21:00:58 +0000 Subject: [PATCH 1/6] Add phone, tablet, device fp deletion strings. Test: Manual. Fixes: 245411037 Change-Id: Ib1d83072878ede27758beb0381a12cab6172b9d6 --- res/values/strings.xml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 12a343959c1..4447619316b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1476,10 +1476,18 @@ Do you want to delete this fingerprint? - This deletes the fingerprint images and model associated with \'%1$s\' that are stored on your device + This deletes the fingerprint images and model associated with \'%1$s\' that are stored on your phone + + This deletes the fingerprint images and model associated with \'%1$s\' that are stored on your tablet + + This deletes the fingerprint images and model associated with \'%1$s\' that are stored on your device - You won\'t be able to use your fingerprint to unlock your phone or verify it\'s you in apps. + You won\'t be able to use your fingerprint to unlock your phone or verify it\'s you in apps. + + You won\'t be able to use your fingerprint to unlock your tablet or verify it\'s you in apps. + + You won\'t be able to use your fingerprint to unlock your device or verify it\'s you in apps. You won\'t be able to use your fingerprint to unlock your work profile, authorize purchases, or sign in to work apps. From 9c962b03e9f23afbb1b274a35993e383fa3e0582 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Mon, 22 Aug 2022 14:09:24 +0800 Subject: [PATCH 2/6] 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 3/6] 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 4/6] 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 5/6] 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. From 676bcedb856f1650b313a338c3c8d81a377a5bd0 Mon Sep 17 00:00:00 2001 From: Kuan Wang Date: Thu, 8 Sep 2022 12:13:27 +0800 Subject: [PATCH 6/6] Add logging for count of shown / hidden apps in Battery Usage page. Bug: 245455490 Test: manually Change-Id: I7342e822f00f9a0aedcef592cae9809251518812 --- .../fuelgauge/batteryusage/DataProcessor.java | 26 +++++++++++++ .../batteryusage/DataProcessorTest.java | 38 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java index 125f879abff..f493eceb206 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java @@ -18,6 +18,7 @@ package com.android.settings.fuelgauge.batteryusage; import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTime; +import android.app.settings.SettingsEnums; import android.content.ContentValues; import android.content.Context; import android.os.AsyncTask; @@ -36,6 +37,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.settings.Utils; import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.fuelgauge.BatteryStatus; import java.time.Duration; @@ -354,10 +356,25 @@ public final class DataProcessor { insertDailyUsageDiffData(hourlyBatteryLevelsPerDay, resultMap); // Insert diff data [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]. insertAllUsageDiffData(resultMap); + // Compute the apps number before purge. Must put before purgeLowPercentageAndFakeData. + final int countOfAppBeforePurge = getCountOfApps(resultMap); purgeLowPercentageAndFakeData(context, resultMap); + // Compute the apps number after purge. Must put after purgeLowPercentageAndFakeData. + final int countOfAppAfterPurge = getCountOfApps(resultMap); if (!isUsageMapValid(resultMap, hourlyBatteryLevelsPerDay)) { return null; } + + final MetricsFeatureProvider metricsFeatureProvider = + FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + metricsFeatureProvider.action( + context, + SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, + countOfAppAfterPurge); + metricsFeatureProvider.action( + context, + SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, + countOfAppBeforePurge - countOfAppAfterPurge); return resultMap; } @@ -933,6 +950,15 @@ public final class DataProcessor { return calendar.getTimeInMillis(); } + private static int getCountOfApps(final Map> resultMap) { + final BatteryDiffData diffDataList = + resultMap.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL); + return diffDataList == null + ? 0 + : diffDataList.getAppDiffEntryList().size() + + diffDataList.getSystemDiffEntryList().size(); + } + private static boolean contains(String target, Set packageNames) { if (target != null && packageNames != null) { for (CharSequence packageName : packageNames) { diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java index 883b0e7db91..84f9310a28a 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java @@ -18,9 +18,12 @@ package com.android.settings.fuelgauge.batteryusage; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.settings.SettingsEnums; import android.content.ContentValues; import android.content.Context; import android.text.format.DateUtils; @@ -28,6 +31,7 @@ import android.text.format.DateUtils; import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Test; @@ -52,6 +56,7 @@ public class DataProcessorTest { private Context mContext; private FakeFeatureFactory mFeatureFactory; + private MetricsFeatureProvider mMetricsFeatureProvider; private PowerUsageFeatureProvider mPowerUsageFeatureProvider; @Before @@ -61,6 +66,7 @@ public class DataProcessorTest { mContext = spy(RuntimeEnvironment.application); mFeatureFactory = FakeFeatureFactory.setupForTest(); + mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider; mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider; } @@ -75,6 +81,10 @@ public class DataProcessorTest { assertThat(DataProcessor.getBatteryLevelData( mContext, /*handler=*/ null, new HashMap<>(), /*asyncResponseDelegate=*/ null)) .isNull(); + verify(mMetricsFeatureProvider, never()) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT); + verify(mMetricsFeatureProvider, never()) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT); } @Test @@ -88,6 +98,10 @@ public class DataProcessorTest { assertThat(DataProcessor.getBatteryLevelData( mContext, /*handler=*/ null, batteryHistoryMap, /*asyncResponseDelegate=*/ null)) .isNull(); + verify(mMetricsFeatureProvider, never()) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT); + verify(mMetricsFeatureProvider, never()) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT); } @Test @@ -421,6 +435,10 @@ public class DataProcessorTest { assertThat(DataProcessor.getBatteryUsageMap( mContext, hourlyBatteryLevelsPerDay, new HashMap<>())).isNull(); + verify(mMetricsFeatureProvider, never()) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT); + verify(mMetricsFeatureProvider, never()) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT); } @Test @@ -549,6 +567,10 @@ public class DataProcessorTest { resultDiffData.getSystemDiffEntryList().get(0), currentUserId, /*uid=*/ 3L, ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*consumePercentage=*/ 25.0, /*foregroundUsageTimeInMs=*/ 50, /*backgroundUsageTimeInMs=*/ 60); + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, 3); + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, 0); } @Test @@ -640,6 +662,10 @@ public class DataProcessorTest { /*backgroundUsageTimeInMs=*/ 0); assertThat(resultMap.get(0).get(0)).isNotNull(); assertThat(resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL)).isNotNull(); + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, 2); + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, 0); } @Test @@ -701,6 +727,10 @@ public class DataProcessorTest { .isEqualTo(entry.mConsumePower * ratio); assertThat(resultMap.get(0).get(0)).isNotNull(); assertThat(resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL)).isNotNull(); + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, 1); + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, 0); } @Test @@ -772,6 +802,10 @@ public class DataProcessorTest { resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 2L, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 50.0, /*foregroundUsageTimeInMs=*/ 10, /*backgroundUsageTimeInMs=*/ 20); + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, 1); + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, 1); } @Test @@ -843,6 +877,10 @@ public class DataProcessorTest { assertThat(resultEntry.mBackgroundUsageTimeInMs).isEqualTo(20); resultEntry = resultDiffData.getAppDiffEntryList().get(1); assertThat(resultEntry.mBackgroundUsageTimeInMs).isEqualTo(0); + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, 2); + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, 0); } private static Map> createHistoryMap(