https://drive.google.com/file/d/19Ms4JOPVfQ6rfXr71vTeXJLSvJqJtRfw/ Bug: 201501553 Test: make SettingsRoboTests Change-Id: I2c03585163ddb7809a09944aec326a41f6bd4758
624 lines
25 KiB
Java
624 lines
25 KiB
Java
/*
|
|
* Copyright (C) 2021 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under the
|
|
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the specific language governing
|
|
* permissions and limitations under the License.
|
|
*/
|
|
package com.android.settings.fuelgauge;
|
|
|
|
import static java.lang.Math.round;
|
|
|
|
import static com.android.settings.Utils.formatPercentage;
|
|
|
|
import android.accessibilityservice.AccessibilityServiceInfo;
|
|
import android.content.Context;
|
|
import android.content.res.Resources;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.CornerPathEffect;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Path;
|
|
import android.graphics.Rect;
|
|
import android.os.Handler;
|
|
import android.text.format.DateFormat;
|
|
import android.text.format.DateUtils;
|
|
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.appcompat.widget.AppCompatImageView;
|
|
import androidx.annotation.VisibleForTesting;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settings.overlay.FeatureFactory;
|
|
import com.android.settingslib.Utils;
|
|
|
|
import java.time.Clock;
|
|
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 {
|
|
private static final String TAG = "BatteryChartView";
|
|
private static final List<String> ACCESSIBILITY_SERVICE_NAMES =
|
|
Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService");
|
|
|
|
private static final int DEFAULT_TRAPEZOID_COUNT = 12;
|
|
private static final int DEFAULT_TIMESTAMP_COUNT = 4;
|
|
private static final int TIMESTAMP_GAPS_COUNT = DEFAULT_TIMESTAMP_COUNT - 1;
|
|
private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
|
|
private static final long UPDATE_STATE_DELAYED_TIME = 500L;
|
|
|
|
/** Selects all trapezoid shapes. */
|
|
public static final int SELECTED_INDEX_ALL = -1;
|
|
public static final int SELECTED_INDEX_INVALID = -2;
|
|
|
|
/** A callback listener for selected group index is updated. */
|
|
public interface OnSelectListener {
|
|
void onSelect(int trapezoidIndex);
|
|
}
|
|
|
|
private int mDividerWidth;
|
|
private int mDividerHeight;
|
|
private int mTrapezoidCount;
|
|
private float mTrapezoidVOffset;
|
|
private float mTrapezoidHOffset;
|
|
private boolean mIsSlotsClickabled;
|
|
private String[] mPercentages = getPercentages();
|
|
|
|
@VisibleForTesting int mHoveredIndex = SELECTED_INDEX_INVALID;
|
|
@VisibleForTesting int mSelectedIndex = SELECTED_INDEX_INVALID;
|
|
@VisibleForTesting String[] mTimestamps;
|
|
|
|
// 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 timestamp information.
|
|
private final Rect[] mTimestampsBounds =
|
|
new Rect[] {new Rect(), new Rect(), new Rect(), new Rect()};
|
|
|
|
@VisibleForTesting
|
|
Handler mHandler = new Handler();
|
|
@VisibleForTesting
|
|
final Runnable mUpdateClickableStateRun = () -> updateClickableState();
|
|
|
|
private int[] mLevels;
|
|
private Paint mTextPaint;
|
|
private Paint mDividerPaint;
|
|
private Paint mTrapezoidPaint;
|
|
|
|
@VisibleForTesting
|
|
Paint mTrapezoidCurvePaint = null;
|
|
private TrapezoidSlot[] mTrapezoidSlots;
|
|
// Records the location to calculate selected index.
|
|
private float mTouchUpEventX = Float.MIN_VALUE;
|
|
private BatteryChartView.OnSelectListener mOnSelectListener;
|
|
|
|
public BatteryChartView(Context context) {
|
|
super(context, null);
|
|
}
|
|
|
|
public BatteryChartView(Context context, AttributeSet attrs) {
|
|
super(context, attrs);
|
|
initializeColors(context);
|
|
// Registers the click event listener.
|
|
setOnClickListener(this);
|
|
setSelectedIndex(SELECTED_INDEX_ALL);
|
|
setTrapezoidCount(DEFAULT_TRAPEZOID_COUNT);
|
|
setClickable(false);
|
|
setLatestTimestamp(0);
|
|
}
|
|
|
|
/** Sets the total trapezoid count for drawing. */
|
|
public void setTrapezoidCount(int trapezoidCount) {
|
|
Log.i(TAG, "trapezoidCount:" + trapezoidCount);
|
|
mTrapezoidCount = trapezoidCount;
|
|
mTrapezoidSlots = new TrapezoidSlot[trapezoidCount];
|
|
// Allocates the trapezoid slot array.
|
|
for (int index = 0; index < trapezoidCount; index++) {
|
|
mTrapezoidSlots[index] = new TrapezoidSlot();
|
|
}
|
|
invalidate();
|
|
}
|
|
|
|
/** Sets all levels value to draw the trapezoid shape */
|
|
public void setLevels(int[] levels) {
|
|
Log.d(TAG, "setLevels() " + (levels == null ? "null" : levels.length));
|
|
if (levels == null) {
|
|
mLevels = null;
|
|
return;
|
|
}
|
|
// We should provide trapezoid count + 1 data to draw all trapezoids.
|
|
mLevels = levels.length == mTrapezoidCount + 1 ? levels : null;
|
|
setClickable(false);
|
|
invalidate();
|
|
if (mLevels == null) {
|
|
return;
|
|
}
|
|
// Sets the chart is clickable if there is at least one valid item in it.
|
|
for (int index = 0; index < mLevels.length - 1; index++) {
|
|
if (mLevels[index] != 0 && mLevels[index + 1] != 0) {
|
|
setClickable(true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Sets the selected group index to draw highlight effect. */
|
|
public void setSelectedIndex(int index) {
|
|
if (mSelectedIndex != index) {
|
|
mSelectedIndex = index;
|
|
invalidate();
|
|
// Callbacks to the listener if we have.
|
|
if (mOnSelectListener != null) {
|
|
mOnSelectListener.onSelect(mSelectedIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Sets the callback to monitor the selected group index. */
|
|
public void setOnSelectListener(BatteryChartView.OnSelectListener listener) {
|
|
mOnSelectListener = listener;
|
|
}
|
|
|
|
/** Sets the companion {@link TextView} for percentage information. */
|
|
public void setCompanionTextView(TextView textView) {
|
|
if (textView != null) {
|
|
// Pre-draws the view first to load style atttributions into paint.
|
|
textView.draw(new Canvas());
|
|
mTextPaint = textView.getPaint();
|
|
} else {
|
|
mTextPaint = null;
|
|
}
|
|
setVisibility(View.VISIBLE);
|
|
requestLayout();
|
|
}
|
|
|
|
/** Sets the latest timestamp for drawing into x-axis information. */
|
|
public void setLatestTimestamp(long latestTimestamp) {
|
|
if (latestTimestamp == 0) {
|
|
latestTimestamp = Clock.systemUTC().millis();
|
|
}
|
|
if (mTimestamps == null) {
|
|
mTimestamps = new String[DEFAULT_TIMESTAMP_COUNT];
|
|
}
|
|
final long timeSlotOffset =
|
|
DateUtils.HOUR_IN_MILLIS * (/*total 24 hours*/ 24 / TIMESTAMP_GAPS_COUNT);
|
|
final boolean is24HourFormat = DateFormat.is24HourFormat(getContext());
|
|
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
|
|
mTimestamps[index] =
|
|
ConvertUtils.utcToLocalTimeHour(
|
|
getContext(),
|
|
latestTimestamp - (TIMESTAMP_GAPS_COUNT - index) * timeSlotOffset,
|
|
is24HourFormat);
|
|
}
|
|
requestLayout();
|
|
}
|
|
|
|
@Override
|
|
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
// Measures text bounds and updates indent configuration.
|
|
if (mTextPaint != null) {
|
|
for (int index = 0; index < mPercentages.length; index++) {
|
|
mTextPaint.getTextBounds(
|
|
mPercentages[index], 0, mPercentages[index].length(),
|
|
mPercentageBounds[index]);
|
|
}
|
|
// Updates the indent configurations.
|
|
mIndent.top = mPercentageBounds[0].height();
|
|
mIndent.right = mPercentageBounds[0].width() + mTextPadding;
|
|
|
|
if (mTimestamps != null) {
|
|
int maxHeight = 0;
|
|
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
|
|
mTextPaint.getTextBounds(
|
|
mTimestamps[index], 0, mTimestamps[index].length(),
|
|
mTimestampsBounds[index]);
|
|
maxHeight = Math.max(maxHeight, mTimestampsBounds[index].height());
|
|
}
|
|
mIndent.bottom = maxHeight + round(mTextPadding * 1.5f);
|
|
}
|
|
Log.d(TAG, "setIndent:" + mPercentageBounds[0]);
|
|
} else {
|
|
mIndent.set(0, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void draw(Canvas canvas) {
|
|
super.draw(canvas);
|
|
drawHorizontalDividers(canvas);
|
|
drawVerticalDividers(canvas);
|
|
drawTrapezoids(canvas);
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
// Caches the location to calculate selected trapezoid index.
|
|
final int action = event.getAction();
|
|
switch (action) {
|
|
case MotionEvent.ACTION_UP:
|
|
mTouchUpEventX = event.getX();
|
|
break;
|
|
case MotionEvent.ACTION_CANCEL:
|
|
mTouchUpEventX = Float.MIN_VALUE; // reset
|
|
break;
|
|
}
|
|
return super.onTouchEvent(event);
|
|
}
|
|
|
|
@Override
|
|
public boolean onHoverEvent(MotionEvent event) {
|
|
final int action = event.getAction();
|
|
switch (action) {
|
|
case MotionEvent.ACTION_HOVER_ENTER:
|
|
case MotionEvent.ACTION_HOVER_MOVE:
|
|
final int trapezoidIndex = getTrapezoidIndex(event.getX());
|
|
if (mHoveredIndex != trapezoidIndex) {
|
|
mHoveredIndex = trapezoidIndex;
|
|
invalidate();
|
|
}
|
|
break;
|
|
}
|
|
return super.onHoverEvent(event);
|
|
}
|
|
|
|
@Override
|
|
public void onHoverChanged(boolean hovered) {
|
|
super.onHoverChanged(hovered);
|
|
if (!hovered) {
|
|
mHoveredIndex = SELECTED_INDEX_INVALID; // reset
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onClick(View view) {
|
|
if (mTouchUpEventX == Float.MIN_VALUE) {
|
|
Log.w(TAG, "invalid motion event for onClick() callback");
|
|
return;
|
|
}
|
|
final int trapezoidIndex = getTrapezoidIndex(mTouchUpEventX);
|
|
// Ignores the click event if the level is zero.
|
|
if (trapezoidIndex == SELECTED_INDEX_INVALID
|
|
|| !isValidToDraw(trapezoidIndex)) {
|
|
return;
|
|
}
|
|
// Selects all if users click the same trapezoid item two times.
|
|
if (trapezoidIndex == mSelectedIndex) {
|
|
setSelectedIndex(SELECTED_INDEX_ALL);
|
|
} else {
|
|
setSelectedIndex(trapezoidIndex);
|
|
}
|
|
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 levels again to force update the click state.
|
|
setLevels(mLevels);
|
|
}
|
|
invalidate();
|
|
}
|
|
|
|
@Override
|
|
public void setClickable(boolean clickable) {
|
|
super.setClickable(mIsSlotsClickabled && clickable);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
void setClickableForce(boolean clickable) {
|
|
super.setClickable(clickable);
|
|
}
|
|
|
|
private void initializeColors(Context context) {
|
|
setBackgroundColor(Color.TRANSPARENT);
|
|
mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context);
|
|
mTrapezoidColor = Utils.getDisabled(context, mTrapezoidSolidColor);
|
|
mTrapezoidHoverColor = Utils.getColorAttrDefaultColor(context,
|
|
com.android.internal.R.attr.colorAccentSecondaryVariant);
|
|
// Initializes the divider line paint.
|
|
final Resources resources = getContext().getResources();
|
|
mDividerWidth = resources.getDimensionPixelSize(R.dimen.chartview_divider_width);
|
|
mDividerHeight = resources.getDimensionPixelSize(R.dimen.chartview_divider_height);
|
|
mDividerPaint = new Paint();
|
|
mDividerPaint.setAntiAlias(true);
|
|
mDividerPaint.setColor(DIVIDER_COLOR);
|
|
mDividerPaint.setStyle(Paint.Style.STROKE);
|
|
mDividerPaint.setStrokeWidth(mDividerWidth);
|
|
Log.i(TAG, "mDividerWidth:" + mDividerWidth);
|
|
Log.i(TAG, "mDividerHeight:" + mDividerHeight);
|
|
// Initializes the trapezoid paint.
|
|
mTrapezoidHOffset = resources.getDimension(R.dimen.chartview_trapezoid_margin_start);
|
|
mTrapezoidVOffset = resources.getDimension(R.dimen.chartview_trapezoid_margin_bottom);
|
|
mTrapezoidPaint = new Paint();
|
|
mTrapezoidPaint.setAntiAlias(true);
|
|
mTrapezoidPaint.setColor(mTrapezoidSolidColor);
|
|
mTrapezoidPaint.setStyle(Paint.Style.FILL);
|
|
mTrapezoidPaint.setPathEffect(
|
|
new CornerPathEffect(
|
|
resources.getDimensionPixelSize(R.dimen.chartview_trapezoid_radius)));
|
|
// Initializes for drawing text information.
|
|
mTextPadding = resources.getDimensionPixelSize(R.dimen.chartview_text_padding);
|
|
}
|
|
|
|
private void drawHorizontalDividers(Canvas canvas) {
|
|
final int width = getWidth() - mIndent.right;
|
|
final int height = getHeight() - mIndent.top - mIndent.bottom;
|
|
// Draws the top divider line for 100% curve.
|
|
float offsetY = mIndent.top + mDividerWidth * .5f;
|
|
canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
|
|
drawPercentage(canvas, /*index=*/ 0, offsetY);
|
|
|
|
// Draws the center divider line for 50% curve.
|
|
final float availableSpace =
|
|
height - mDividerWidth * 2 - mTrapezoidVOffset - mDividerHeight;
|
|
offsetY = mIndent.top + mDividerWidth + availableSpace * .5f;
|
|
canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
|
|
drawPercentage(canvas, /*index=*/ 1, offsetY);
|
|
|
|
// Draws the bottom divider line for 0% curve.
|
|
offsetY = mIndent.top + (height - mDividerHeight - mDividerWidth * .5f);
|
|
canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
|
|
drawPercentage(canvas, /*index=*/ 2, offsetY);
|
|
}
|
|
|
|
private void drawPercentage(Canvas canvas, int index, float offsetY) {
|
|
if (mTextPaint != null) {
|
|
canvas.drawText(
|
|
mPercentages[index],
|
|
getWidth() - mPercentageBounds[index].width() - mPercentageBounds[index].left,
|
|
offsetY + mPercentageBounds[index].height() *.5f,
|
|
mTextPaint);
|
|
}
|
|
}
|
|
|
|
private void drawVerticalDividers(Canvas canvas) {
|
|
final int width = getWidth() - mIndent.right;
|
|
final int dividerCount = mTrapezoidCount + 1;
|
|
final float dividerSpace = dividerCount * mDividerWidth;
|
|
final float unitWidth = (width - dividerSpace) / (float) mTrapezoidCount;
|
|
final float bottomY = getHeight() - mIndent.bottom;
|
|
final float startY = bottomY - mDividerHeight;
|
|
final float trapezoidSlotOffset = mTrapezoidHOffset + mDividerWidth * .5f;
|
|
// Draws each vertical dividers.
|
|
float startX = mDividerWidth * .5f;
|
|
for (int index = 0; index < dividerCount; index++) {
|
|
canvas.drawLine(startX, startY, startX, bottomY, mDividerPaint);
|
|
final float nextX = startX + mDividerWidth + unitWidth;
|
|
// Updates the trapezoid slots for drawing.
|
|
if (index < mTrapezoidSlots.length) {
|
|
mTrapezoidSlots[index].mLeft = round(startX + trapezoidSlotOffset);
|
|
mTrapezoidSlots[index].mRight = round(nextX - trapezoidSlotOffset);
|
|
}
|
|
startX = nextX;
|
|
}
|
|
// Draws the timestamp slot information.
|
|
if (mTimestamps != null) {
|
|
final float[] xOffsets = new float[DEFAULT_TIMESTAMP_COUNT];
|
|
final float baselineX = mDividerWidth * .5f;
|
|
final float offsetX = mDividerWidth + unitWidth;
|
|
final int slotBarOffset = (/*total 12 bars*/ 12) / TIMESTAMP_GAPS_COUNT;
|
|
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
|
|
xOffsets[index] = baselineX + index * offsetX * slotBarOffset;
|
|
}
|
|
drawTimestamp(canvas, xOffsets);
|
|
}
|
|
}
|
|
|
|
private void drawTimestamp(Canvas canvas, float[] xOffsets) {
|
|
// Draws the 1st timestamp info.
|
|
canvas.drawText(
|
|
mTimestamps[0],
|
|
xOffsets[0] - mTimestampsBounds[0].left,
|
|
getTimestampY(0), mTextPaint);
|
|
final int latestIndex = DEFAULT_TIMESTAMP_COUNT - 1;
|
|
// Draws the last timestamp info.
|
|
canvas.drawText(
|
|
mTimestamps[latestIndex],
|
|
xOffsets[latestIndex] - mTimestampsBounds[latestIndex].width()
|
|
- mTimestampsBounds[latestIndex].left,
|
|
getTimestampY(latestIndex), mTextPaint);
|
|
// Draws the rest of timestamp info since it is located in the center.
|
|
for (int index = 1; index <= DEFAULT_TIMESTAMP_COUNT - 2; index++) {
|
|
canvas.drawText(
|
|
mTimestamps[index],
|
|
xOffsets[index] -
|
|
(mTimestampsBounds[index].width() - mTimestampsBounds[index].left) * .5f,
|
|
getTimestampY(index), mTextPaint);
|
|
|
|
}
|
|
}
|
|
|
|
private int getTimestampY(int index) {
|
|
return getHeight() - mTimestampsBounds[index].height()
|
|
+ (mTimestampsBounds[index].height() + mTimestampsBounds[index].top)
|
|
+ round(mTextPadding * 1.5f);
|
|
}
|
|
|
|
private void drawTrapezoids(Canvas canvas) {
|
|
// Ignores invalid trapezoid data.
|
|
if (mLevels == null) {
|
|
return;
|
|
}
|
|
final float trapezoidBottom =
|
|
getHeight() - mIndent.bottom - mDividerHeight - mDividerWidth
|
|
- mTrapezoidVOffset;
|
|
final float availableSpace = trapezoidBottom - mDividerWidth * .5f - mIndent.top;
|
|
final float unitHeight = availableSpace / 100f;
|
|
// Draws all trapezoid shapes into the canvas.
|
|
final Path trapezoidPath = new Path();
|
|
Path trapezoidCurvePath = null;
|
|
for (int index = 0; index < mTrapezoidCount; index++) {
|
|
// Not draws the trapezoid for corner or not initialization cases.
|
|
if (!isValidToDraw(index)) {
|
|
if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) {
|
|
canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint);
|
|
trapezoidCurvePath = null;
|
|
}
|
|
continue;
|
|
}
|
|
// Configures the trapezoid paint color.
|
|
final int trapezoidColor =
|
|
!mIsSlotsClickabled
|
|
? mTrapezoidColor
|
|
: mSelectedIndex == index || mSelectedIndex == SELECTED_INDEX_ALL
|
|
? mTrapezoidSolidColor : mTrapezoidColor;
|
|
final boolean isHover = mHoveredIndex == index && isValidToDraw(mHoveredIndex);
|
|
mTrapezoidPaint.setColor(isHover ? mTrapezoidHoverColor : trapezoidColor);
|
|
|
|
final float leftTop = round(trapezoidBottom - mLevels[index] * unitHeight);
|
|
final float rightTop = round(trapezoidBottom - mLevels[index + 1] * unitHeight);
|
|
trapezoidPath.reset();
|
|
trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom);
|
|
trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
|
|
trapezoidPath.lineTo(mTrapezoidSlots[index].mRight, rightTop);
|
|
trapezoidPath.lineTo(mTrapezoidSlots[index].mRight, trapezoidBottom);
|
|
// A tricky way to make the trapezoid shape drawing the rounded corner.
|
|
trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, trapezoidBottom);
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Searches the corresponding trapezoid index from x location.
|
|
private int getTrapezoidIndex(float x) {
|
|
for (int index = 0; index < mTrapezoidSlots.length; index++) {
|
|
final TrapezoidSlot slot = mTrapezoidSlots[index];
|
|
if (x >= slot.mLeft - mTrapezoidHOffset
|
|
&& x <= slot.mRight + mTrapezoidHOffset) {
|
|
return index;
|
|
}
|
|
}
|
|
return SELECTED_INDEX_INVALID;
|
|
}
|
|
|
|
private boolean isValidToDraw(int trapezoidIndex) {
|
|
return mLevels != null
|
|
&& trapezoidIndex >= 0
|
|
&& trapezoidIndex < mLevels.length - 1
|
|
&& mLevels[trapezoidIndex] != 0
|
|
&& mLevels[trapezoidIndex + 1] != 0;
|
|
}
|
|
|
|
private static String[] getPercentages() {
|
|
return new String[] {
|
|
formatPercentage(/*percentage=*/ 100, /*round=*/ true),
|
|
formatPercentage(/*percentage=*/ 50, /*round=*/ true),
|
|
formatPercentage(/*percentage=*/ 0, /*round=*/ true)};
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static boolean isAccessibilityEnabled(Context context) {
|
|
final AccessibilityManager accessibilityManager =
|
|
context.getSystemService(AccessibilityManager.class);
|
|
if (!accessibilityManager.isEnabled()) {
|
|
return false;
|
|
}
|
|
final List<AccessibilityServiceInfo> serviceInfoList =
|
|
accessibilityManager.getEnabledAccessibilityServiceList(
|
|
AccessibilityServiceInfo.FEEDBACK_SPOKEN
|
|
| AccessibilityServiceInfo.FEEDBACK_GENERIC);
|
|
for (AccessibilityServiceInfo info : serviceInfoList) {
|
|
for (String serviceName : ACCESSIBILITY_SERVICE_NAMES) {
|
|
final String serviceId = info.getId();
|
|
if (serviceId != null && serviceId.contains(serviceName)) {
|
|
Log.d(TAG, "acccessibilityEnabled:" + serviceId);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// A container class for each trapezoid left and right location.
|
|
private static final class TrapezoidSlot {
|
|
public float mLeft;
|
|
public float mRight;
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format(Locale.US, "TrapezoidSlot[%f,%f]", mLeft, mRight);
|
|
}
|
|
}
|
|
}
|