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

Commit 9d46ada7 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Migrate FocusEventDebugView to an input subpackage." into main

parents a527b022 ff6b7c6b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -116,6 +116,7 @@ import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
import com.android.server.input.InputManagerInternal.LidSwitchCallback;
import com.android.server.input.debug.FocusEventDebugView;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.WindowManagerPolicy;

+2 −1
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@
 * limitations under the License.
 */

package com.android.server.input;
package com.android.server.input.debug;

import android.view.Display;
import android.view.InputEvent;
@@ -22,6 +22,7 @@ import android.view.InputEventReceiver;
import android.view.MotionEvent;

import com.android.server.UiThread;
import com.android.server.input.InputManagerService;

/**
 * Receives input events before they are dispatched and reports them to FocusEventDebugView.
+6 −388
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@
 * limitations under the License.
 */

package com.android.server.input;
package com.android.server.input.debug;

import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static android.util.TypedValue.COMPLEX_UNIT_SP;
@@ -24,11 +24,9 @@ import android.animation.LayoutTransition;
import android.annotation.AnyThread;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.util.DisplayMetrics;
import android.util.Pair;
@@ -40,7 +38,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RoundedCorner;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.animation.AccelerateInterpolator;
import android.widget.HorizontalScrollView;
@@ -50,19 +47,17 @@ import android.widget.TextView;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.input.InputManagerService;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 *  Displays focus events, such as physical keyboard KeyEvents and non-pointer MotionEvents on
 *  the screen.
 */
class FocusEventDebugView extends RelativeLayout {
public class FocusEventDebugView extends RelativeLayout {

    private static final String TAG = FocusEventDebugView.class.getSimpleName();

@@ -112,7 +107,7 @@ class FocusEventDebugView extends RelativeLayout {
        mOuterPadding = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, OUTER_PADDING_DP, mDm);
    }

