diff options
7 files changed, 207 insertions, 32 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index bf6e1c4a0520..50ebab751574 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5627,7 +5627,7 @@ 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 @@ -5635,6 +5635,14 @@ public final class Settings { 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 * orientationplot.py tool. diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 0dd85695b07a..80cf6c313316 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -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); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 467855996310..c697c1ff66ec 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -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? diff --git a/services/core/java/com/android/server/input/FocusEventDebugGlobalMonitor.java b/services/core/java/com/android/server/input/FocusEventDebugGlobalMonitor.java new file mode 100644 index 000000000000..67c221f77037 --- /dev/null +++ b/services/core/java/com/android/server/input/FocusEventDebugGlobalMonitor.java @@ -0,0 +1,48 @@ +/* + * 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); + } + } +} diff --git a/services/core/java/com/android/server/input/FocusEventDebugView.java b/services/core/java/com/android/server/input/FocusEventDebugView.java index fba2aa60b952..02e0eb053c88 100644 --- a/services/core/java/com/android/server/input/FocusEventDebugView.java +++ b/services/core/java/com/android/server/input/FocusEventDebugView.java @@ -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[]{ diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 6241ebbc60cc..b8e9d5dfb3bc 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -390,6 +390,8 @@ public class InputManagerService extends IInputManager.Stub @GuardedBy("mFocusEventDebugViewLock") @Nullable private FocusEventDebugView mFocusEventDebugView; + private boolean mShowKeyPresses = false; + private boolean mShowRotaryInput = false; /** Point of injection for test dependencies. */ @VisibleForTesting @@ -2476,7 +2478,7 @@ public class InputManagerService extends IInputManager.Stub private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { synchronized (mFocusEventDebugViewLock) { if (mFocusEventDebugView != null) { - mFocusEventDebugView.reportEvent(event); + mFocusEventDebugView.reportKeyEvent(event); } } return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags); @@ -3396,14 +3398,45 @@ public class InputManagerService extends IInputManager.Stub mWindowManagerCallbacks.notifyPointerLocationChanged(enabled); } - void updateFocusEventDebugViewEnabled(boolean enabled) { + void updateShowKeyPresses(boolean enabled) { + if (mShowKeyPresses == enabled) { + return; + } + + mShowKeyPresses = enabled; + updateFocusEventDebugViewEnabled(); + + synchronized (mFocusEventDebugViewLock) { + if (mFocusEventDebugView != null) { + mFocusEventDebugView.updateShowKeyPresses(enabled); + } + } + } + + void updateShowRotaryInput(boolean enabled) { + if (mShowRotaryInput == enabled) { + return; + } + + mShowRotaryInput = enabled; + updateFocusEventDebugViewEnabled(); + + synchronized (mFocusEventDebugViewLock) { + if (mFocusEventDebugView != null) { + mFocusEventDebugView.updateShowRotaryInput(enabled); + } + } + } + + private void updateFocusEventDebugViewEnabled() { + boolean enabled = mShowKeyPresses || mShowRotaryInput; FocusEventDebugView view; synchronized (mFocusEventDebugViewLock) { if (enabled == (mFocusEventDebugView != null)) { return; } if (enabled) { - mFocusEventDebugView = new FocusEventDebugView(mContext); + mFocusEventDebugView = new FocusEventDebugView(mContext, this); view = mFocusEventDebugView; } else { view = mFocusEventDebugView; diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index cf7c692d9a54..aab491ee1def 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -86,7 +86,9 @@ class InputSettingsObserver extends ContentObserver { Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_TIMEOUT_MS), (reason) -> updateKeyRepeatInfo(getLatestLongPressTimeoutValue())), Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_DELAY_MS), - (reason) -> updateKeyRepeatInfo(getLatestLongPressTimeoutValue()))); + (reason) -> updateKeyRepeatInfo(getLatestLongPressTimeoutValue())), + Map.entry(Settings.System.getUriFor(Settings.System.SHOW_ROTARY_INPUT), + (reason) -> updateShowRotaryInput())); } /** @@ -164,8 +166,11 @@ class InputSettingsObserver extends ContentObserver { } private void updateShowKeyPresses() { - mService.updateFocusEventDebugViewEnabled( - getBoolean(Settings.System.SHOW_KEY_PRESSES, false)); + mService.updateShowKeyPresses(getBoolean(Settings.System.SHOW_KEY_PRESSES, false)); + } + + private void updateShowRotaryInput() { + mService.updateShowRotaryInput(getBoolean(Settings.System.SHOW_ROTARY_INPUT, false)); } private void updateAccessibilityLargePointer() { |