Merge changes from topic "accessibility1" into tm-qpr-dev

* changes:
  Support accessibility for battery chart (4)
  Support accessibility for battery chart (3)
  Support accessibility for battery chart (2)
  Support accessibility for battery chart (1)
This commit is contained in:
Zaiyue Xue
2022-09-14 05:46:17 +00:00
committed by Android (Google) Code Review
5 changed files with 307 additions and 374 deletions

View File

@@ -30,6 +30,7 @@ import android.text.format.DateFormat;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
@@ -107,12 +108,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
private boolean mIs24HourFormat; private boolean mIs24HourFormat;
private boolean mIsFooterPrefAdded = false; private boolean mIsFooterPrefAdded = false;
private View mBatteryChartViewGroup; private View mBatteryChartViewGroup;
private View mCategoryTitleView;
private PreferenceScreen mPreferenceScreen; private PreferenceScreen mPreferenceScreen;
private FooterPreference mFooterPreference; private FooterPreference mFooterPreference;
// Daily view model only saves abbreviated day of week texts (e.g. MON). This field saves the
// full day of week texts (e.g. Monday), which is used in category title and battery detail
// page.
private List<String> mDailyTimestampFullTexts;
private BatteryChartViewModel mDailyViewModel; private BatteryChartViewModel mDailyViewModel;
private List<BatteryChartViewModel> mHourlyViewModels; private List<BatteryChartViewModel> mHourlyViewModels;
@@ -127,6 +125,13 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
private final AnimatorListenerAdapter mHourlyChartFadeOutAdapter = private final AnimatorListenerAdapter mHourlyChartFadeOutAdapter =
createHourlyChartAnimatorListenerAdapter(/*isToShow=*/ false); createHourlyChartAnimatorListenerAdapter(/*isToShow=*/ false);
@VisibleForTesting
final DailyChartLabelTextGenerator mDailyChartLabelTextGenerator =
new DailyChartLabelTextGenerator();
@VisibleForTesting
final HourlyChartLabelTextGenerator mHourlyChartLabelTextGenerator =
new HourlyChartLabelTextGenerator();
// Preference cache to avoid create new instance each time. // Preference cache to avoid create new instance each time.
@VisibleForTesting @VisibleForTesting
final Map<String, Preference> mPreferenceCache = new HashMap<>(); final Map<String, Preference> mPreferenceCache = new HashMap<>();
@@ -284,29 +289,24 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
getTotalHours(batteryLevelData)); getTotalHours(batteryLevelData));
if (batteryLevelData == null) { if (batteryLevelData == null) {
mDailyTimestampFullTexts = null;
mDailyViewModel = null; mDailyViewModel = null;
mHourlyViewModels = null; mHourlyViewModels = null;
refreshUi(); refreshUi();
return; return;
} }
mDailyTimestampFullTexts = generateTimestampDayOfWeekTexts(
mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(),
/* isAbbreviation= */ false);
mDailyViewModel = new BatteryChartViewModel( mDailyViewModel = new BatteryChartViewModel(
batteryLevelData.getDailyBatteryLevels().getLevels(), batteryLevelData.getDailyBatteryLevels().getLevels(),
generateTimestampDayOfWeekTexts( batteryLevelData.getDailyBatteryLevels().getTimestamps(),
mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(), BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS,
/* isAbbreviation= */ true), mDailyChartLabelTextGenerator);
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS);
mHourlyViewModels = new ArrayList<>(); mHourlyViewModels = new ArrayList<>();
for (BatteryLevelData.PeriodBatteryLevelData hourlyBatteryLevelsPerDay : for (BatteryLevelData.PeriodBatteryLevelData hourlyBatteryLevelsPerDay :
batteryLevelData.getHourlyBatteryLevelsPerDay()) { batteryLevelData.getHourlyBatteryLevelsPerDay()) {
mHourlyViewModels.add(new BatteryChartViewModel( mHourlyViewModels.add(new BatteryChartViewModel(
hourlyBatteryLevelsPerDay.getLevels(), hourlyBatteryLevelsPerDay.getLevels(),
generateTimestampHourTexts( hourlyBatteryLevelsPerDay.getTimestamps(),
mContext, hourlyBatteryLevelsPerDay.getTimestamps()), BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS)); mHourlyChartLabelTextGenerator));
} }
refreshUi(); refreshUi();
} }
@@ -334,6 +334,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
mDailyChartIndex = trapezoidIndex; mDailyChartIndex = trapezoidIndex;
mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
refreshUi(); refreshUi();
requestAccessibilityFocusForCategoryTitle(mDailyChartView);
mMetricsFeatureProvider.action( mMetricsFeatureProvider.action(
mPrefContext, mPrefContext,
trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
@@ -349,6 +350,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
Log.d(TAG, "onHourlyChartSelect:" + trapezoidIndex); Log.d(TAG, "onHourlyChartSelect:" + trapezoidIndex);
mHourlyChartIndex = trapezoidIndex; mHourlyChartIndex = trapezoidIndex;
refreshUi(); refreshUi();
requestAccessibilityFocusForCategoryTitle(mHourlyChartView);
mMetricsFeatureProvider.action( mMetricsFeatureProvider.action(
mPrefContext, mPrefContext,
trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
@@ -532,6 +534,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) { private String getSlotInformation(boolean isApp, String slotInformation) {
// TODO: Updates the right slot information from daily and hourly chart selection. // TODO: Updates the right slot information from daily and hourly chart selection.
// Null means we show all information without a specific time slot. // Null means we show all information without a specific time slot.
@@ -548,8 +562,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
@VisibleForTesting @VisibleForTesting
String getSlotInformation() { String getSlotInformation() {
if (mDailyTimestampFullTexts == null || mDailyViewModel == null if (mDailyViewModel == null || mHourlyViewModels == null) {
|| mHourlyViewModels == null) {
// No data // No data
return null; return null;
} }
@@ -557,17 +570,13 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
return null; return null;
} }
final String selectedDayText = mDailyTimestampFullTexts.get(mDailyChartIndex); final String selectedDayText = mDailyViewModel.getFullText(mDailyChartIndex);
if (mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) { if (mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
return selectedDayText; return selectedDayText;
} }
final String fromHourText = mHourlyViewModels.get(mDailyChartIndex).texts().get( final String selectedHourText = mHourlyViewModels.get(mDailyChartIndex).getFullText(
mHourlyChartIndex); mHourlyChartIndex);
final String toHourText = mHourlyViewModels.get(mDailyChartIndex).texts().get(
mHourlyChartIndex + 1);
final String selectedHourText =
String.format("%s%s%s", fromHourText, mIs24HourFormat ? "-" : " - ", toHourText);
if (isBatteryLevelDataInOneDay()) { if (isBatteryLevelDataInOneDay()) {
return selectedHourText; return selectedHourText;
} }
@@ -712,25 +721,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
/ DateUtils.HOUR_IN_MILLIS); / DateUtils.HOUR_IN_MILLIS);
} }
private static List<String> generateTimestampDayOfWeekTexts(@NonNull final Context context,
@NonNull final List<Long> timestamps, final boolean isAbbreviation) {
final ArrayList<String> texts = new ArrayList<>();
for (Long timestamp : timestamps) {
texts.add(ConvertUtils.utcToLocalTimeDayOfWeek(context, timestamp, isAbbreviation));
}
return texts;
}
private static List<String> generateTimestampHourTexts(
@NonNull final Context context, @NonNull final List<Long> timestamps) {
final boolean is24HourFormat = DateFormat.is24HourFormat(context);
final ArrayList<String> texts = new ArrayList<>();
for (Long timestamp : timestamps) {
texts.add(ConvertUtils.utcToLocalTimeHour(context, timestamp, is24HourFormat));
}
return texts;
}
/** Used for {@link AppBatteryPreferenceController}. */ /** Used for {@link AppBatteryPreferenceController}. */
public static List<BatteryDiffEntry> getAppBatteryUsageData(Context context) { public static List<BatteryDiffEntry> getAppBatteryUsageData(Context context) {
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();
@@ -776,4 +766,36 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
} }
return null; return null;
} }
private final class DailyChartLabelTextGenerator implements
BatteryChartViewModel.LabelTextGenerator {
@Override
public String generateText(List<Long> timestamps, int index) {
return ConvertUtils.utcToLocalTimeDayOfWeek(mContext,
timestamps.get(index), /* isAbbreviation= */ true);
}
@Override
public String generateFullText(List<Long> timestamps, int index) {
return ConvertUtils.utcToLocalTimeDayOfWeek(mContext,
timestamps.get(index), /* isAbbreviation= */ false);
}
}
private final class HourlyChartLabelTextGenerator implements
BatteryChartViewModel.LabelTextGenerator {
@Override
public String generateText(List<Long> timestamps, int index) {
return ConvertUtils.utcToLocalTimeHour(mContext, timestamps.get(index),
mIs24HourFormat);
}
@Override
public String generateFullText(List<Long> timestamps, int index) {
return index == timestamps.size() - 1
? generateText(timestamps, index)
: String.format("%s%s%s", generateText(timestamps, index),
mIs24HourFormat ? "-" : " - ", generateText(timestamps, index + 1));
}
}
} }

