diff options
| author | 2024-10-30 16:00:06 +0000 | |
|---|---|---|
| committer | 2024-11-26 21:36:17 +0000 | |
| commit | 2d5b6806e3a184e358bcd4eb873a199ccfa8fdfd (patch) | |
| tree | 4eacf4891d2fdb5815da20ecf30495c562154e04 | |
| parent | eddf0372024d756526a792d9b20735148d858f55 (diff) | |
Add WindowManager flag for capturing the power key event in the foreground.
This is part of a feature to grant Wallet access to the double-click
power button gesture while the app is in the foreground.
Wallet would need to set this flag for specific windows it wishes to
handle the power gesture.
Bug: 357144512
Test: atest WmTests:WindowManagerServiceTests
Flag: com.android.hardware.input.override_power_key_behavior_in_focused_window
Change-Id: I03c97cac407afca81ed193a33524717e0aed92f0
4 files changed, 149 insertions, 1 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 4b83b43bfd8f..deb7f17dc603 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -19037,8 +19037,10 @@ package android.view { public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable { method public final long getUserActivityTimeout(); + method @FlaggedApi("com.android.hardware.input.override_power_key_behavior_in_focused_window") @RequiresPermission(android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) public boolean isReceivePowerKeyDoublePressEnabled(); method public boolean isSystemApplicationOverlay(); method @FlaggedApi("android.companion.virtualdevice.flags.status_bar_and_insets") public void setInsetsParams(@NonNull java.util.List<android.view.WindowManager.InsetsParams>); + method @FlaggedApi("com.android.hardware.input.override_power_key_behavior_in_focused_window") @RequiresPermission(android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) public void setReceivePowerKeyDoublePressEnabled(boolean); method @RequiresPermission(android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY) public void setSystemApplicationOverlay(boolean); method public final void setUserActivityTimeout(long); field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000 diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 1e8cad61381c..101d5c950b71 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -80,6 +80,9 @@ import static android.view.WindowLayoutParamsProto.WINDOW_ANIMATIONS; import static android.view.WindowLayoutParamsProto.X; import static android.view.WindowLayoutParamsProto.Y; +import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW; +import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow; + import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; @@ -4465,6 +4468,29 @@ public interface WindowManager extends ViewManager { public static final int INPUT_FEATURE_SENSITIVE_FOR_PRIVACY = 1 << 3; /** + * Input feature used to indicate that the system should send power key events to this + * window when it's in the foreground. The window can override the double press power key + * gesture behavior. + * + * A double press gesture is defined as two + * {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)} events within a time span defined by + * {@link ViewConfiguration#getMultiPressTimeout()}. + * + * Note: While the window may receive all power key {@link KeyEvent}s, it can only + * override the double press gesture behavior. The system will perform default behavior for + * single, long-press and other multi-press gestures, regardless of if the app handles the + * key or not. + * + * To override the default behavior for double press, the app must return true for the + * second {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)}. If the app returns false, the + * system behavior will be performed for double press. + * @hide + */ + @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) + public static final int + INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS = 1 << 4; + + /** * An internal annotation for flags that can be specified to {@link #inputFeatures}. * * NOTE: These are not the same as {@link android.os.InputConfig} flags. @@ -4477,6 +4503,7 @@ public interface WindowManager extends ViewManager { INPUT_FEATURE_DISABLE_USER_ACTIVITY, INPUT_FEATURE_SPY, INPUT_FEATURE_SENSITIVE_FOR_PRIVACY, + INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS }) public @interface InputFeatureFlags { } @@ -4766,6 +4793,44 @@ public interface WindowManager extends ViewManager { } /** + * Specifies if the system should send power key events to this window when it's in the + * foreground, with only the double tap gesture behavior being overrideable. + * + * @param enabled if true, the system should send power key events to this window when it's + * in the foreground, with only the power key double tap gesture being + * overrideable. + * @hide + */ + @SystemApi + @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) + @FlaggedApi(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) + public void setReceivePowerKeyDoublePressEnabled(boolean enabled) { + if (enabled) { + inputFeatures + |= INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS; + } else { + inputFeatures + &= ~INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS; + } + } + + /** + * Returns whether or not the system should send power key events to this window when it's + * in the foreground, with only the double tap gesture being overrideable. + * + * @return if the system should send power key events to this window when it's in the + * foreground, with only the double tap gesture being overrideable. + * @hide + */ + @SystemApi + @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) + @FlaggedApi(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) + public boolean isReceivePowerKeyDoublePressEnabled() { + return (inputFeatures + & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS) != 0; + } + + /** * Specifies that the window should be considered a trusted system overlay. Trusted system * overlays are ignored when considering whether windows are obscured during input * dispatch. Requires the {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} @@ -6157,6 +6222,16 @@ public interface WindowManager extends ViewManager { inputFeatures &= ~INPUT_FEATURE_SPY; features.add("INPUT_FEATURE_SPY"); } + if (overridePowerKeyBehaviorInFocusedWindow()) { + if ((inputFeatures + & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS) + != 0) { + inputFeatures + &= + ~INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS; + features.add("INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS"); + } + } if (inputFeatures != 0) { features.add(Integer.toHexString(inputFeatures)); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a0c0b9836507..ab065229d359 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -69,6 +69,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; @@ -101,6 +102,7 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER import static android.view.flags.Flags.sensitiveContentAppProtection; import static android.window.WindowProviderService.isWindowProviderService; +import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BOOT; @@ -157,9 +159,9 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.INPUT_METHOD_W import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY; import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER; import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID; +import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; import static com.android.window.flags.Flags.multiCrop; import static com.android.window.flags.Flags.setScPropertiesInClient; -import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; import android.Manifest; import android.Manifest.permission; @@ -9217,6 +9219,25 @@ public class WindowManagerService extends IWindowManager.Stub + "' because it isn't a trusted overlay"); return inputFeatures & ~INPUT_FEATURE_SENSITIVE_FOR_PRIVACY; } + + // You need OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission to be able + // to set INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS. + if (overridePowerKeyBehaviorInFocusedWindow() + && (inputFeatures + & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS) + != 0) { + final int powerPermissionResult = + mContext.checkPermission( + permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW, + callingPid, + callingUid); + if (powerPermissionResult != PackageManager.PERMISSION_GRANTED) { + throw new IllegalArgumentException( + "Cannot use INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS from" + windowName + + " because it doesn't have the" + + " OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission"); + } + } return inputFeatures; } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 8bbba1b17240..0ccdb6174279 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -29,6 +29,7 @@ import static android.view.Display.FLAG_OWN_FOCUS; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; @@ -47,6 +48,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW; import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; @@ -76,6 +78,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityThread; import android.app.IApplicationThread; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; @@ -1139,6 +1142,53 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test + @RequiresFlagsEnabled(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) + public void testUpdateInputChannel_sanitizeWithoutPermission_ThrowsError() { + final Session session = mock(Session.class); + final int callingUid = Process.FIRST_APPLICATION_UID; + final int callingPid = 1234; + final SurfaceControl surfaceControl = mock(SurfaceControl.class); + final IBinder window = new Binder(); + final InputTransferToken inputTransferToken = mock(InputTransferToken.class); + + + final InputChannel inputChannel = new InputChannel(); + + assertThrows(IllegalArgumentException.class, () -> + mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, + surfaceControl, window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, + 0 /* privateFlags */, + INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS, + TYPE_APPLICATION, null /* windowToken */, inputTransferToken, + "TestInputChannel", inputChannel)); + } + + + @Test + @RequiresFlagsEnabled(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) + public void testUpdateInputChannel_sanitizeWithPermission_doesNotThrowError() { + final Session session = mock(Session.class); + final int callingUid = Process.FIRST_APPLICATION_UID; + final int callingPid = 1234; + final SurfaceControl surfaceControl = mock(SurfaceControl.class); + final IBinder window = new Binder(); + final InputTransferToken inputTransferToken = mock(InputTransferToken.class); + + doReturn(PackageManager.PERMISSION_GRANTED).when(mWm.mContext).checkPermission( + android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW, + callingPid, + callingUid); + + final InputChannel inputChannel = new InputChannel(); + + mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl, + window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, 0 /* privateFlags */, + INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS, + TYPE_APPLICATION, null /* windowToken */, inputTransferToken, "TestInputChannel", + inputChannel); + } + + @Test public void testUpdateInputChannel_allowSpyWindowForInputMonitorPermission() { final Session session = mock(Session.class); final int callingUid = Process.SYSTEM_UID; |