Loading core/java/android/hardware/input/AidlInputGestureData.aidl 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * Copyright 2024 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 android.hardware.input; /** @hide */ @JavaDerive(equals=true) parcelable AidlInputGestureData { int keycode; int modifierState; int gestureType; // App launch parameters: Only set if gestureType is KEY_GESTURE_TYPE_LAUNCH_APPLICATION String appLaunchCategory; String appLaunchRole; String appLaunchPackageName; String appLaunchClassName; } core/java/android/hardware/input/IInputManager.aidl +18 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.hardware.input; import android.graphics.Rect; import android.hardware.input.AidlInputGestureData; import android.hardware.input.HostUsiVersion; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.KeyboardLayout; Loading Loading @@ -261,4 +262,21 @@ interface IInputManager { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") void unregisterKeyGestureHandler(IKeyGestureHandler handler); @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") int addCustomInputGesture(in AidlInputGestureData data); @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") int removeCustomInputGesture(in AidlInputGestureData data); @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") void removeAllCustomInputGestures(); AidlInputGestureData[] getCustomInputGestures(); } core/java/android/hardware/input/InputGestureData.java 0 → 100644 +249 −0 Original line number Diff line number Diff line /* * Copyright 2024 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 android.hardware.input; import android.annotation.NonNull; import android.annotation.Nullable; import android.view.KeyEvent; import java.util.Objects; /** * Data class to store input gesture data. * * <p> * All input gestures are of type Trigger -> Action(Key gesture type, app data). And currently types * of triggers supported are: * - KeyTrigger (Keycode + modifierState) * - TODO(b/365064144): Add Touchpad gesture based trigger * </p> * @hide */ public final class InputGestureData { @NonNull private final AidlInputGestureData mInputGestureData; public InputGestureData(AidlInputGestureData inputGestureData) { this.mInputGestureData = inputGestureData; validate(); } /** Returns the trigger information for this input gesture */ public Trigger getTrigger() { if (mInputGestureData.keycode != KeyEvent.KEYCODE_UNKNOWN) { return new KeyTrigger(mInputGestureData.keycode, mInputGestureData.modifierState); } throw new RuntimeException("InputGestureData is corrupted, invalid trigger type!"); } /** Returns the action to perform for this input gesture */ public Action getAction() { return new Action(mInputGestureData.gestureType, getAppLaunchData()); } private void validate() { Trigger trigger = getTrigger(); Action action = getAction(); if (trigger == null) { throw new IllegalArgumentException("No trigger found"); } if (action.keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) { throw new IllegalArgumentException("No system action found"); } if (action.keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION && action.appLaunchData == null) { throw new IllegalArgumentException( "No app launch data for system action launch application"); } } public AidlInputGestureData getAidlData() { return mInputGestureData; } @Nullable private AppLaunchData getAppLaunchData() { if (mInputGestureData.gestureType != KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION) { return null; } return AppLaunchData.createLaunchData(mInputGestureData.appLaunchCategory, mInputGestureData.appLaunchRole, mInputGestureData.appLaunchPackageName, mInputGestureData.appLaunchClassName); } /** Builder class for creating {@link InputGestureData} */ public static class Builder { @Nullable private Trigger mTrigger = null; private int mKeyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED; @Nullable private AppLaunchData mAppLaunchData = null; /** Set input gesture trigger data for key based gestures */ public Builder setTrigger(Trigger trigger) { mTrigger = trigger; return this; } /** Set input gesture system action */ public Builder setKeyGestureType(@KeyGestureEvent.KeyGestureType int keyGestureType) { mKeyGestureType = keyGestureType; return this; } /** Set input gesture system action as launching a target app */ public Builder setAppLaunchData(@NonNull AppLaunchData appLaunchData) { mKeyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION; mAppLaunchData = appLaunchData; return this; } /** Creates {@link android.hardware.input.InputGestureData} based on data provided */ public InputGestureData build() throws IllegalArgumentException { if (mTrigger == null) { throw new IllegalArgumentException("No trigger found"); } if (mKeyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) { throw new IllegalArgumentException("No system action found"); } if (mKeyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION && mAppLaunchData == null) { throw new IllegalArgumentException( "No app launch data for system action launch application"); } AidlInputGestureData data = new AidlInputGestureData(); if (mTrigger instanceof KeyTrigger keyTrigger) { data.keycode = keyTrigger.getKeycode(); data.modifierState = keyTrigger.getModifierState(); } else { throw new IllegalArgumentException("Invalid trigger type!"); } data.gestureType = mKeyGestureType; if (mAppLaunchData != null) { if (mAppLaunchData instanceof AppLaunchData.CategoryData categoryData) { data.appLaunchCategory = categoryData.getCategory(); } else if (mAppLaunchData instanceof AppLaunchData.RoleData roleData) { data.appLaunchRole = roleData.getRole(); } else if (mAppLaunchData instanceof AppLaunchData.ComponentData componentData) { data.appLaunchPackageName = componentData.getPackageName(); data.appLaunchClassName = componentData.getClassName(); } else { throw new IllegalArgumentException("AppLaunchData type is invalid!"); } } return new InputGestureData(data); } } @Override public String toString() { return "InputGestureData { " + "trigger = " + getTrigger() + ", action = " + getAction() + " }"; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InputGestureData that = (InputGestureData) o; return mInputGestureData.keycode == that.mInputGestureData.keycode && mInputGestureData.modifierState == that.mInputGestureData.modifierState && mInputGestureData.gestureType == that.mInputGestureData.gestureType && Objects.equals(mInputGestureData.appLaunchCategory, that.mInputGestureData.appLaunchCategory) && Objects.equals(mInputGestureData.appLaunchRole, that.mInputGestureData.appLaunchRole) && Objects.equals(mInputGestureData.appLaunchPackageName, that.mInputGestureData.appLaunchPackageName) && Objects.equals(mInputGestureData.appLaunchPackageName, that.mInputGestureData.appLaunchPackageName); } @Override public int hashCode() { int _hash = 1; _hash = 31 * _hash + mInputGestureData.keycode; _hash = 31 * _hash + mInputGestureData.modifierState; _hash = 31 * _hash + mInputGestureData.gestureType; _hash = 31 * _hash + (mInputGestureData.appLaunchCategory != null ? mInputGestureData.appLaunchCategory.hashCode() : 0); _hash = 31 * _hash + (mInputGestureData.appLaunchRole != null ? mInputGestureData.appLaunchRole.hashCode() : 0); _hash = 31 * _hash + (mInputGestureData.appLaunchPackageName != null ? mInputGestureData.appLaunchPackageName.hashCode() : 0); _hash = 31 * _hash + (mInputGestureData.appLaunchPackageName != null ? mInputGestureData.appLaunchPackageName.hashCode() : 0); return _hash; } public interface Trigger { } /** Creates a input gesture trigger based on a key press */ public static Trigger createKeyTrigger(int keycode, int modifierState) { return new KeyTrigger(keycode, modifierState); } /** Key based input gesture trigger */ public static class KeyTrigger implements Trigger { private static final int SHORTCUT_META_MASK = KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON; private final int mKeycode; private final int mModifierState; private KeyTrigger(int keycode, int modifierState) { if (keycode <= KeyEvent.KEYCODE_UNKNOWN || keycode > KeyEvent.getMaxKeyCode()) { throw new IllegalArgumentException("Invalid keycode = " + keycode); } mKeycode = keycode; mModifierState = modifierState; } public int getKeycode() { return mKeycode; } public int getModifierState() { return mModifierState; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof KeyTrigger that)) return false; return mKeycode == that.mKeycode && mModifierState == that.mModifierState; } @Override public int hashCode() { return Objects.hash(mKeycode, mModifierState); } @Override public String toString() { return "KeyTrigger{" + "mKeycode=" + KeyEvent.keyCodeToString(mKeycode) + ", mModifierState=" + mModifierState + '}'; } } /** Data for action to perform when input gesture is triggered */ public record Action(@KeyGestureEvent.KeyGestureType int keyGestureType, @Nullable AppLaunchData appLaunchData) { } } core/java/android/hardware/input/InputManager.java +125 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.hardware.input; import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API; import static com.android.input.flags.Flags.FLAG_DEVICE_ASSOCIATIONS; import static com.android.hardware.input.Flags.enableCustomizableInputGestures; import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag; import static com.android.hardware.input.Flags.keyboardGlyphMap; Loading Loading @@ -257,6 +258,52 @@ public final class InputManager { int REMAPPABLE_MODIFIER_KEY_CAPS_LOCK = KeyEvent.KEYCODE_CAPS_LOCK; } /** * Custom input gesture error: Input gesture already exists * * @hide */ public static final int CUSTOM_INPUT_GESTURE_RESULT_SUCCESS = 1; /** * Custom input gesture error: Input gesture already exists * * @hide */ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS = 2; /** * Custom input gesture error: Input gesture does not exist * * @hide */ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST = 3; /** * Custom input gesture error: Input gesture is reserved for system action * * @hide */ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE = 4; /** * Custom input gesture error: Failure error code for all other errors/warnings * * @hide */ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER = 5; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "CUSTOM_INPUT_GESTURE_RESULT_" }, value = { CUSTOM_INPUT_GESTURE_RESULT_SUCCESS, CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS, CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST, CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE, CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER, }) public @interface CustomInputGestureResult {} /** * Switch State: Unknown. * Loading Loading @@ -1432,6 +1479,84 @@ public final class InputManager { mGlobal.unregisterKeyGestureEventHandler(handler); } /** Adds a new custom input gesture * * @param inputGestureData gesture data to add as custom gesture * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) @CustomInputGestureResult public int addCustomInputGesture(@NonNull InputGestureData inputGestureData) { if (!enableCustomizableInputGestures()) { return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } try { return mIm.addCustomInputGesture(inputGestureData.getAidlData()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } /** Removes an existing custom gesture * * <p> NOTE: Should not be used to remove system gestures. This API is only to be used to * remove gestures added using {@link #addCustomInputGesture(InputGestureData)} * * @param inputGestureData gesture data for the existing custom gesture to remove * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) @CustomInputGestureResult public int removeCustomInputGesture(@NonNull InputGestureData inputGestureData) { if (!enableCustomizableInputGestures()) { return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } try { return mIm.removeCustomInputGesture(inputGestureData.getAidlData()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } /** Removes all custom input gestures * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) public void removeAllCustomInputGestures() { if (!enableCustomizableInputGestures()) { return; } try { mIm.removeAllCustomInputGestures(); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** Get all custom input gestures * * @hide */ public List<InputGestureData> getCustomInputGestures() { List<InputGestureData> result = new ArrayList<>(); if (!enableCustomizableInputGestures()) { return result; } try { for (AidlInputGestureData data : mIm.getCustomInputGestures()) { result.add(new InputGestureData(data)); } } catch (RemoteException e) { e.rethrowFromSystemServer(); } return result; } /** * A callback used to be notified about battery state changes for an input device. The * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the Loading core/java/android/hardware/input/input_framework.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -142,6 +142,13 @@ flag { bug: "373458181" } flag { name: "enable_customizable_input_gestures" namespace: "input" description: "Enables keyboard shortcut customization support" bug: "365064144" } flag { name: "override_power_key_behavior_in_focused_window" namespace: "input_native" Loading Loading
core/java/android/hardware/input/AidlInputGestureData.aidl 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * Copyright 2024 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 android.hardware.input; /** @hide */ @JavaDerive(equals=true) parcelable AidlInputGestureData { int keycode; int modifierState; int gestureType; // App launch parameters: Only set if gestureType is KEY_GESTURE_TYPE_LAUNCH_APPLICATION String appLaunchCategory; String appLaunchRole; String appLaunchPackageName; String appLaunchClassName; }
core/java/android/hardware/input/IInputManager.aidl +18 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.hardware.input; import android.graphics.Rect; import android.hardware.input.AidlInputGestureData; import android.hardware.input.HostUsiVersion; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.KeyboardLayout; Loading Loading @@ -261,4 +262,21 @@ interface IInputManager { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") void unregisterKeyGestureHandler(IKeyGestureHandler handler); @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") int addCustomInputGesture(in AidlInputGestureData data); @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") int removeCustomInputGesture(in AidlInputGestureData data); @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") void removeAllCustomInputGestures(); AidlInputGestureData[] getCustomInputGestures(); }
core/java/android/hardware/input/InputGestureData.java 0 → 100644 +249 −0 Original line number Diff line number Diff line /* * Copyright 2024 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 android.hardware.input; import android.annotation.NonNull; import android.annotation.Nullable; import android.view.KeyEvent; import java.util.Objects; /** * Data class to store input gesture data. * * <p> * All input gestures are of type Trigger -> Action(Key gesture type, app data). And currently types * of triggers supported are: * - KeyTrigger (Keycode + modifierState) * - TODO(b/365064144): Add Touchpad gesture based trigger * </p> * @hide */ public final class InputGestureData { @NonNull private final AidlInputGestureData mInputGestureData; public InputGestureData(AidlInputGestureData inputGestureData) { this.mInputGestureData = inputGestureData; validate(); } /** Returns the trigger information for this input gesture */ public Trigger getTrigger() { if (mInputGestureData.keycode != KeyEvent.KEYCODE_UNKNOWN) { return new KeyTrigger(mInputGestureData.keycode, mInputGestureData.modifierState); } throw new RuntimeException("InputGestureData is corrupted, invalid trigger type!"); } /** Returns the action to perform for this input gesture */ public Action getAction() { return new Action(mInputGestureData.gestureType, getAppLaunchData()); } private void validate() { Trigger trigger = getTrigger(); Action action = getAction(); if (trigger == null) { throw new IllegalArgumentException("No trigger found"); } if (action.keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) { throw new IllegalArgumentException("No system action found"); } if (action.keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION && action.appLaunchData == null) { throw new IllegalArgumentException( "No app launch data for system action launch application"); } } public AidlInputGestureData getAidlData() { return mInputGestureData; } @Nullable private AppLaunchData getAppLaunchData() { if (mInputGestureData.gestureType != KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION) { return null; } return AppLaunchData.createLaunchData(mInputGestureData.appLaunchCategory, mInputGestureData.appLaunchRole, mInputGestureData.appLaunchPackageName, mInputGestureData.appLaunchClassName); } /** Builder class for creating {@link InputGestureData} */ public static class Builder { @Nullable private Trigger mTrigger = null; private int mKeyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED; @Nullable private AppLaunchData mAppLaunchData = null; /** Set input gesture trigger data for key based gestures */ public Builder setTrigger(Trigger trigger) { mTrigger = trigger; return this; } /** Set input gesture system action */ public Builder setKeyGestureType(@KeyGestureEvent.KeyGestureType int keyGestureType) { mKeyGestureType = keyGestureType; return this; } /** Set input gesture system action as launching a target app */ public Builder setAppLaunchData(@NonNull AppLaunchData appLaunchData) { mKeyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION; mAppLaunchData = appLaunchData; return this; } /** Creates {@link android.hardware.input.InputGestureData} based on data provided */ public InputGestureData build() throws IllegalArgumentException { if (mTrigger == null) { throw new IllegalArgumentException("No trigger found"); } if (mKeyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) { throw new IllegalArgumentException("No system action found"); } if (mKeyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION && mAppLaunchData == null) { throw new IllegalArgumentException( "No app launch data for system action launch application"); } AidlInputGestureData data = new AidlInputGestureData(); if (mTrigger instanceof KeyTrigger keyTrigger) { data.keycode = keyTrigger.getKeycode(); data.modifierState = keyTrigger.getModifierState(); } else { throw new IllegalArgumentException("Invalid trigger type!"); } data.gestureType = mKeyGestureType; if (mAppLaunchData != null) { if (mAppLaunchData instanceof AppLaunchData.CategoryData categoryData) { data.appLaunchCategory = categoryData.getCategory(); } else if (mAppLaunchData instanceof AppLaunchData.RoleData roleData) { data.appLaunchRole = roleData.getRole(); } else if (mAppLaunchData instanceof AppLaunchData.ComponentData componentData) { data.appLaunchPackageName = componentData.getPackageName(); data.appLaunchClassName = componentData.getClassName(); } else { throw new IllegalArgumentException("AppLaunchData type is invalid!"); } } return new InputGestureData(data); } } @Override public String toString() { return "InputGestureData { " + "trigger = " + getTrigger() + ", action = " + getAction() + " }"; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InputGestureData that = (InputGestureData) o; return mInputGestureData.keycode == that.mInputGestureData.keycode && mInputGestureData.modifierState == that.mInputGestureData.modifierState && mInputGestureData.gestureType == that.mInputGestureData.gestureType && Objects.equals(mInputGestureData.appLaunchCategory, that.mInputGestureData.appLaunchCategory) && Objects.equals(mInputGestureData.appLaunchRole, that.mInputGestureData.appLaunchRole) && Objects.equals(mInputGestureData.appLaunchPackageName, that.mInputGestureData.appLaunchPackageName) && Objects.equals(mInputGestureData.appLaunchPackageName, that.mInputGestureData.appLaunchPackageName); } @Override public int hashCode() { int _hash = 1; _hash = 31 * _hash + mInputGestureData.keycode; _hash = 31 * _hash + mInputGestureData.modifierState; _hash = 31 * _hash + mInputGestureData.gestureType; _hash = 31 * _hash + (mInputGestureData.appLaunchCategory != null ? mInputGestureData.appLaunchCategory.hashCode() : 0); _hash = 31 * _hash + (mInputGestureData.appLaunchRole != null ? mInputGestureData.appLaunchRole.hashCode() : 0); _hash = 31 * _hash + (mInputGestureData.appLaunchPackageName != null ? mInputGestureData.appLaunchPackageName.hashCode() : 0); _hash = 31 * _hash + (mInputGestureData.appLaunchPackageName != null ? mInputGestureData.appLaunchPackageName.hashCode() : 0); return _hash; } public interface Trigger { } /** Creates a input gesture trigger based on a key press */ public static Trigger createKeyTrigger(int keycode, int modifierState) { return new KeyTrigger(keycode, modifierState); } /** Key based input gesture trigger */ public static class KeyTrigger implements Trigger { private static final int SHORTCUT_META_MASK = KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON; private final int mKeycode; private final int mModifierState; private KeyTrigger(int keycode, int modifierState) { if (keycode <= KeyEvent.KEYCODE_UNKNOWN || keycode > KeyEvent.getMaxKeyCode()) { throw new IllegalArgumentException("Invalid keycode = " + keycode); } mKeycode = keycode; mModifierState = modifierState; } public int getKeycode() { return mKeycode; } public int getModifierState() { return mModifierState; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof KeyTrigger that)) return false; return mKeycode == that.mKeycode && mModifierState == that.mModifierState; } @Override public int hashCode() { return Objects.hash(mKeycode, mModifierState); } @Override public String toString() { return "KeyTrigger{" + "mKeycode=" + KeyEvent.keyCodeToString(mKeycode) + ", mModifierState=" + mModifierState + '}'; } } /** Data for action to perform when input gesture is triggered */ public record Action(@KeyGestureEvent.KeyGestureType int keyGestureType, @Nullable AppLaunchData appLaunchData) { } }
core/java/android/hardware/input/InputManager.java +125 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.hardware.input; import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API; import static com.android.input.flags.Flags.FLAG_DEVICE_ASSOCIATIONS; import static com.android.hardware.input.Flags.enableCustomizableInputGestures; import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag; import static com.android.hardware.input.Flags.keyboardGlyphMap; Loading Loading @@ -257,6 +258,52 @@ public final class InputManager { int REMAPPABLE_MODIFIER_KEY_CAPS_LOCK = KeyEvent.KEYCODE_CAPS_LOCK; } /** * Custom input gesture error: Input gesture already exists * * @hide */ public static final int CUSTOM_INPUT_GESTURE_RESULT_SUCCESS = 1; /** * Custom input gesture error: Input gesture already exists * * @hide */ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS = 2; /** * Custom input gesture error: Input gesture does not exist * * @hide */ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST = 3; /** * Custom input gesture error: Input gesture is reserved for system action * * @hide */ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE = 4; /** * Custom input gesture error: Failure error code for all other errors/warnings * * @hide */ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER = 5; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "CUSTOM_INPUT_GESTURE_RESULT_" }, value = { CUSTOM_INPUT_GESTURE_RESULT_SUCCESS, CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS, CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST, CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE, CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER, }) public @interface CustomInputGestureResult {} /** * Switch State: Unknown. * Loading Loading @@ -1432,6 +1479,84 @@ public final class InputManager { mGlobal.unregisterKeyGestureEventHandler(handler); } /** Adds a new custom input gesture * * @param inputGestureData gesture data to add as custom gesture * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) @CustomInputGestureResult public int addCustomInputGesture(@NonNull InputGestureData inputGestureData) { if (!enableCustomizableInputGestures()) { return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } try { return mIm.addCustomInputGesture(inputGestureData.getAidlData()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } /** Removes an existing custom gesture * * <p> NOTE: Should not be used to remove system gestures. This API is only to be used to * remove gestures added using {@link #addCustomInputGesture(InputGestureData)} * * @param inputGestureData gesture data for the existing custom gesture to remove * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) @CustomInputGestureResult public int removeCustomInputGesture(@NonNull InputGestureData inputGestureData) { if (!enableCustomizableInputGestures()) { return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } try { return mIm.removeCustomInputGesture(inputGestureData.getAidlData()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } /** Removes all custom input gestures * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) public void removeAllCustomInputGestures() { if (!enableCustomizableInputGestures()) { return; } try { mIm.removeAllCustomInputGestures(); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** Get all custom input gestures * * @hide */ public List<InputGestureData> getCustomInputGestures() { List<InputGestureData> result = new ArrayList<>(); if (!enableCustomizableInputGestures()) { return result; } try { for (AidlInputGestureData data : mIm.getCustomInputGestures()) { result.add(new InputGestureData(data)); } } catch (RemoteException e) { e.rethrowFromSystemServer(); } return result; } /** * A callback used to be notified about battery state changes for an input device. The * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the Loading
core/java/android/hardware/input/input_framework.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -142,6 +142,13 @@ flag { bug: "373458181" } flag { name: "enable_customizable_input_gestures" namespace: "input" description: "Enables keyboard shortcut customization support" bug: "365064144" } flag { name: "override_power_key_behavior_in_focused_window" namespace: "input_native" Loading