View File

@@ -20,7 +20,6 @@ import static com.android.settings.Utils.formatPercentage;
import static java.lang.Math.round; import static java.lang.Math.round;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Canvas; import android.graphics.Canvas;
@@ -29,34 +28,34 @@ 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.Handler; 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.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;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils; import com.android.settingslib.Utils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
/** A widget component to draw chart graph. */ /** A widget component to draw chart graph. */
public class BatteryChartView extends AppCompatImageView implements View.OnClickListener, public class BatteryChartView extends AppCompatImageView implements View.OnClickListener {
AccessibilityManager.AccessibilityStateChangeListener {
private static final String TAG = "BatteryChartView"; private static final String TAG = "BatteryChartView";
private static final List<String> ACCESSIBILITY_SERVICE_NAMES =
Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService");
private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5"); private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
private static final long UPDATE_STATE_DELAYED_TIME = 500L; private static final long UPDATE_STATE_DELAYED_TIME = 500L;
@@ -67,48 +66,32 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
void onSelect(int trapezoidIndex); void onSelect(int trapezoidIndex);
} }
private BatteryChartViewModel mViewModel; private final String[] mPercentages = getPercentages();
private final Rect mIndent = new Rect();
private final Rect[] mPercentageBounds = new Rect[]{new Rect(), new Rect(), new Rect()};
private final List<Rect> mAxisLabelsBounds = new ArrayList<>();
private BatteryChartViewModel mViewModel;
private int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
private int mDividerWidth; private int mDividerWidth;
private int mDividerHeight; private int mDividerHeight;
private float mTrapezoidVOffset; private float mTrapezoidVOffset;
private float mTrapezoidHOffset; 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 mTrapezoidColor;
private int mTrapezoidSolidColor; private int mTrapezoidSolidColor;
private int mTrapezoidHoverColor; private int mTrapezoidHoverColor;
// For drawing the percentage information.
private int mTextPadding; private int mTextPadding;
private final Rect mIndent = new Rect();
private final Rect[] mPercentageBounds =
new Rect[]{new Rect(), new Rect(), new Rect()};
// For drawing the axis label information.
private final List<Rect> mAxisLabelsBounds = new ArrayList<>();
@VisibleForTesting
Handler mHandler = new Handler();
@VisibleForTesting
final Runnable mUpdateClickableStateRun = () -> updateClickableState();
private Paint mTextPaint;
private Paint mDividerPaint; private Paint mDividerPaint;
private Paint mTrapezoidPaint; private Paint mTrapezoidPaint;
private Paint mTextPaint;
private AccessibilityNodeProvider mAccessibilityNodeProvider;
private BatteryChartView.OnSelectListener mOnSelectListener;
@VisibleForTesting
Paint mTrapezoidCurvePaint = null;
@VisibleForTesting @VisibleForTesting
TrapezoidSlot[] mTrapezoidSlots; TrapezoidSlot[] mTrapezoidSlots;
// Records the location to calculate selected index. // Records the location to calculate selected index.
@VisibleForTesting @VisibleForTesting
float mTouchUpEventX = Float.MIN_VALUE; float mTouchUpEventX = Float.MIN_VALUE;
private BatteryChartView.OnSelectListener mOnSelectListener;
public BatteryChartView(Context context) { public BatteryChartView(Context context) {
super(context, null); super(context, null);
@@ -175,7 +158,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
if (mViewModel != null) { if (mViewModel != null) {
int maxTop = 0; int maxTop = 0;
for (int index = 0; index < mViewModel.size(); index++) { 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)); mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds.get(index));
maxTop = Math.max(maxTop, -mAxisLabelsBounds.get(index).top); maxTop = Math.max(maxTop, -mAxisLabelsBounds.get(index).top);
} }
@@ -225,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
@@ -246,79 +242,51 @@ 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);
} }
@Override private boolean sendAccessibilityEvent(int virtualDescendantId, int eventType) {
public void onAttachedToWindow() { ViewParent parent = getParent();
super.onAttachedToWindow(); if (parent == null || !AccessibilityManager.getInstance(mContext).isEnabled()) {
updateClickableState(); return false;
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(); AccessibilityEvent accessibilityEvent = new AccessibilityEvent(eventType);
accessibilityEvent.setSource(this, virtualDescendantId);
accessibilityEvent.setEnabled(true);
accessibilityEvent.setClassName(getAccessibilityClassName());
accessibilityEvent.setPackageName(getContext().getPackageName());
return parent.requestSendAccessibilityEvent(this, accessibilityEvent);
} }
@Override private void sendAccessibilityEventForHover(int eventType) {
public void setClickable(boolean clickable) { if (isTrapezoidIndexValid(mViewModel, mHoveredIndex)) {
super.setClickable(mIsSlotsClickabled && clickable); sendAccessibilityEvent(mHoveredIndex, eventType);
} }
@VisibleForTesting
void setClickableForce(boolean clickable) {
super.setClickable(clickable);
} }
private void initializeTrapezoidSlots(int count) { private void initializeTrapezoidSlots(int count) {
@@ -522,7 +490,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
Canvas canvas, final int index, final Rect displayArea, final float baselineY) { Canvas canvas, final int index, final Rect displayArea, final float baselineY) {
mTextPaint.setTextAlign(Paint.Align.CENTER); mTextPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText( canvas.drawText(
mViewModel.texts().get(index), mViewModel.getText(index),
displayArea.centerX(), displayArea.centerX(),
baselineY, baselineY,
mTextPaint); mTextPaint);
@@ -545,25 +513,20 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
for (int index = 0; index < mTrapezoidSlots.length; index++) { for (int index = 0; index < mTrapezoidSlots.length; index++) {
// Not draws the trapezoid for corner or not initialization cases. // Not draws the trapezoid for corner or not initialization cases.
if (!isValidToDraw(mViewModel, index)) { if (!isValidToDraw(mViewModel, index)) {
if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) {
canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint);
trapezoidCurvePath = null;
}
continue; continue;
} }
// Configures the trapezoid paint color. // Configures the trapezoid paint color.
final int trapezoidColor = mIsSlotsClickabled && (mViewModel.selectedIndex() == index final int trapezoidColor = (mViewModel.selectedIndex() == index
|| mViewModel.selectedIndex() == BatteryChartViewModel.SELECTED_INDEX_ALL) || mViewModel.selectedIndex() == BatteryChartViewModel.SELECTED_INDEX_ALL)
? mTrapezoidSolidColor : mTrapezoidColor; ? mTrapezoidSolidColor : mTrapezoidColor;
final boolean isHoverState = final boolean isHoverState = mHoveredIndex == index && isValidToDraw(mViewModel,
mIsSlotsClickabled && mHoveredIndex == index mHoveredIndex);
&& isValidToDraw(mViewModel, mHoveredIndex);
mTrapezoidPaint.setColor(isHoverState ? mTrapezoidHoverColor : trapezoidColor); mTrapezoidPaint.setColor(isHoverState ? mTrapezoidHoverColor : trapezoidColor);
final float leftTop = round( final float leftTop = round(
trapezoidBottom - requireNonNull(mViewModel.levels().get(index)) * unitHeight); trapezoidBottom - requireNonNull(mViewModel.getLevel(index)) * unitHeight);
final float rightTop = round(trapezoidBottom final float rightTop = round(trapezoidBottom
- requireNonNull(mViewModel.levels().get(index + 1)) * unitHeight); - requireNonNull(mViewModel.getLevel(index + 1)) * unitHeight);
trapezoidPath.reset(); trapezoidPath.reset();
trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom); trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom);
trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop); trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
@@ -574,22 +537,6 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop); trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
// Draws the trapezoid shape into canvas. // Draws the trapezoid shape into canvas.
canvas.drawPath(trapezoidPath, mTrapezoidPaint); canvas.drawPath(trapezoidPath, mTrapezoidPaint);
// Generates path for non-clickable trapezoid curve.
if (mTrapezoidCurvePaint != null) {
if (trapezoidCurvePath == null) {
trapezoidCurvePath = new Path();
trapezoidCurvePath.moveTo(mTrapezoidSlots[index].mLeft, leftTop);
} else {
trapezoidCurvePath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
}
trapezoidCurvePath.lineTo(mTrapezoidSlots[index].mRight, rightTop);
}
}
// Draws the trapezoid curve for non-clickable case.
if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) {
canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint);
trapezoidCurvePath = null;
} }
} }
@@ -617,14 +564,19 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private static boolean isTrapezoidValid( private static boolean isTrapezoidValid(
@NonNull BatteryChartViewModel viewModel, int trapezoidIndex) { @NonNull BatteryChartViewModel viewModel, int trapezoidIndex) {
return viewModel.levels().get(trapezoidIndex) != null return viewModel.getLevel(trapezoidIndex) != null
&& viewModel.levels().get(trapezoidIndex + 1) != null; && viewModel.getLevel(trapezoidIndex + 1) != null;
}
private static boolean isTrapezoidIndexValid(
@NonNull BatteryChartViewModel viewModel, int trapezoidIndex) {
return viewModel != null
&& trapezoidIndex >= 0
&& trapezoidIndex < viewModel.size() - 1;
} }
private static boolean isValidToDraw(BatteryChartViewModel viewModel, int trapezoidIndex) { private static boolean isValidToDraw(BatteryChartViewModel viewModel, int trapezoidIndex) {
return viewModel != null return isTrapezoidIndexValid(viewModel, trapezoidIndex)
&& trapezoidIndex >= 0
&& trapezoidIndex < viewModel.size() - 1
&& isTrapezoidValid(viewModel, trapezoidIndex); && isTrapezoidValid(viewModel, trapezoidIndex);
} }
@@ -645,27 +597,61 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
formatPercentage(/*percentage=*/ 0, /*round=*/ true)}; formatPercentage(/*percentage=*/ 0, /*round=*/ true)};
} }
@VisibleForTesting private class BatteryChartAccessibilityNodeProvider extends AccessibilityNodeProvider {
static boolean isAccessibilityEnabled(Context context) { @Override
final AccessibilityManager accessibilityManager = public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
context.getSystemService(AccessibilityManager.class); if (virtualViewId == AccessibilityNodeProvider.HOST_VIEW_ID) {
if (!accessibilityManager.isEnabled()) { final AccessibilityNodeInfo hostInfo =
return false; new AccessibilityNodeInfo(BatteryChartView.this);
} for (int index = 0; index < mViewModel.size() - 1; index++) {
final List<AccessibilityServiceInfo> serviceInfoList = hostInfo.addChild(BatteryChartView.this, index);
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 hostInfo;
}
final int index = virtualViewId;
if (!isTrapezoidIndexValid(mViewModel, index)) {
Log.w(TAG, "Invalid virtual view id:" + index);
return null;
}
final AccessibilityNodeInfo childInfo =
new AccessibilityNodeInfo(BatteryChartView.this, index);
onInitializeAccessibilityNodeInfo(childInfo);
childInfo.setClickable(isValidToDraw(mViewModel, index));
childInfo.setText(mViewModel.getFullText(index));
childInfo.setContentDescription(mViewModel.getFullText(index));
final Rect bounds = new Rect();
getBoundsOnScreen(bounds, true);
final int hostLeft = bounds.left;
bounds.left = round(hostLeft + mTrapezoidSlots[index].mLeft);
bounds.right = round(hostLeft + mTrapezoidSlots[index].mRight);
childInfo.setBoundsInScreen(bounds);
return childInfo;
}
@Override
public boolean performAction(int virtualViewId, int action,
@Nullable Bundle arguments) {
if (virtualViewId == AccessibilityNodeProvider.HOST_VIEW_ID) {
return performAccessibilityAction(action, arguments);
}
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK:
onTrapezoidClicked(BatteryChartView.this, virtualViewId);
return true;
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
return sendAccessibilityEvent(virtualViewId,
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
return sendAccessibilityEvent(virtualViewId,
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
default:
return performAccessibilityAction(action, arguments);
} }
} }
return false;
} }
// A container class for each trapezoid left and right location. // A container class for each trapezoid left and right location.