    FocusEventDebugView(Context c, InputManagerService service) {
    public FocusEventDebugView(Context c, InputManagerService service) {
        this(c, service, () -> new RotaryInputValueView(c), () -> new RotaryInputGraphView(c));
    }

@@ -149,11 +144,13 @@ class FocusEventDebugView extends RelativeLayout {
        return super.dispatchKeyEvent(event);
    }

    /** Determines whether to show the key presses visualization. */
    @AnyThread
    public void updateShowKeyPresses(boolean enabled) {
        post(() -> handleUpdateShowKeyPresses(enabled));
    }

    /** Determines whether to show the rotary input visualization. */
    @AnyThread
    public void updateShowRotaryInput(boolean enabled) {
        post(() -> handleUpdateShowRotaryInput(enabled));
@@ -358,13 +355,6 @@ class FocusEventDebugView extends RelativeLayout {
        return mRotaryInputValueView != null;
    }

    /**
     * Converts a dimension in scaled pixel units to integer display pixels.
     */
    private static int applyDimensionSp(int dimensionSp, DisplayMetrics dm) {
        return (int) TypedValue.applyDimension(COMPLEX_UNIT_SP, dimensionSp, dm);
    }

    private static class PressedKeyView extends TextView {

        private static final ColorFilter sInvertColors = new ColorMatrixColorFilter(new float[]{
@@ -473,376 +463,4 @@ class FocusEventDebugView extends RelativeLayout {
            invalidate();
        }
    }

    // TODO(b/286086154): move RotaryInputGraphView and RotaryInputValueView to a subpackage.

    /** Draws the most recent rotary input value and indicates whether the source is active. */
    @VisibleForTesting
    static class RotaryInputValueView extends TextView {

        private static final int INACTIVE_TEXT_COLOR = 0xffff00ff;
        private static final int ACTIVE_TEXT_COLOR = 0xff420f28;
        private static final int TEXT_SIZE_SP = 8;
        private static final int SIDE_PADDING_SP = 4;
        /** Determines how long the active status lasts. */
        private static final int ACTIVE_STATUS_DURATION = 250 /* milliseconds */;
        private static final ColorFilter ACTIVE_BACKGROUND_FILTER =
                new ColorMatrixColorFilter(new float[]{
                        0, 0, 0, 0, 255, // red
                        0, 0, 0, 0,   0, // green
                        0, 0, 0, 0, 255, // blue
                        0, 0, 0, 0, 200  // alpha
                });

        private final Runnable mUpdateActivityStatusCallback = () -> updateActivityStatus(false);
        private final float mScaledVerticalScrollFactor;

        @VisibleForTesting
        RotaryInputValueView(Context c) {
            super(c);

            DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
            mScaledVerticalScrollFactor = ViewConfiguration.get(c).getScaledVerticalScrollFactor();

            setText(getFormattedValue(0));
            setTextColor(INACTIVE_TEXT_COLOR);
            setTextSize(applyDimensionSp(TEXT_SIZE_SP, dm));
            setPaddingRelative(applyDimensionSp(SIDE_PADDING_SP, dm), 0,
                    applyDimensionSp(SIDE_PADDING_SP, dm), 0);
            setTypeface(null, Typeface.BOLD);
            setBackgroundResource(R.drawable.focus_event_rotary_input_background);
        }

        void updateValue(float value) {
            removeCallbacks(mUpdateActivityStatusCallback);

            setText(getFormattedValue(value * mScaledVerticalScrollFactor));

            updateActivityStatus(true);
            postDelayed(mUpdateActivityStatusCallback, ACTIVE_STATUS_DURATION);
        }

        @VisibleForTesting
        void updateActivityStatus(boolean active) {
            if (active) {
                setTextColor(ACTIVE_TEXT_COLOR);
                getBackground().setColorFilter(ACTIVE_BACKGROUND_FILTER);
            } else {
                setTextColor(INACTIVE_TEXT_COLOR);
                getBackground().clearColorFilter();
            }
        }

        private static String getFormattedValue(float value) {
            return String.format("%s%.1f", value < 0 ? "-" : "+", Math.abs(value));
        }
    }

    /**
     * Shows a graph with the rotary input values as a function of time.
     * The graph gets reset if no action is received for a certain amount of time.
     */
    @VisibleForTesting
    static class RotaryInputGraphView extends View {

        private static final int FRAME_COLOR = 0xbf741b47;
        private static final int FRAME_WIDTH_SP = 2;
        private static final int FRAME_BORDER_GAP_SP = 10;
        private static final int FRAME_TEXT_SIZE_SP = 10;
        private static final int FRAME_TEXT_OFFSET_SP = 2;
        private static final int GRAPH_COLOR = 0xffff00ff;
        private static final int GRAPH_LINE_WIDTH_SP = 1;
        private static final int GRAPH_POINT_RADIUS_SP = 4;
        private static final long MAX_SHOWN_TIME_INTERVAL = TimeUnit.SECONDS.toMillis(5);
        private static final float DEFAULT_FRAME_CENTER_POSITION = 0;
        private static final int MAX_GRAPH_VALUES_SIZE = 400;
        /** Maximum time between values so that they are considered part of the same gesture. */
        private static final long MAX_GESTURE_TIME = TimeUnit.SECONDS.toMillis(1);

        private final DisplayMetrics mDm;
        /**
         * Distance in position units (amount scrolled in display pixels) from the center to the
         * top/bottom frame lines.
         */
        private final float mFrameCenterToBorderDistance;
        private final float mScaledVerticalScrollFactor;
        private final Locale mDefaultLocale;
        private final Paint mFramePaint = new Paint();
        private final Paint mFrameTextPaint = new Paint();
        private final Paint mGraphLinePaint = new Paint();
        private final Paint mGraphPointPaint = new Paint();

        private final CyclicBuffer mGraphValues = new CyclicBuffer(MAX_GRAPH_VALUES_SIZE);
        /** Position at which graph values are placed at the center of the graph. */
        private float mFrameCenterPosition = DEFAULT_FRAME_CENTER_POSITION;

        @VisibleForTesting
        RotaryInputGraphView(Context c) {
            super(c);

            mDm = mContext.getResources().getDisplayMetrics();
            // This makes the center-to-border distance equivalent to the display height, meaning
            // that the total height of the graph is equivalent to 2x the display height.
            mFrameCenterToBorderDistance = mDm.heightPixels;
            mScaledVerticalScrollFactor = ViewConfiguration.get(c).getScaledVerticalScrollFactor();
            mDefaultLocale = Locale.getDefault();

            mFramePaint.setColor(FRAME_COLOR);
            mFramePaint.setStrokeWidth(applyDimensionSp(FRAME_WIDTH_SP, mDm));

            mFrameTextPaint.setColor(GRAPH_COLOR);
            mFrameTextPaint.setTextSize(applyDimensionSp(FRAME_TEXT_SIZE_SP, mDm));

            mGraphLinePaint.setColor(GRAPH_COLOR);
            mGraphLinePaint.setStrokeWidth(applyDimensionSp(GRAPH_LINE_WIDTH_SP, mDm));
            mGraphLinePaint.setStrokeCap(Paint.Cap.ROUND);
            mGraphLinePaint.setStrokeJoin(Paint.Join.ROUND);

            mGraphPointPaint.setColor(GRAPH_COLOR);
            mGraphPointPaint.setStrokeWidth(applyDimensionSp(GRAPH_POINT_RADIUS_SP, mDm));
            mGraphPointPaint.setStrokeCap(Paint.Cap.ROUND);
            mGraphPointPaint.setStrokeJoin(Paint.Join.ROUND);
        }

        /**
         * Reads new scroll axis value and updates the list accordingly. Old positions are
         * kept at the front (what you would get with getFirst), while the recent positions are
         * kept at the back (what you would get with getLast). Also updates the frame center
         * position to handle out-of-bounds cases.
         */
        void addValue(float scrollAxisValue, long eventTime) {
            // Remove values that are too old.
            while (mGraphValues.getSize() > 0
                    && (eventTime - mGraphValues.getFirst().mTime) > MAX_SHOWN_TIME_INTERVAL) {
                mGraphValues.removeFirst();
            }

            // If there are no recent values, reset the frame center.
            if (mGraphValues.getSize() == 0) {
                mFrameCenterPosition = DEFAULT_FRAME_CENTER_POSITION;
            }

            // Handle new value. We multiply the scroll axis value by the scaled scroll factor to
            // get the amount of pixels to be scrolled. We also compute the accumulated position
            // by adding the current value to the last one (if not empty).
            final float displacement = scrollAxisValue * mScaledVerticalScrollFactor;
            final float prevPos = (mGraphValues.getSize() == 0 ? 0 : mGraphValues.getLast().mPos);
            final float pos = prevPos + displacement;

            mGraphValues.add(pos, eventTime);

            // The difference between the distance of the most recent position from the center
            // frame (pos - mFrameCenterPosition) and the maximum allowed distance from the center
            // frame (mFrameCenterToBorderDistance).
            final float verticalDiff = Math.abs(pos - mFrameCenterPosition)
                    - mFrameCenterToBorderDistance;
            // If needed, translate frame.
            if (verticalDiff > 0) {
                final int sign = pos - mFrameCenterPosition < 0 ? -1 : 1;
                // Here, we update the center frame position by the exact amount needed for us to
                // stay within the maximum allowed distance from the center frame.
                mFrameCenterPosition += sign * verticalDiff;
            }

            // Redraw canvas.
            invalidate();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);

            // Note: vertical coordinates in Canvas go from top to bottom,
            // that is bottomY > middleY > topY.
            final int verticalMargin = applyDimensionSp(FRAME_BORDER_GAP_SP, mDm);
            final int topY = verticalMargin;
            final int bottomY = getHeight() - verticalMargin;
            final int middleY = (topY + bottomY) / 2;

            // Note: horizontal coordinates in Canvas go from left to right,
            // that is rightX > leftX.
            final int leftX = 0;
            final int rightX = getWidth();

            // Draw the frame, which includes 3 lines that show the maximum,
            // minimum and middle positions of the graph.
            canvas.drawLine(leftX, topY, rightX, topY, mFramePaint);
            canvas.drawLine(leftX, middleY, rightX, middleY, mFramePaint);
            canvas.drawLine(leftX, bottomY, rightX, bottomY, mFramePaint);

            // Draw the position that each frame line corresponds to.
            final int frameTextOffset = applyDimensionSp(FRAME_TEXT_OFFSET_SP, mDm);
            canvas.drawText(
                    String.format(mDefaultLocale, "%.1f",
                            mFrameCenterPosition + mFrameCenterToBorderDistance),
                    leftX,
                    topY - frameTextOffset, mFrameTextPaint
            );
            canvas.drawText(
                    String.format(mDefaultLocale, "%.1f", mFrameCenterPosition),
                    leftX,
                    middleY - frameTextOffset, mFrameTextPaint
            );
            canvas.drawText(
                    String.format(mDefaultLocale, "%.1f",
                            mFrameCenterPosition - mFrameCenterToBorderDistance),
                    leftX,
                    bottomY - frameTextOffset, mFrameTextPaint
            );

            // If there are no graph values to be drawn, stop here.
            if (mGraphValues.getSize() == 0) {
                return;
            }

            // Draw the graph using the times and positions.
            // We start at the most recent value (which should be drawn at the right) and move
            // to the older values (which should be drawn to the left of more recent ones). Negative
            // indices are handled by circuling back to the end of the buffer.
            final long mostRecentTime = mGraphValues.getLast().mTime;
            float prevCoordX = 0;
            float prevCoordY = 0;
            float prevAge = 0;
            for (Iterator<GraphValue> iter = mGraphValues.reverseIterator(); iter.hasNext();) {
                final GraphValue value = iter.next();

                final int age = (int) (mostRecentTime - value.mTime);
                final float pos = value.mPos;

                // We get the horizontal coordinate in time units from left to right with
                // (MAX_SHOWN_TIME_INTERVAL - age). Then, we rescale it to match the canvas
                // units by dividing it by the time-domain length (MAX_SHOWN_TIME_INTERVAL)
                // and by multiplying it by the canvas length (rightX - leftX). Finally, we
                // offset the coordinate by adding it to leftX.
                final float coordX = leftX + ((float) (MAX_SHOWN_TIME_INTERVAL - age)
                        / MAX_SHOWN_TIME_INTERVAL) * (rightX - leftX);

                // We get the vertical coordinate in position units from middle to top with
                // (pos - mFrameCenterPosition). Then, we rescale it to match the canvas
                // units by dividing it by half of the position-domain length
                // (mFrameCenterToBorderDistance) and by multiplying it by half of the canvas
                // length (middleY - topY). Finally, we offset the coordinate by subtracting
                // it from middleY (we can't "add" here because the coordinate grows from top
                // to bottom).
                final float coordY = middleY - ((pos - mFrameCenterPosition)
                        / mFrameCenterToBorderDistance) * (middleY - topY);

                // Draw a point for this value.
                canvas.drawPoint(coordX, coordY, mGraphPointPaint);

                // If this value is part of the same gesture as the previous one, draw a line
                // between them. We ignore the first value (with age = 0).
                if (age != 0 && (age - prevAge) <= MAX_GESTURE_TIME) {
                    canvas.drawLine(prevCoordX, prevCoordY, coordX, coordY, mGraphLinePaint);
                }

                prevCoordX = coordX;
                prevCoordY = coordY;
                prevAge = age;
            }
        }

        @VisibleForTesting
        float getFrameCenterPosition() {
            return mFrameCenterPosition;
        }

        /**
         * Holds data needed to draw each entry in the graph.
         */
        private static class GraphValue {
            /** Position. */
            float mPos;
            /** Time when this value was added. */
            long mTime;

            GraphValue(float pos, long time) {
                this.mPos = pos;
                this.mTime = time;
            }
        }

        /**
         * Holds the graph values as a cyclic buffer. It has a fixed capacity, and it replaces the
         * old values with new ones to avoid creating new objects.
         */
        private static class CyclicBuffer {
            private final GraphValue[] mValues;
            private final int mCapacity;
            private int mSize = 0;
            private int mLastIndex = 0;

            // The iteration index and counter are here to make it easier to reset them.
            /** Determines the value currently pointed by the iterator. */
            private int mIteratorIndex;
            /** Counts how many values have been iterated through. */
            private int mIteratorCount;

            /** Used traverse the values in reverse order. */
            private final Iterator<GraphValue> mReverseIterator = new Iterator<GraphValue>() {
                @Override
                public boolean hasNext() {
                    return mIteratorCount <= mSize;
                }

                @Override
                public GraphValue next() {
                    // Returns the value currently pointed by the iterator and moves the iterator to
                    // the previous one.
                    mIteratorCount++;
                    return mValues[(mIteratorIndex-- + mCapacity) % mCapacity];
                }
            };

            CyclicBuffer(int capacity) {
                mCapacity = capacity;
                mValues = new GraphValue[capacity];
            }

            /**
             * Add new graph value. If there is an existing object, we replace its data with the
             * new one. With this, we re-use old objects instead of creating new ones.
             */
            void add(float pos, long time) {
                mLastIndex = (mLastIndex + 1) % mCapacity;
                if (mValues[mLastIndex] == null) {
                    mValues[mLastIndex] = new GraphValue(pos, time);
                } else {
                    final GraphValue oldValue = mValues[mLastIndex];
                    oldValue.mPos = pos;
                    oldValue.mTime = time;
                }

                // If needed, account for new value in the buffer size.
                if (mSize != mCapacity) {
                    mSize++;
                }
            }

            int getSize() {
                return mSize;
            }

            GraphValue getFirst() {
                final int distanceBetweenLastAndFirst = (mCapacity - mSize) + 1;
                final int firstIndex = (mLastIndex + distanceBetweenLastAndFirst) % mCapacity;
                return mValues[firstIndex];
            }

            GraphValue getLast() {
                return mValues[mLastIndex];
            }

            void removeFirst() {
                mSize--;
            }

            /** Returns an iterator pointing at the last value. */
            Iterator<GraphValue> reverseIterator() {
                mIteratorIndex = mLastIndex;
                mIteratorCount = 1;
                return mReverseIterator;
            }
        }
    }
}
+345 −0

File added.

Preview size limit exceeded, changes collapsed.

+102 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 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.server.input.debug;

import static android.util.TypedValue.COMPLEX_UNIT_SP;

import android.content.Context;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Typeface;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.ViewConfiguration;
import android.widget.TextView;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;

import java.util.Locale;

/** Draws the most recent rotary input value and indicates whether the source is active. */
class RotaryInputValueView extends TextView {

    private static final int INACTIVE_TEXT_COLOR = 0xffff00ff;
    private static final int ACTIVE_TEXT_COLOR = 0xff420f28;
    private static final int TEXT_SIZE_SP = 8;
    private static final int SIDE_PADDING_SP = 4;
    /** Determines how long the active status lasts. */
    private static final int ACTIVE_STATUS_DURATION = 250 /* milliseconds */;
    private static final ColorFilter ACTIVE_BACKGROUND_FILTER =
            new ColorMatrixColorFilter(new float[]{
                    0, 0, 0, 0, 255, // red
                    0, 0, 0, 0,   0, // green
                    0, 0, 0, 0, 255, // blue
                    0, 0, 0, 0, 200  // alpha
            });

    private final Runnable mUpdateActivityStatusCallback = () -> updateActivityStatus(false);
    private final float mScaledVerticalScrollFactor;
    private final Locale mDefaultLocale = Locale.getDefault();

    RotaryInputValueView(Context c) {
        super(c);

        DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
        mScaledVerticalScrollFactor = ViewConfiguration.get(c).getScaledVerticalScrollFactor();

        setText(getFormattedValue(0));
        setTextColor(INACTIVE_TEXT_COLOR);
        setTextSize(applyDimensionSp(TEXT_SIZE_SP, dm));
        setPaddingRelative(applyDimensionSp(SIDE_PADDING_SP, dm), 0,
                applyDimensionSp(SIDE_PADDING_SP, dm), 0);
        setTypeface(null, Typeface.BOLD);
        setBackgroundResource(R.drawable.focus_event_rotary_input_background);
    }

    /** Updates the shown text with the formatted value. */
    void updateValue(float value) {
        removeCallbacks(mUpdateActivityStatusCallback);

        setText(getFormattedValue(value * mScaledVerticalScrollFactor));

        updateActivityStatus(true);
        postDelayed(mUpdateActivityStatusCallback, ACTIVE_STATUS_DURATION);
    }

    @VisibleForTesting
    void updateActivityStatus(boolean active) {
        if (active) {
            setTextColor(ACTIVE_TEXT_COLOR);
            getBackground().setColorFilter(ACTIVE_BACKGROUND_FILTER);
        } else {
            setTextColor(INACTIVE_TEXT_COLOR);
            getBackground().clearColorFilter();
        }
    }

    private String getFormattedValue(float value) {
        return String.format(mDefaultLocale, "%s%.1f", value < 0 ? "-" : "+", Math.abs(value));
    }

    /**
     * Converts a dimension in scaled pixel units to integer display pixels.
     */
    private static int applyDimensionSp(int dimensionSp, DisplayMetrics dm) {
        return (int) TypedValue.applyDimension(COMPLEX_UNIT_SP, dimensionSp, dm);
    }
}
Loading