Merge changes I58a9c06b,I08bedeca into udc-dev

* changes:
  ViewGroup: Send events to the child at the event's dispatch location
  Create FocusEventDebugView to show key presses on screen
This commit is contained in:
Prabir Pradhan
2023-05-09 14:12:50 +00:00
committed by Android (Google) Code Review
13 changed files with 522 additions and 23 deletions

View File

@@ -5452,6 +5452,14 @@ public final class Settings {
@Readable
public static final String SHOW_TOUCHES = "show_touches";
/**
* Show key presses and other events dispatched to focused windows on the screen.
* 0 = no
* 1 = yes
* @hide
*/
public static final String SHOW_KEY_PRESSES = "show_key_presses";
/**
* Log raw orientation data from
* {@link com.android.server.policy.WindowOrientationListener} for use with the
@@ -5842,6 +5850,7 @@ public final class Settings {
PRIVATE_SETTINGS.add(NOTIFICATION_LIGHT_PULSE);
PRIVATE_SETTINGS.add(POINTER_LOCATION);
PRIVATE_SETTINGS.add(SHOW_TOUCHES);
PRIVATE_SETTINGS.add(SHOW_KEY_PRESSES);
PRIVATE_SETTINGS.add(WINDOW_ORIENTATION_LISTENER_LOG);
PRIVATE_SETTINGS.add(POWER_SOUNDS_ENABLED);
PRIVATE_SETTINGS.add(DOCK_SOUNDS_ENABLED);

View File

@@ -4166,6 +4166,40 @@ public final class MotionEvent extends InputEvent implements Parcelable {
nativeWriteToParcel(mNativePtr, out);
}
/**
* Get the x coordinate of the location where the pointer should be dispatched.
*
* This is required because a mouse event, such as from a touchpad, may contain multiple
* pointers that should all be dispatched to the cursor position.
* @hide
*/
public float getXDispatchLocation(int pointerIndex) {
if (isFromSource(InputDevice.SOURCE_MOUSE)) {
final float xCursorPosition = getXCursorPosition();
if (xCursorPosition != INVALID_CURSOR_POSITION) {
return xCursorPosition;
}
}
return getX(pointerIndex);
}
/**
* Get the y coordinate of the location where the pointer should be dispatched.
*
* This is required because a mouse event, such as from a touchpad, may contain multiple
* pointers that should all be dispatched to the cursor position.
* @hide
*/
public float getYDispatchLocation(int pointerIndex) {
if (isFromSource(InputDevice.SOURCE_MOUSE)) {
final float yCursorPosition = getYCursorPosition();
if (yCursorPosition != INVALID_CURSOR_POSITION) {
return yCursorPosition;
}
}
return getY(pointerIndex);
}
/**
* Transfer object for pointer coordinates.
*

View File

@@ -2040,8 +2040,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
final float x = event.getXDispatchLocation(pointerIndex);
final float y = event.getYDispatchLocation(pointerIndex);
if (isOnScrollbarThumb(x, y) || isDraggingScrollBar()) {
return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
}
@@ -2125,8 +2125,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
HoverTarget firstOldHoverTarget = mFirstHoverTarget;
mFirstHoverTarget = null;
if (!interceptHover && action != MotionEvent.ACTION_HOVER_EXIT) {
final float x = event.getX();
final float y = event.getY();
final float x = event.getXDispatchLocation(0);
final float y = event.getYDispatchLocation(0);
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final ArrayList<View> preorderedList = buildOrderedChildList();
@@ -2347,8 +2347,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Check what the child under the pointer says about the tooltip.
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final float x = event.getX();
final float y = event.getY();
final float x = event.getXDispatchLocation(0);
final float y = event.getYDispatchLocation(0);
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
@@ -2443,8 +2443,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
protected boolean pointInHoveredChild(MotionEvent event) {
if (mFirstHoverTarget != null) {
return isTransformedTouchPointInView(event.getX(), event.getY(),
mFirstHoverTarget.child, null);
return isTransformedTouchPointInView(event.getXDispatchLocation(0),
event.getYDispatchLocation(0), mFirstHoverTarget.child, null);
}
return false;
}
@@ -2513,8 +2513,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public boolean onInterceptHoverEvent(MotionEvent event) {
if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
final int action = event.getAction();
final float x = event.getX();
final float y = event.getY();
final float x = event.getXDispatchLocation(0);
final float y = event.getYDispatchLocation(0);
if ((action == MotionEvent.ACTION_HOVER_MOVE
|| action == MotionEvent.ACTION_HOVER_ENTER) && isOnScrollbar(x, y)) {
return true;
@@ -2535,8 +2535,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Send the event to the child under the pointer.
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final float x = event.getX();
final float y = event.getY();
final float x = event.getXDispatchLocation(0);
final float y = event.getXDispatchLocation(0);
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
@@ -2700,10 +2700,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
final float x = ev.getXDispatchLocation(actionIndex);
final float y = ev.getYDispatchLocation(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
@@ -2757,8 +2755,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
mLastTouchDownX = x;
mLastTouchDownY = y;
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
@@ -3287,7 +3285,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
&& isOnScrollbarThumb(ev.getXDispatchLocation(0), ev.getYDispatchLocation(0))) {
return true;
}
return false;

View File

@@ -68,6 +68,7 @@ message SystemSettingsProto {
// orientationplot.py tool.
// 0 = no, 1 = yes
optional SettingProto window_orientation_listener_log = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto show_key_presses = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional DevOptions developer_options = 7;

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:name="focus_event_pressed_key_background"
android:shape="rectangle">
<!-- View background color -->
<solid
android:color="#AA000000" >
</solid>
<!-- View border color and width -->
<stroke
android:width="2dp"
android:color="@android:color/white">
</stroke>
<!-- The radius makes the corners rounded -->
<corners
android:radius="8dp">
</corners>
</shape>

View File

@@ -5119,4 +5119,6 @@
<java-symbol type="style" name="ThemeOverlay.DeviceDefault.Accent" />
<java-symbol type="style" name="ThemeOverlay.DeviceDefault.Accent.Light" />
<java-symbol type="style" name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" />
<java-symbol type="drawable" name="focus_event_pressed_key_background" />
</resources>

View File

@@ -20,6 +20,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -87,6 +88,9 @@ public class ViewGroupTest {
viewGroup.dispatchTouchEvent(event);
verify(viewB).dispatchTouchEvent(event);
viewGroup.onResolvePointerIcon(event, 0 /* pointerIndex */);
verify(viewB).onResolvePointerIcon(event, 0);
event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
MotionEvent.ACTION_POINTER_DOWN | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
2 /* pointerCount */, properties, coords, 0 /* metaState */, 0 /* buttonState */,
@@ -95,7 +99,11 @@ public class ViewGroupTest {
viewGroup.dispatchTouchEvent(event);
verify(viewB).dispatchTouchEvent(event);
viewGroup.onResolvePointerIcon(event, 1 /* pointerIndex */);
verify(viewB).onResolvePointerIcon(event, 1);
verify(viewA, never()).dispatchTouchEvent(any());
verify(viewA, never()).onResolvePointerIcon(any(), anyInt());
}
/**

View File

@@ -183,6 +183,7 @@ public class SystemSettingsValidators {
VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
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.WINDOW_ORIENTATION_LISTENER_LOG, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.LOCKSCREEN_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.LOCKSCREEN_DISABLED, BOOLEAN_VALIDATOR);

View File

@@ -2777,6 +2777,9 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.System.SHOW_TOUCHES,
SystemSettingsProto.DevOptions.SHOW_TOUCHES);
dumpSetting(s, p,
Settings.System.SHOW_KEY_PRESSES,
SystemSettingsProto.DevOptions.SHOW_KEY_PRESSES);
dumpSetting(s, p,
Settings.System.POINTER_LOCATION,
SystemSettingsProto.DevOptions.POINTER_LOCATION);

View File

@@ -77,7 +77,8 @@ public class SettingsBackupTest {
Settings.System.SCREEN_BRIGHTNESS, // removed in P
Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW
Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
Settings.System.SHOW_TOUCHES, // bug?
Settings.System.SHOW_TOUCHES,
Settings.System.SHOW_KEY_PRESSES,
Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
Settings.System.SIP_ALWAYS, // value, not a setting
Settings.System.SYSTEM_LOCALES, // bug?

View File

@@ -0,0 +1,343 @@
/*
* 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 static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static android.util.TypedValue.COMPLEX_UNIT_SP;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.animation.LayoutTransition;
import android.annotation.AnyThread;
import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Typeface;
import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.RoundedCorner;
import android.view.View;
import android.view.WindowInsets;
import android.view.animation.AccelerateInterpolator;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.R;
import java.util.HashMap;
import java.util.Map;
/**
* Displays focus events, such as physical keyboard KeyEvents and non-pointer MotionEvents on
* the screen.
*/
class FocusEventDebugView extends LinearLayout {
private static final String TAG = FocusEventDebugView.class.getSimpleName();
private static final int KEY_FADEOUT_DURATION_MILLIS = 1000;
private static final int KEY_TRANSITION_DURATION_MILLIS = 100;
private static final int OUTER_PADDING_DP = 16;
private static final int KEY_SEPARATION_MARGIN_DP = 16;
private static final int KEY_VIEW_SIDE_PADDING_DP = 16;
private static final int KEY_VIEW_VERTICAL_PADDING_DP = 8;
private static final int KEY_VIEW_MIN_WIDTH_DP = 32;
private static final int KEY_VIEW_TEXT_SIZE_SP = 12;
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;
FocusEventDebugView(Context c) {
super(c);
setFocusableInTouchMode(true);
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
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int paddingBottom = 0;
final RoundedCorner bottomLeft =
insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
if (bottomLeft != null) {
paddingBottom = bottomLeft.getRadius();
}
final RoundedCorner bottomRight =
insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
if (bottomRight != null) {
paddingBottom = Math.max(paddingBottom, bottomRight.getRadius());
}
if (insets.getDisplayCutout() != null) {
paddingBottom =
Math.max(paddingBottom, insets.getDisplayCutout().getSafeInsetBottom());
}
setPadding(mOuterPadding, mOuterPadding, mOuterPadding, mOuterPadding + paddingBottom);
setClipToPadding(false);
invalidate();
return super.onApplyWindowInsets(insets);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
handleKeyEvent(event);
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.
return;
}
post(() -> handleKeyEvent(KeyEvent.obtain((KeyEvent) event)));
}
private void handleKeyEvent(KeyEvent keyEvent) {
final var identifier = new Pair<>(keyEvent.getDeviceId(), keyEvent.getScanCode());
final var container = KeyEvent.isModifierKey(keyEvent.getKeyCode())
? mPressedModifierContainer
: mPressedKeyContainer;
PressedKeyView pressedKeyView = mPressedKeys.get(identifier);
switch (keyEvent.getAction()) {
case KeyEvent.ACTION_DOWN: {
if (pressedKeyView != null) {
if (keyEvent.getRepeatCount() == 0) {
Slog.w(TAG, "Got key down for "
+ KeyEvent.keyCodeToString(keyEvent.getKeyCode())
+ " that was already tracked as being down.");
break;
}
container.handleKeyRepeat(pressedKeyView);
break;
}
pressedKeyView = new PressedKeyView(mContext, getLabel(keyEvent));
mPressedKeys.put(identifier, pressedKeyView);
container.handleKeyPressed(pressedKeyView);
break;
}
case KeyEvent.ACTION_UP: {
if (pressedKeyView == null) {
Slog.w(TAG, "Got key up for " + KeyEvent.keyCodeToString(keyEvent.getKeyCode())
+ " that was not tracked as being down.");
break;
}
mPressedKeys.remove(identifier);
container.handleKeyRelease(pressedKeyView);
break;
}
default:
break;
}
keyEvent.recycle();
}
private static String getLabel(KeyEvent event) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_SPACE:
return "\u2423";
case KeyEvent.KEYCODE_TAB:
return "\u21e5";
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_NUMPAD_ENTER:
return "\u23CE";
case KeyEvent.KEYCODE_DEL:
return "\u232B";
case KeyEvent.KEYCODE_FORWARD_DEL:
return "\u2326";
case KeyEvent.KEYCODE_ESCAPE:
return "ESC";
case KeyEvent.KEYCODE_DPAD_UP:
return "\u2191";
case KeyEvent.KEYCODE_DPAD_DOWN:
return "\u2193";
case KeyEvent.KEYCODE_DPAD_LEFT:
return "\u2190";
case KeyEvent.KEYCODE_DPAD_RIGHT:
return "\u2192";
case KeyEvent.KEYCODE_DPAD_UP_RIGHT:
return "\u2197";
case KeyEvent.KEYCODE_DPAD_UP_LEFT:
return "\u2196";
case KeyEvent.KEYCODE_DPAD_DOWN_RIGHT:
return "\u2198";
case KeyEvent.KEYCODE_DPAD_DOWN_LEFT:
return "\u2199";
default:
break;
}
final int unicodeChar = event.getUnicodeChar();
if (unicodeChar != 0) {
return new String(Character.toChars(unicodeChar));
}
final var label = KeyEvent.keyCodeToString(event.getKeyCode());
if (label.startsWith("KEYCODE_")) {
return label.substring(8);
}
return label;
}
private static class PressedKeyView extends TextView {
private static final ColorFilter sInvertColors = new ColorMatrixColorFilter(new float[]{
-1.0f, 0, 0, 0, 255, // red
0, -1.0f, 0, 0, 255, // green
0, 0, -1.0f, 0, 255, // blue
0, 0, 0, 1.0f, 0 // alpha
});
PressedKeyView(Context c, String label) {
super(c);
final var dm = c.getResources().getDisplayMetrics();
final int keyViewSidePadding =
(int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_VIEW_SIDE_PADDING_DP, dm);
final int keyViewVerticalPadding =
(int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_VIEW_VERTICAL_PADDING_DP,
dm);
final int keyViewMinWidth =
(int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_VIEW_MIN_WIDTH_DP, dm);
final int textSize =
(int) TypedValue.applyDimension(COMPLEX_UNIT_SP, KEY_VIEW_TEXT_SIZE_SP, dm);
setText(label);
setGravity(Gravity.CENTER);
setMinimumWidth(keyViewMinWidth);
setTextSize(textSize);
setTypeface(Typeface.SANS_SERIF);
setBackgroundResource(R.drawable.focus_event_pressed_key_background);
setPaddingRelative(keyViewSidePadding, keyViewVerticalPadding, keyViewSidePadding,
keyViewVerticalPadding);
setHighlighted(true);
}
void setHighlighted(boolean isHighlighted) {
if (isHighlighted) {
setTextColor(Color.BLACK);
getBackground().setColorFilter(sInvertColors);
} else {
setTextColor(Color.WHITE);
getBackground().clearColorFilter();
}
invalidate();
}
}
private static class PressedKeyContainer extends LinearLayout {
private final MarginLayoutParams mPressedKeyLayoutParams;
PressedKeyContainer(Context c) {
super(c);
final var dm = c.getResources().getDisplayMetrics();
final int keySeparationMargin =
(int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_SEPARATION_MARGIN_DP, dm);
final var transition = new LayoutTransition();
transition.disableTransitionType(LayoutTransition.APPEARING);
transition.disableTransitionType(LayoutTransition.DISAPPEARING);
transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
transition.setDuration(KEY_TRANSITION_DURATION_MILLIS);
setLayoutTransition(transition);
mPressedKeyLayoutParams = new MarginLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
if (getOrientation() == VERTICAL) {
mPressedKeyLayoutParams.setMargins(0, keySeparationMargin, 0, 0);
} else {
mPressedKeyLayoutParams.setMargins(keySeparationMargin, 0, 0, 0);
}
}
public void handleKeyPressed(PressedKeyView pressedKeyView) {
addView(pressedKeyView, getChildCount(), mPressedKeyLayoutParams);
invalidate();
}
public void handleKeyRepeat(PressedKeyView repeatedKeyView) {
// Do nothing for now.
}
public void handleKeyRelease(PressedKeyView releasedKeyView) {
releasedKeyView.setHighlighted(false);
releasedKeyView.clearAnimation();
releasedKeyView.animate()
.alpha(0)
.setDuration(KEY_FADEOUT_DURATION_MILLIS)
.setInterpolator(new AccelerateInterpolator())
.withEndAction(this::cleanUpPressedKeyViews)
.start();
}
private void cleanUpPressedKeyViews() {
int numChildrenToRemove = 0;
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if (child.getAlpha() != 0) {
break;
}
child.setVisibility(View.GONE);
child.clearAnimation();
numChildrenToRemove++;
}
removeViews(0, numChildrenToRemove);
invalidate();
}
}
}