View File

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

View File

@@ -19,6 +19,7 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
@@ -173,22 +174,33 @@ public final class BatteryChartPreferenceControllerTest {
@Test @Test
public void setBatteryChartViewModel_6Hours() { public void setBatteryChartViewModel_6Hours() {
reset(mHourlyChartView);
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6)); mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
verify(mDailyChartView, atLeastOnce()).setVisibility(View.GONE); verify(mDailyChartView, atLeastOnce()).setVisibility(View.GONE);
verify(mHourlyChartView, atLeastOnce()).setVisibility(View.VISIBLE); 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(100, 97, 95),
List.of("8 AM", "10 AM", "12 PM"), List.of(1619251200000L /* 8 AM */,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS)); 1619258400000L /* 10 AM */,
1619265600000L /* 12 PM */),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
mBatteryChartPreferenceController.mHourlyChartLabelTextGenerator));
} }
@Test @Test
public void setBatteryChartViewModel_60Hours() { public void setBatteryChartViewModel_60Hours() {
BatteryChartViewModel expectedDailyViewModel = new BatteryChartViewModel( BatteryChartViewModel expectedDailyViewModel = new BatteryChartViewModel(
List.of(100, 83, 59, 41), List.of(100, 83, 59, 41),
List.of("Sat", "Sun", "Mon", "Mon"), // "Sat", "Sun", "Mon", "Mon"
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS); List.of(1619251200000L /* Sat */,
1619308800000L /* Sun */,
1619395200000L /* Mon */,
1619460000000L /* Mon */),
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS,
mBatteryChartPreferenceController.mDailyChartLabelTextGenerator);
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60)); mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
@@ -208,9 +220,17 @@ public final class BatteryChartPreferenceControllerTest {
verify(mDailyChartView).setViewModel(expectedDailyViewModel); verify(mDailyChartView).setViewModel(expectedDailyViewModel);
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel( verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(100, 97, 95, 93, 91, 89, 87, 85, 83), 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", List.of(1619251200000L /* 8 AM */,
"12 AM"), 1619258400000L /* 10 AM */,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS)); 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(mDailyChartView);
reset(mHourlyChartView); reset(mHourlyChartView);
@@ -224,9 +244,21 @@ public final class BatteryChartPreferenceControllerTest {
verify(mDailyChartView).setViewModel(expectedDailyViewModel); verify(mDailyChartView).setViewModel(expectedDailyViewModel);
BatteryChartViewModel expectedHourlyViewModel = new BatteryChartViewModel( BatteryChartViewModel expectedHourlyViewModel = new BatteryChartViewModel(
List.of(83, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59), 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", List.of(1619308800000L /* 12 AM */,
"4 PM", "6 PM", "8 PM", "10 PM", "12 AM"), 1619316000000L /* 2 AM */,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS); 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); expectedHourlyViewModel.setSelectedIndex(6);
verify(mHourlyChartView).setViewModel(expectedHourlyViewModel); verify(mHourlyChartView).setViewModel(expectedHourlyViewModel);
@@ -243,9 +275,18 @@ public final class BatteryChartPreferenceControllerTest {
verify(mDailyChartView).setViewModel(expectedDailyViewModel); verify(mDailyChartView).setViewModel(expectedDailyViewModel);
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel( verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(59, 57, 55, 53, 51, 49, 47, 45, 43, 41), 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", List.of(1619395200000L /* 12 AM */,
"4 PM", "6 PM"), 1619402400000L /* 2 AM */,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS)); 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 @Test

