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

Commit 1719321a authored by Wenyu Zhang's avatar Wenyu Zhang
Browse files

[2/3] autoclick: Draw click dot indicator

Indicates where exactly the click will happen.

Demo: http://b/430320631#comment5

Change-Id: I1e84ecaaf0d3c8dfb6536c79e4e69138426a6870
Bug: b/430320631
Test: AutoclickIndicatorViewTest
Flag: com.android.server.accessibility.enable_autoclick_indicator
parent 2cd60c5e
Loading
Loading
Loading
Loading
+39 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
@@ -48,6 +49,11 @@ public class AutoclickIndicatorView extends View {

    static final int MINIMAL_ANIMATION_DURATION = 50;

    // The radius of the click point indicator.
    private static final float POINT_RADIUS_DP = 4f;

    private static final float POINT_STROKE_WIDTH_DP = 1f;

    private final int mColor = R.color.materialColorPrimary;

    // Radius of the indicator circle.
@@ -55,11 +61,15 @@ public class AutoclickIndicatorView extends View {

    // Paint object used to draw the indicator.
    private final Paint mPaint;
    private final Paint mPointPaint;

    private final ValueAnimator mAnimator;

    private final RectF mRingRect;

    private final float mPointSizePx;
    private final float mPointStrokeWidthPx;

    // x and y coordinates of the mouse.
    private float mMouseX;
    private float mMouseY;
@@ -84,6 +94,15 @@ public class AutoclickIndicatorView extends View {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(10);

        // Convert dp to pixels based on screen density for the point indicator.
        float density = getResources().getDisplayMetrics().density;
        mPointSizePx = POINT_RADIUS_DP * density;
        mPointStrokeWidthPx = POINT_STROKE_WIDTH_DP * density;

        // Setup paint for drawing the point indicator.
        mPointPaint = new Paint();
        mPointPaint.setAntiAlias(true);

        mAnimator = ValueAnimator.ofFloat(0, 360);
        mAnimator.setDuration(mAnimationDuration);
        mAnimator.setInterpolator(new LinearInterpolator());
@@ -124,12 +143,24 @@ public class AutoclickIndicatorView extends View {
        super.onDraw(canvas);

        if (showIndicator) {
            // Draw the ring indicator.
            mRingRect.set(
                    /* left= */ mSnapshotX - mRadius,
                    /* top= */ mSnapshotY - mRadius,
                    /* right= */ mSnapshotX + mRadius,
                    /* bottom= */ mSnapshotY + mRadius);
            canvas.drawArc(mRingRect, /* startAngle= */ -90, mSweepAngle, false, mPaint);

            // Draw a point indicator at the cursor's current location.
            // Draw a circle at (mMouseX, mMouseY) with default black fill and white border.
            mPointPaint.setStyle(Paint.Style.FILL);
            mPointPaint.setColor(Color.BLACK);
            canvas.drawCircle(mMouseX, mMouseY, mPointSizePx, mPointPaint);

            mPointPaint.setStyle(Paint.Style.STROKE);
            mPointPaint.setStrokeWidth(mPointStrokeWidthPx);
            mPointPaint.setColor(Color.WHITE);
            canvas.drawCircle(mMouseX, mMouseY, mPointSizePx, mPointPaint);
        }
    }

@@ -156,8 +187,16 @@ public class AutoclickIndicatorView extends View {
    }

    public void setCoordination(float x, float y) {
        if (mMouseX == x && mMouseY == y) {
            return;
        }
        mMouseX = x;
        mMouseY = y;

        // Redraw the click point indicator with the updated coordinates.
        if (showIndicator) {
            invalidate();
        }
    }

    public void setRadius(int radius) {
+54 −1
Original line number Diff line number Diff line
@@ -37,12 +37,12 @@ import android.graphics.RectF;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.view.accessibility.AccessibilityManager;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -76,6 +76,29 @@ public class AutoclickIndicatorViewTest {
        // Verify ring is drawn.
        verify(mMockCanvas).drawArc(
                any(RectF.class), eq(-90f), anyFloat(), eq(false), any(Paint.class));
        // Verify point is drawn (fill and stroke).
        verify(mMockCanvas, times(2)).drawCircle(eq(100f), eq(200f), anyFloat(), any(Paint.class));
    }

    @Test
    public void onDraw_mouseMovesAfterIndicatorShown_pointMovesWithMouse() {
        mAutoclickIndicatorView.setCoordination(100f, 200f);
        mAutoclickIndicatorView.redrawIndicator();
        // After indicator is shown, mouse moves to a new position.
        mAutoclickIndicatorView.setCoordination(300f, 400f);

        mAutoclickIndicatorView.onDraw(mMockCanvas);

        // Verify ring is drawn at the original snapshot position.
        ArgumentCaptor<RectF> rectCaptor = ArgumentCaptor.forClass(RectF.class);
        verify(mMockCanvas)
                .drawArc(rectCaptor.capture(), eq(-90f), anyFloat(), eq(false), any(Paint.class));
        RectF ringRect = rectCaptor.getValue();
        assertThat(ringRect.centerX()).isEqualTo(100f);
        assertThat(ringRect.centerY()).isEqualTo(200f);

        // Verify point is drawn at the new mouse position (fill and stroke).
        verify(mMockCanvas, times(2)).drawCircle(eq(300f), eq(400f), anyFloat(), any(Paint.class));
    }

    @Test
@@ -86,12 +109,15 @@ public class AutoclickIndicatorViewTest {

        verify(mMockCanvas, never()).drawArc(
                any(RectF.class), anyFloat(), anyFloat(), anyBoolean(), any(Paint.class));
        verify(mMockCanvas, never())
                .drawCircle(anyFloat(), anyFloat(), anyFloat(), any(Paint.class));
    }

    @Test
    public void setCoordination_showIndicatorTrue_invalidatesView() {
        mSpyAutoclickIndicatorView.setCoordination(100f, 200f);
        mSpyAutoclickIndicatorView.redrawIndicator();
        clearInvocations(mSpyAutoclickIndicatorView);

        mSpyAutoclickIndicatorView.setCoordination(300f, 400f);

@@ -109,6 +135,17 @@ public class AutoclickIndicatorViewTest {
        verify(mSpyAutoclickIndicatorView, never()).invalidate();
    }

    @Test
    public void setCoordination_sameCoordinates_doesNotInvalidateView() {
        mSpyAutoclickIndicatorView.setCoordination(100f, 200f);
        mSpyAutoclickIndicatorView.redrawIndicator();
        clearInvocations(mSpyAutoclickIndicatorView);

        mSpyAutoclickIndicatorView.setCoordination(100f, 200f);

        verify(mSpyAutoclickIndicatorView, never()).invalidate();
    }

    @Test
    public void redrawIndicator_startsAnimation() {
        mAutoclickIndicatorView.redrawIndicator();
@@ -117,6 +154,12 @@ public class AutoclickIndicatorViewTest {
        assertThat(animator.isStarted()).isTrue();
    }

    @Test
    public void redrawIndicator_invalidatesView() {
        mSpyAutoclickIndicatorView.redrawIndicator();
        verify(mSpyAutoclickIndicatorView).invalidate();
    }

    @Test
    public void clearIndicator_cancelsAnimation() {
        mAutoclickIndicatorView.redrawIndicator();
@@ -126,6 +169,16 @@ public class AutoclickIndicatorViewTest {
        assertThat(animator.isStarted()).isFalse();
    }

    @Test
    public void clearIndicator_invalidatesView() {
        mSpyAutoclickIndicatorView.redrawIndicator();
        clearInvocations(mSpyAutoclickIndicatorView);

        mSpyAutoclickIndicatorView.clearIndicator();

        verify(mSpyAutoclickIndicatorView).invalidate();
    }

    @Test
    public void setAnimationDuration_updatesAnimatorDuration() {
        int testDuration = 1000;