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

Commit 86494fef authored by Prabir Pradhan's avatar Prabir Pradhan
Browse files

PointerLocationView: Draw pointers in unrotated display space

The view is useful in debugging and recording where a user interacted on
screen. Previously, the view worked in the logical display space, like
any other app. This is not helpful in debugging input during rotation,
since the drawn trace content will rotate with the display.

In this CL, make PointerLocationView work in the unrotated display
space. In particular, the pointer trace (aka trail) and the pointer
states (e.g. crosshairs, velocity vector, touch/tool oval, etc.) are
drawn in the unrotated display space, while the display labels and text
will rotate with the display.

The view will now record where the user interacted with the device
relative the physical hardware, so the pointer trace will not rotate
with the display. This will give a more accurate representation of where
the user interactions happened when looking at screen recordings where
the display rotates.

Bug: 368317060
Test: manual, with setting enabled
Flag: EXEMPT bug fix
Change-Id: I4dfe797010914669c1aa8639fbb04e1eefd8ff13
parent 004167a6
Loading
Loading
Loading
Loading
+68 −14
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.RoundedCorner;
import android.view.Surface;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
@@ -148,7 +149,8 @@ public class PointerLocationView extends View implements InputDeviceListener,

    // Draw the trace of all pointers in the current gesture in a separate layer
    // that is not cleared on every frame so that we don't have to re-draw the
    // entire trace on each frame.
    // entire trace on each frame. The trace bitmap is in the coordinate space of the unrotated
    // display.
    private Bitmap mTraceBitmap;
    private final Canvas mTraceCanvas;

@@ -292,7 +294,11 @@ public class PointerLocationView extends View implements InputDeviceListener,
    protected void onDraw(Canvas canvas) {
        final int NP = mPointers.size();

        // Pointer trace.
        canvas.save();
        rotateCanvasToUnrotatedDisplay(canvas);
        canvas.drawBitmap(mTraceBitmap, 0, 0, null);
        canvas.restore();

        if (!mSystemGestureExclusion.isEmpty()) {
            mSystemGestureExclusionPath.reset();
@@ -309,7 +315,9 @@ public class PointerLocationView extends View implements InputDeviceListener,
        // Labels
        drawLabels(canvas);

        // Pointer trace.
        // Current pointer states.
        canvas.save();
        rotateCanvasToUnrotatedDisplay(canvas);
        for (int p = 0; p < NP; p++) {
            final PointerState ps = mPointers.valueAt(p);
            float lastX = ps.mCurrentX, lastY = ps.mCurrentY;
@@ -392,6 +400,7 @@ public class PointerLocationView extends View implements InputDeviceListener,
                }
            }
        }
        canvas.restore();
    }

    private void drawLabels(Canvas canvas) {
@@ -571,6 +580,11 @@ public class PointerLocationView extends View implements InputDeviceListener,

    @Override
    public void onPointerEvent(MotionEvent event) {
        // PointerLocationView stores and draws events in the unrotated display space, so undo the
        // event's rotation to bring it back to the unrotated display space.
        event.transform(MotionEvent.createRotateMatrix(inverseRotation(event.getSurfaceRotation()),
                mTraceBitmap.getWidth(), mTraceBitmap.getHeight()));

        final int action = event.getAction();

        if (action == MotionEvent.ACTION_DOWN
@@ -703,12 +717,13 @@ public class PointerLocationView extends View implements InputDeviceListener,
        float y = ps.mCurrentY;
        float lastX = ps.mPreviousX;
        float lastY = ps.mPreviousY;
        if (!Float.isNaN(x) && !Float.isNaN(y) && !Float.isNaN(lastX) && !Float.isNaN(lastY)) {
        if (Float.isNaN(x) || Float.isNaN(y) || Float.isNaN(lastX) || Float.isNaN(lastY)) {
            return;
        }
        mTraceCanvas.drawLine(lastX, lastY, x, y, mPathPaint);
        Paint paint = ps.mPreviousPointIsHistorical ? mPaint : mCurrentPointPaint;
        mTraceCanvas.drawPoint(lastX, lastY, paint);
    }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
@@ -1013,17 +1028,56 @@ public class PointerLocationView extends View implements InputDeviceListener,
    }

    private void configureTraceBitmap() {
        final int width = mContext.getDisplay().getWidth();
        final int height = mContext.getDisplay().getHeight();
        if (mTraceBitmap != null && mTraceBitmap.getWidth() == width
                && mTraceBitmap.getHeight() == height) {
        final var display = mContext.getDisplay();
        final boolean rotated = display.getRotation() == Surface.ROTATION_90
                || display.getRotation() == Surface.ROTATION_270;
        int unrotatedWidth = rotated ? display.getHeight() : display.getWidth();
        int unrotatedHeight = rotated ? display.getWidth() : display.getHeight();

        if (mTraceBitmap != null && mTraceBitmap.getWidth() == unrotatedWidth
                && mTraceBitmap.getHeight() == unrotatedHeight) {
            return;
        }
        if (width <= 0 || height <= 0) {
            Slog.w(TAG, "Ignoring configuration: invalid display size: " + width + "x" + height);
            return;
        if (unrotatedWidth <= 0 || unrotatedHeight <= 0) {
            Slog.w(TAG, "Ignoring configuration: invalid display size: " + unrotatedWidth + "x"
                    + unrotatedHeight);
            // Initialize the bitmap to an arbitrary size. It should be reconfigured with a valid
            // size in the future.
            unrotatedWidth = 100;
            unrotatedHeight = 100;
        }
        mTraceBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        mTraceBitmap = Bitmap.createBitmap(unrotatedWidth, unrotatedHeight,
                Bitmap.Config.ARGB_8888);
        mTraceCanvas.setBitmap(mTraceBitmap);
    }

    private static int inverseRotation(@Surface.Rotation int rotation) {
        return switch(rotation) {
            case Surface.ROTATION_0 -> Surface.ROTATION_0;
            case Surface.ROTATION_90 -> Surface.ROTATION_270;
            case Surface.ROTATION_180 -> Surface.ROTATION_180;
            case Surface.ROTATION_270 -> Surface.ROTATION_90;
            default -> {
                Slog.e(TAG, "Received unexpected surface rotation: " + rotation);
                yield Surface.ROTATION_0;
            }
        };
    }

    private void rotateCanvasToUnrotatedDisplay(Canvas c) {
        switch (inverseRotation(mContext.getDisplay().getRotation())) {
            case Surface.ROTATION_90 -> {
                c.rotate(90);
                c.translate(0, -mTraceBitmap.getHeight());
            }
            case Surface.ROTATION_180 -> {
                c.rotate(180);
                c.translate(-mTraceBitmap.getWidth(), -mTraceBitmap.getHeight());
            }
            case Surface.ROTATION_270 -> {
                c.rotate(270);
                c.translate(-mTraceBitmap.getWidth(), 0);
            }
        }
    }
}