View File

@@ -18,6 +18,7 @@ package com.android.server.input;
import static android.provider.DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT;
import static android.view.KeyEvent.KEYCODE_UNKNOWN;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import android.Manifest;
import android.annotation.EnforcePermission;
@@ -32,6 +33,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.Sensors;
@@ -100,6 +102,7 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.view.VerifiedInputEvent;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -386,6 +389,11 @@ public class InputManagerService extends IInputManager.Stub
/** Whether to use the dev/input/event or uevent subsystem for the audio jack. */
final boolean mUseDevInputEventForAudioJack;
private final Object mFocusEventDebugViewLock = new Object();
@GuardedBy("mFocusEventDebugViewLock")
@Nullable
private FocusEventDebugView mFocusEventDebugView;
/** Point of injection for test dependencies. */
@VisibleForTesting
static class Injector {
@@ -427,7 +435,7 @@ public class InputManagerService extends IInputManager.Stub
mContext = injector.getContext();
mHandler = new InputManagerHandler(injector.getLooper());
mNative = injector.getNativeService(this);
mSettingsObserver = new InputSettingsObserver(mContext, mHandler, mNative);
mSettingsObserver = new InputSettingsObserver(mContext, mHandler, this, mNative);
mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
injector.getLooper());
mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
@@ -2460,6 +2468,11 @@ public class InputManagerService extends IInputManager.Stub
// Native callback.
@SuppressWarnings("unused")
private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
synchronized (mFocusEventDebugViewLock) {
if (mFocusEventDebugView != null) {
mFocusEventDebugView.reportEvent(event);
}
}
return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);
}
@@ -3367,6 +3380,45 @@ public class InputManagerService extends IInputManager.Stub
}
}
void updateFocusEventDebugViewEnabled(boolean enabled) {
FocusEventDebugView view;
synchronized (mFocusEventDebugViewLock) {
if (enabled == (mFocusEventDebugView != null)) {
return;
}
if (enabled) {
mFocusEventDebugView = new FocusEventDebugView(mContext);
view = mFocusEventDebugView;
} else {
view = mFocusEventDebugView;
mFocusEventDebugView = null;
}
}
Objects.requireNonNull(view);
// Interact with WM outside the lock, since the lock is part of the input hotpath.
final WindowManager wm =
Objects.requireNonNull(mContext.getSystemService(WindowManager.class));
if (!enabled) {
wm.removeView(view);
return;
}
// TODO: Support multi display
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setFitInsetsTypes(0);
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
lp.format = PixelFormat.TRANSLUCENT;
lp.setTitle("FocusEventDebugView - display " + mContext.getDisplayId());
lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
wm.addView(view, lp);
}
interface KeyboardBacklightControllerInterface {
default void incrementKeyboardBacklight(int deviceId) {}
default void decrementKeyboardBacklight(int deviceId) {}

View File

@@ -43,13 +43,16 @@ class InputSettingsObserver extends ContentObserver {
private final Context mContext;
private final Handler mHandler;
private final InputManagerService mService;
private final NativeInputManagerService mNative;
private final Map<Uri, Consumer<String /* reason*/>> mObservers;
InputSettingsObserver(Context context, Handler handler, NativeInputManagerService nativeIms) {
InputSettingsObserver(Context context, Handler handler, InputManagerService service,
NativeInputManagerService nativeIms) {
super(handler);
mContext = context;
mHandler = handler;
mService = service;
mNative = nativeIms;
mObservers = Map.ofEntries(
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SPEED),
@@ -72,7 +75,9 @@ class InputSettingsObserver extends ContentObserver {
Map.entry(
Settings.Global.getUriFor(
Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH),
(reason) -> updateMaximumObscuringOpacityForTouch()));
(reason) -> updateMaximumObscuringOpacityForTouch()),
Map.entry(Settings.System.getUriFor(Settings.System.SHOW_KEY_PRESSES),
(reason) -> updateShowKeyPresses()));
}
/**
@@ -145,6 +150,11 @@ class InputSettingsObserver extends ContentObserver {
mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
}
private void updateShowKeyPresses() {
mService.updateFocusEventDebugViewEnabled(
getBoolean(Settings.System.SHOW_KEY_PRESSES, false));
}
private void updateAccessibilityLargePointer() {
final int accessibilityConfig = Settings.Secure.getIntForUser(
mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,