Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Loading
+124 −9
Original line number Diff line number Diff line
@@ -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 {