View File

@@ -17,17 +17,11 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.google.common.truth.Truth.assertThat; 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.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context; import android.content.Context;
import android.os.LocaleList; import android.os.LocaleList;
import android.view.View; import android.view.View;
import android.view.accessibility.AccessibilityManager;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
@@ -40,8 +34,6 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -53,10 +45,6 @@ public final class BatteryChartViewTest {
private FakeFeatureFactory mFeatureFactory; private FakeFeatureFactory mFeatureFactory;
private PowerUsageFeatureProvider mPowerUsageFeatureProvider; private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
@Mock
private AccessibilityServiceInfo mMockAccessibilityServiceInfo;
@Mock
private AccessibilityManager mMockAccessibilityManager;
@Mock @Mock
private View mMockView; private View mMockView;
@@ -69,42 +57,14 @@ public final class BatteryChartViewTest {
mContext.getResources().getConfiguration().setLocales( mContext.getResources().getConfiguration().setLocales(
new LocaleList(new Locale("en_US"))); new LocaleList(new Locale("en_US")));
mBatteryChartView = new BatteryChartView(mContext); mBatteryChartView = new BatteryChartView(mContext);
doReturn(mMockAccessibilityManager).when(mContext)
.getSystemService(AccessibilityManager.class);
doReturn("TalkBackService").when(mMockAccessibilityServiceInfo).getId();
doReturn(Arrays.asList(mMockAccessibilityServiceInfo))
.when(mMockAccessibilityManager)
.getEnabledAccessibilityServiceList(anyInt());
}
@Test
public void isAccessibilityEnabled_disable_returnFalse() {
doReturn(false).when(mMockAccessibilityManager).isEnabled();
assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isFalse();
}
@Test
public void isAccessibilityEnabled_emptyInfo_returnFalse() {
doReturn(true).when(mMockAccessibilityManager).isEnabled();
doReturn(new ArrayList<AccessibilityServiceInfo>())
.when(mMockAccessibilityManager)
.getEnabledAccessibilityServiceList(anyInt());
assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isFalse();
}
@Test
public void isAccessibilityEnabled_validServiceId_returnTrue() {
doReturn(true).when(mMockAccessibilityManager).isEnabled();
assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isTrue();
} }
@Test @Test
public void onClick_invokesCallback() { public void onClick_invokesCallback() {
final int originalSelectedIndex = 2; final int originalSelectedIndex = 2;
BatteryChartViewModel batteryChartViewModel = new BatteryChartViewModel( BatteryChartViewModel batteryChartViewModel = new BatteryChartViewModel(
List.of(90, 80, 70, 60), List.of("", "", "", ""), List.of(90, 80, 70, 60), List.of(0L, 0L, 0L, 0L),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS); BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS, null);
batteryChartViewModel.setSelectedIndex(originalSelectedIndex); batteryChartViewModel.setSelectedIndex(originalSelectedIndex);
mBatteryChartView.setViewModel(batteryChartViewModel); mBatteryChartView.setViewModel(batteryChartViewModel);
for (int i = 0; i < mBatteryChartView.mTrapezoidSlots.length; i++) { for (int i = 0; i < mBatteryChartView.mTrapezoidSlots.length; i++) {
@@ -130,116 +90,4 @@ public final class BatteryChartViewTest {
mBatteryChartView.onClick(mMockView); mBatteryChartView.onClick(mMockView);
assertThat(selectedIndex[0]).isEqualTo(BatteryChartViewModel.SELECTED_INDEX_ALL); assertThat(selectedIndex[0]).isEqualTo(BatteryChartViewModel.SELECTED_INDEX_ALL);
} }
@Test
public void clickable_isChartGraphSlotsEnabledIsFalse_notClickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(false);
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isFalse();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
}
@Test
public void clickable_accessibilityIsDisabled_clickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
doReturn(false).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isTrue();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
}
@Test
public void clickable_accessibilityIsEnabledWithoutValidId_clickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
doReturn(true).when(mMockAccessibilityManager).isEnabled();
doReturn(new ArrayList<AccessibilityServiceInfo>())
.when(mMockAccessibilityManager)
.getEnabledAccessibilityServiceList(anyInt());
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isTrue();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
}
@Test
public void clickable_accessibilityIsEnabledWithValidId_notClickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
doReturn(true).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isFalse();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
}
@Test
public void clickable_restoreFromNonClickableState() {
final List<Integer> levels = new ArrayList<Integer>();
final List<String> texts = new ArrayList<String>();
for (int index = 0; index < 13; index++) {
levels.add(index + 1);
texts.add("");
}
mBatteryChartView.setViewModel(new BatteryChartViewModel(levels, texts,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
doReturn(true).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
// Ensures the testing environment is correct.
assertThat(mBatteryChartView.isClickable()).isFalse();
// Turns off accessibility service.
doReturn(false).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isTrue();
}
@Test
public void onAttachedToWindow_addAccessibilityStateChangeListener() {
mBatteryChartView.onAttachedToWindow();
verify(mMockAccessibilityManager)
.addAccessibilityStateChangeListener(mBatteryChartView);
}
@Test
public void onDetachedFromWindow_removeAccessibilityStateChangeListener() {
mBatteryChartView.onAttachedToWindow();
mBatteryChartView.mHandler.postDelayed(
mBatteryChartView.mUpdateClickableStateRun, 1000);
mBatteryChartView.onDetachedFromWindow();
verify(mMockAccessibilityManager)
.removeAccessibilityStateChangeListener(mBatteryChartView);
assertThat(mBatteryChartView.mHandler.hasCallbacks(
mBatteryChartView.mUpdateClickableStateRun))
.isFalse();
}
@Test
public void onAccessibilityStateChanged_postUpdateStateRunnable() {
mBatteryChartView.mHandler = spy(mBatteryChartView.mHandler);
mBatteryChartView.onAccessibilityStateChanged(/*enabled=*/ true);
verify(mBatteryChartView.mHandler)
.removeCallbacks(mBatteryChartView.mUpdateClickableStateRun);
verify(mBatteryChartView.mHandler)
.postDelayed(mBatteryChartView.mUpdateClickableStateRun, 500L);
}
} }