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

Commit f09f083e authored by Iago Mendes's avatar Iago Mendes
Browse files

Listen to rotary input events and report them to FocusEventDebugView.

A system setting key was implemented for showing rotary input
(temporarily only by logging it). To show this input, use the following
command:

adb shell settings put system show_rotary_input 1

Bug: 286086154
Test: manual
Change-Id: If8f817796eefc12874a00f41549e555e8e774b25
parent a3594881
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -5627,13 +5627,21 @@ public final class Settings {
        public static final String SHOW_TOUCHES = "show_touches";
        /**
         * Show key presses and other events dispatched to focused windows on the screen.
         * Show key presses dispatched to focused windows on the screen.
         * 0 = no
         * 1 = yes
         * @hide
         */
        public static final String SHOW_KEY_PRESSES = "show_key_presses";
        /**
         * Show rotary input dispatched to focused windows on the screen.
         * 0 = no
         * 1 = yes
         * @hide
         */
        public static final String SHOW_ROTARY_INPUT = "show_rotary_input";
        /**
         * Log raw orientation data from
         * {@link com.android.server.policy.WindowOrientationListener} for use with the
+1 −0
Original line number Diff line number Diff line
@@ -184,6 +184,7 @@ public class SystemSettingsValidators {
        VALIDATORS.put(System.POINTER_LOCATION, BOOLEAN_VALIDATOR);
        VALIDATORS.put(System.SHOW_TOUCHES, BOOLEAN_VALIDATOR);
        VALIDATORS.put(System.SHOW_KEY_PRESSES, BOOLEAN_VALIDATOR);
        VALIDATORS.put(System.SHOW_ROTARY_INPUT, BOOLEAN_VALIDATOR);
        VALIDATORS.put(System.WINDOW_ORIENTATION_LISTENER_LOG, BOOLEAN_VALIDATOR);
        VALIDATORS.put(System.LOCKSCREEN_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
        VALIDATORS.put(System.LOCKSCREEN_DISABLED, BOOLEAN_VALIDATOR);
+1 −0
Original line number Diff line number Diff line
@@ -79,6 +79,7 @@ public class SettingsBackupTest {
                    Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
                    Settings.System.SHOW_TOUCHES,
                    Settings.System.SHOW_KEY_PRESSES,
                    Settings.System.SHOW_ROTARY_INPUT,
                    Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
                    Settings.System.SIP_ALWAYS, // value, not a setting
                    Settings.System.SYSTEM_LOCALES, // bug?
+48 −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;

import android.view.Display;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.MotionEvent;

import com.android.server.UiThread;

/**
 * Receives input events before they are dispatched and reports them to FocusEventDebugView.
 */
class FocusEventDebugGlobalMonitor extends InputEventReceiver {
    private final FocusEventDebugView mDebugView;

    FocusEventDebugGlobalMonitor(FocusEventDebugView debugView, InputManagerService service) {
        super(service.monitorInput("FocusEventDebugGlobalMonitor", Display.DEFAULT_DISPLAY),
            UiThread.getHandler().getLooper());
        mDebugView = debugView;
    }

    @Override
    public void onInputEvent(InputEvent event) {
        try {
            if (event instanceof MotionEvent) {
                mDebugView.reportMotionEvent((MotionEvent) event);
            }
        } finally {
            finishInputEvent(event, false);
        }
    }
}
+104 −25
Original line number Diff line number Diff line
@@ -22,17 +22,20 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

import android.animation.LayoutTransition;
import android.annotation.AnyThread;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Typeface;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.InputEvent;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RoundedCorner;
import android.view.View;
import android.view.WindowInsets;
@@ -64,42 +67,31 @@ class FocusEventDebugView extends LinearLayout {
    private static final int KEY_VIEW_MIN_WIDTH_DP = 32;
    private static final int KEY_VIEW_TEXT_SIZE_SP = 12;

    private final InputManagerService mService;
    private final int mOuterPadding;

    // Tracks all keys that are currently pressed/down.
    private final Map<Pair<Integer /*deviceId*/, Integer /*scanCode*/>, PressedKeyView>
            mPressedKeys = new HashMap<>();

    private final PressedKeyContainer mPressedKeyContainer;
    private final PressedKeyContainer mPressedModifierContainer;
    @Nullable
    private FocusEventDebugGlobalMonitor mFocusEventDebugGlobalMonitor;
    @Nullable
    private PressedKeyContainer mPressedKeyContainer;
    @Nullable
    private PressedKeyContainer mPressedModifierContainer;

    FocusEventDebugView(Context c) {
    FocusEventDebugView(Context c, InputManagerService service) {
        super(c);
        setFocusableInTouchMode(true);

        mService = service;
        final var dm = mContext.getResources().getDisplayMetrics();
        mOuterPadding = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, OUTER_PADDING_DP, dm);

        setOrientation(HORIZONTAL);
        setLayoutDirection(LAYOUT_DIRECTION_RTL);
        setGravity(Gravity.START | Gravity.BOTTOM);

        mPressedKeyContainer = new PressedKeyContainer(mContext);
        mPressedKeyContainer.setOrientation(HORIZONTAL);
        mPressedKeyContainer.setGravity(Gravity.RIGHT | Gravity.BOTTOM);
        mPressedKeyContainer.setLayoutDirection(LAYOUT_DIRECTION_LTR);
        final var scroller = new HorizontalScrollView(mContext);
        scroller.addView(mPressedKeyContainer);
        scroller.setHorizontalScrollBarEnabled(false);
        scroller.addOnLayoutChangeListener(
                (view, l, t, r, b, ol, ot, or, ob) -> scroller.fullScroll(View.FOCUS_RIGHT));
        scroller.setHorizontalFadingEdgeEnabled(true);
        addView(scroller, new LayoutParams(0, WRAP_CONTENT, 1));

        mPressedModifierContainer = new PressedKeyContainer(mContext);
        mPressedModifierContainer.setOrientation(VERTICAL);
        mPressedModifierContainer.setGravity(Gravity.LEFT | Gravity.BOTTOM);
        addView(mPressedModifierContainer, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
    }

    @Override
@@ -135,17 +127,82 @@ class FocusEventDebugView extends LinearLayout {
        return super.dispatchKeyEvent(event);
    }

    /** Report an input event to the debug view. */
    @AnyThread
    public void reportEvent(InputEvent event) {
        if (!(event instanceof KeyEvent)) {
            // TODO: Support non-pointer MotionEvents.
    public void updateShowKeyPresses(boolean enabled) {
        post(() -> handleUpdateShowKeyPresses(enabled));
    }

    @AnyThread
    public void updateShowRotaryInput(boolean enabled) {
        post(() -> handleUpdateShowRotaryInput(enabled));
    }

    private void handleUpdateShowKeyPresses(boolean enabled) {
        if (enabled == showKeyPresses()) {
            return;
        }

        if (!enabled) {
            removeView(mPressedKeyContainer);
            mPressedKeyContainer = null;
            removeView(mPressedModifierContainer);
            mPressedModifierContainer = null;
            return;
        }

        mPressedKeyContainer = new PressedKeyContainer(mContext);
        mPressedKeyContainer.setOrientation(HORIZONTAL);
        mPressedKeyContainer.setGravity(Gravity.RIGHT | Gravity.BOTTOM);
        mPressedKeyContainer.setLayoutDirection(LAYOUT_DIRECTION_LTR);
        final var scroller = new HorizontalScrollView(mContext);
        scroller.addView(mPressedKeyContainer);
        scroller.setHorizontalScrollBarEnabled(false);
        scroller.addOnLayoutChangeListener(
                (view, l, t, r, b, ol, ot, or, ob) -> scroller.fullScroll(View.FOCUS_RIGHT));
        scroller.setHorizontalFadingEdgeEnabled(true);
        addView(scroller, new LayoutParams(0, WRAP_CONTENT, 1));

        mPressedModifierContainer = new PressedKeyContainer(mContext);
        mPressedModifierContainer.setOrientation(VERTICAL);
        mPressedModifierContainer.setGravity(Gravity.LEFT | Gravity.BOTTOM);
        addView(mPressedModifierContainer, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
    }

    private void handleUpdateShowRotaryInput(boolean enabled) {
        if (enabled == showRotaryInput()) {
            return;
        }

        if (!enabled) {
            mFocusEventDebugGlobalMonitor.dispose();
            mFocusEventDebugGlobalMonitor = null;
            return;
        }

        mFocusEventDebugGlobalMonitor = new FocusEventDebugGlobalMonitor(this, mService);
    }

    /** Report a key event to the debug view. */
    @AnyThread
    public void reportKeyEvent(KeyEvent event) {
        post(() -> handleKeyEvent(KeyEvent.obtain((KeyEvent) event)));
    }

    /** Report a motion event to the debug view. */
    @AnyThread
    public void reportMotionEvent(MotionEvent event) {
        if (event.getSource() != InputDevice.SOURCE_ROTARY_ENCODER) {
            return;
        }

        post(() -> handleRotaryInput(MotionEvent.obtain((MotionEvent) event)));
    }

    private void handleKeyEvent(KeyEvent keyEvent) {
        if (!showKeyPresses()) {
            return;
        }

        final var identifier = new Pair<>(keyEvent.getDeviceId(), keyEvent.getScanCode());
        final var container = KeyEvent.isModifierKey(keyEvent.getKeyCode())
                ? mPressedModifierContainer
@@ -185,6 +242,18 @@ class FocusEventDebugView extends LinearLayout {
        keyEvent.recycle();
    }

    private void handleRotaryInput(MotionEvent motionEvent) {
        if (!showRotaryInput()) {
            return;
        }

        float scrollAxisValue = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL);
        // TODO(b/286086154): replace log with visualization.
        Log.d(TAG, "ROTARY INPUT: " + String.valueOf(scrollAxisValue));

        motionEvent.recycle();
    }

    private static String getLabel(KeyEvent event) {
        switch (event.getKeyCode()) {
            case KeyEvent.KEYCODE_SPACE:
@@ -232,6 +301,16 @@ class FocusEventDebugView extends LinearLayout {
        return label;
    }

    /** Determine whether to show key presses by checking one of the key-related objects. */
    private boolean showKeyPresses() {
        return mPressedKeyContainer != null;
    }

    /** Determine whether to show rotary input by checking one of the rotary-related objects. */
    private boolean showRotaryInput() {
        return mFocusEventDebugGlobalMonitor != null;
    }

    private static class PressedKeyView extends TextView {

        private static final ColorFilter sInvertColors = new ColorMatrixColorFilter(new float[]{
Loading