1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.hardware.input; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.view.KeyEvent; 22 23 import java.util.Objects; 24 25 /** 26 * Data class to store input gesture data. 27 * 28 * <p> 29 * All input gestures are of type Trigger -> Action(Key gesture type, app data). And currently types 30 * of triggers supported are: 31 * - KeyTrigger (Keycode + modifierState) 32 * - TODO(b/365064144): Add Touchpad gesture based trigger 33 * </p> 34 * @hide 35 */ 36 public final class InputGestureData { 37 38 public static final int TOUCHPAD_GESTURE_TYPE_UNKNOWN = 0; 39 public static final int TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP = 1; 40 41 @NonNull 42 private final AidlInputGestureData mInputGestureData; 43 InputGestureData(@onNull AidlInputGestureData inputGestureData)44 public InputGestureData(@NonNull AidlInputGestureData inputGestureData) { 45 this.mInputGestureData = inputGestureData; 46 validate(); 47 } 48 49 /** Returns the trigger information for this input gesture */ getTrigger()50 public Trigger getTrigger() { 51 return createTriggerFromAidlTrigger(mInputGestureData.trigger); 52 } 53 54 /** Returns the action to perform for this input gesture */ getAction()55 public Action getAction() { 56 return new Action(mInputGestureData.gestureType, getAppLaunchData()); 57 } 58 validate()59 private void validate() { 60 Trigger trigger = getTrigger(); 61 Action action = getAction(); 62 if (trigger == null) { 63 throw new IllegalArgumentException("No trigger found"); 64 } 65 if (action.keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) { 66 throw new IllegalArgumentException("No system action found"); 67 } 68 if (action.keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION 69 && action.appLaunchData == null) { 70 throw new IllegalArgumentException( 71 "No app launch data for system action launch application"); 72 } 73 } 74 getAidlData()75 public AidlInputGestureData getAidlData() { 76 return mInputGestureData; 77 } 78 79 @Nullable getAppLaunchData()80 private AppLaunchData getAppLaunchData() { 81 if (mInputGestureData.gestureType != KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION) { 82 return null; 83 } 84 return AppLaunchData.createLaunchData(mInputGestureData.appLaunchCategory, 85 mInputGestureData.appLaunchRole, mInputGestureData.appLaunchPackageName, 86 mInputGestureData.appLaunchClassName); 87 } 88 89 /** Builder class for creating {@link InputGestureData} */ 90 public static class Builder { 91 @Nullable 92 private Trigger mTrigger = null; 93 private int mKeyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED; 94 @Nullable 95 private AppLaunchData mAppLaunchData = null; 96 97 /** Set input gesture trigger data for key based gestures */ setTrigger(Trigger trigger)98 public Builder setTrigger(Trigger trigger) { 99 mTrigger = trigger; 100 return this; 101 } 102 103 /** Set input gesture system action */ setKeyGestureType(@eyGestureEvent.KeyGestureType int keyGestureType)104 public Builder setKeyGestureType(@KeyGestureEvent.KeyGestureType int keyGestureType) { 105 mKeyGestureType = keyGestureType; 106 return this; 107 } 108 109 /** Set input gesture system action as launching a target app */ setAppLaunchData(@onNull AppLaunchData appLaunchData)110 public Builder setAppLaunchData(@NonNull AppLaunchData appLaunchData) { 111 mKeyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION; 112 mAppLaunchData = appLaunchData; 113 return this; 114 } 115 116 /** Creates {@link android.hardware.input.InputGestureData} based on data provided */ build()117 public InputGestureData build() throws IllegalArgumentException { 118 if (mTrigger == null) { 119 throw new IllegalArgumentException("No trigger found"); 120 } 121 if (mKeyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) { 122 throw new IllegalArgumentException("No system action found"); 123 } 124 if (mKeyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION 125 && mAppLaunchData == null) { 126 throw new IllegalArgumentException( 127 "No app launch data for system action launch application"); 128 } 129 AidlInputGestureData data = new AidlInputGestureData(); 130 data.trigger = mTrigger.getAidlTrigger(); 131 data.gestureType = mKeyGestureType; 132 if (mAppLaunchData != null) { 133 if (mAppLaunchData instanceof AppLaunchData.CategoryData categoryData) { 134 data.appLaunchCategory = categoryData.getCategory(); 135 } else if (mAppLaunchData instanceof AppLaunchData.RoleData roleData) { 136 data.appLaunchRole = roleData.getRole(); 137 } else if (mAppLaunchData instanceof AppLaunchData.ComponentData componentData) { 138 data.appLaunchPackageName = componentData.getPackageName(); 139 data.appLaunchClassName = componentData.getClassName(); 140 } else { 141 throw new IllegalArgumentException("AppLaunchData type is invalid!"); 142 } 143 } 144 return new InputGestureData(data); 145 } 146 } 147 148 @Override toString()149 public String toString() { 150 return "InputGestureData { " 151 + "trigger = " + getTrigger() 152 + ", action = " + getAction() 153 + " }"; 154 } 155 156 @Override equals(Object o)157 public boolean equals(Object o) { 158 if (this == o) return true; 159 if (o == null || getClass() != o.getClass()) return false; 160 InputGestureData that = (InputGestureData) o; 161 return Objects.equals(mInputGestureData, that.mInputGestureData); 162 } 163 164 @Override hashCode()165 public int hashCode() { 166 return mInputGestureData.hashCode(); 167 } 168 169 public interface Trigger { getAidlTrigger()170 AidlInputGestureData.Trigger getAidlTrigger(); 171 } 172 173 /** Creates a input gesture trigger based on a key press */ createKeyTrigger(int keycode, int modifierState)174 public static Trigger createKeyTrigger(int keycode, int modifierState) { 175 return new KeyTrigger(keycode, modifierState); 176 } 177 178 /** Creates a input gesture trigger based on a touchpad gesture */ createTouchpadTrigger(int touchpadGestureType)179 public static Trigger createTouchpadTrigger(int touchpadGestureType) { 180 return new TouchpadTrigger(touchpadGestureType); 181 } 182 createTriggerFromAidlTrigger(AidlInputGestureData.Trigger aidlTrigger)183 public static Trigger createTriggerFromAidlTrigger(AidlInputGestureData.Trigger aidlTrigger) { 184 switch (aidlTrigger.getTag()) { 185 case AidlInputGestureData.Trigger.Tag.key: { 186 AidlInputGestureData.KeyTrigger trigger = aidlTrigger.getKey(); 187 if (trigger == null) { 188 throw new RuntimeException("aidlTrigger is corrupted, null key trigger!"); 189 } 190 return new KeyTrigger(trigger); 191 } 192 case AidlInputGestureData.Trigger.Tag.touchpadGesture: { 193 AidlInputGestureData.TouchpadGestureTrigger trigger = 194 aidlTrigger.getTouchpadGesture(); 195 if (trigger == null) { 196 throw new RuntimeException( 197 "aidlTrigger is corrupted, null touchpad trigger!"); 198 } 199 return new TouchpadTrigger(trigger); 200 } 201 default: 202 throw new RuntimeException("aidlTrigger is corrupted, invalid trigger type!"); 203 204 } 205 } 206 207 /** Key based input gesture trigger */ 208 public static class KeyTrigger implements Trigger { 209 210 AidlInputGestureData.KeyTrigger mAidlKeyTrigger; 211 KeyTrigger(@onNull AidlInputGestureData.KeyTrigger aidlKeyTrigger)212 private KeyTrigger(@NonNull AidlInputGestureData.KeyTrigger aidlKeyTrigger) { 213 mAidlKeyTrigger = aidlKeyTrigger; 214 } 215 KeyTrigger(int keycode, int modifierState)216 private KeyTrigger(int keycode, int modifierState) { 217 if (keycode <= KeyEvent.KEYCODE_UNKNOWN || keycode > KeyEvent.getMaxKeyCode()) { 218 throw new IllegalArgumentException("Invalid keycode = " + keycode); 219 } 220 mAidlKeyTrigger = new AidlInputGestureData.KeyTrigger(); 221 mAidlKeyTrigger.keycode = keycode; 222 mAidlKeyTrigger.modifierState = modifierState; 223 } 224 getKeycode()225 public int getKeycode() { 226 return mAidlKeyTrigger.keycode; 227 } 228 getModifierState()229 public int getModifierState() { 230 return mAidlKeyTrigger.modifierState; 231 } 232 getAidlTrigger()233 public AidlInputGestureData.Trigger getAidlTrigger() { 234 AidlInputGestureData.Trigger trigger = new AidlInputGestureData.Trigger(); 235 trigger.setKey(mAidlKeyTrigger); 236 return trigger; 237 } 238 239 @Override equals(Object o)240 public boolean equals(Object o) { 241 if (this == o) return true; 242 if (!(o instanceof KeyTrigger that)) return false; 243 return Objects.equals(mAidlKeyTrigger, that.mAidlKeyTrigger); 244 } 245 246 @Override hashCode()247 public int hashCode() { 248 return mAidlKeyTrigger.hashCode(); 249 } 250 251 @Override toString()252 public String toString() { 253 return "KeyTrigger{" + 254 "mKeycode=" + KeyEvent.keyCodeToString(mAidlKeyTrigger.keycode) + 255 ", mModifierState=" + mAidlKeyTrigger.modifierState + 256 '}'; 257 } 258 } 259 260 /** Touchpad based input gesture trigger */ 261 public static class TouchpadTrigger implements Trigger { 262 AidlInputGestureData.TouchpadGestureTrigger mAidlTouchpadTrigger; 263 TouchpadTrigger( @onNull AidlInputGestureData.TouchpadGestureTrigger aidlTouchpadTrigger)264 private TouchpadTrigger( 265 @NonNull AidlInputGestureData.TouchpadGestureTrigger aidlTouchpadTrigger) { 266 mAidlTouchpadTrigger = aidlTouchpadTrigger; 267 } 268 TouchpadTrigger(int touchpadGestureType)269 private TouchpadTrigger(int touchpadGestureType) { 270 if (touchpadGestureType != TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP) { 271 throw new IllegalArgumentException( 272 "Invalid touchpadGestureType = " + touchpadGestureType); 273 } 274 mAidlTouchpadTrigger = new AidlInputGestureData.TouchpadGestureTrigger(); 275 mAidlTouchpadTrigger.gestureType = touchpadGestureType; 276 } 277 getTouchpadGestureType()278 public int getTouchpadGestureType() { 279 return mAidlTouchpadTrigger.gestureType; 280 } 281 getAidlTrigger()282 public AidlInputGestureData.Trigger getAidlTrigger() { 283 AidlInputGestureData.Trigger trigger = new AidlInputGestureData.Trigger(); 284 trigger.setTouchpadGesture(mAidlTouchpadTrigger); 285 return trigger; 286 } 287 288 @Override toString()289 public String toString() { 290 return "TouchpadTrigger{" + 291 "mTouchpadGestureType=" + mAidlTouchpadTrigger.gestureType + 292 '}'; 293 } 294 295 @Override equals(Object o)296 public boolean equals(Object o) { 297 if (this == o) return true; 298 if (!(o instanceof TouchpadTrigger that)) return false; 299 return Objects.equals(mAidlTouchpadTrigger, that.mAidlTouchpadTrigger); 300 } 301 302 @Override hashCode()303 public int hashCode() { 304 return mAidlTouchpadTrigger.hashCode(); 305 } 306 } 307 308 /** Data for action to perform when input gesture is triggered */ Action(@eyGestureEvent.KeyGestureType int keyGestureType, @Nullable AppLaunchData appLaunchData)309 public record Action(@KeyGestureEvent.KeyGestureType int keyGestureType, 310 @Nullable AppLaunchData appLaunchData) { 311 } 312 313 /** Filter definition for InputGestureData */ 314 public enum Filter { 315 KEY(AidlInputGestureData.Trigger.Tag.key), 316 TOUCHPAD(AidlInputGestureData.Trigger.Tag.touchpadGesture); 317 318 @AidlInputGestureData.Trigger.Tag 319 private final int mTag; 320 Filter(@idlInputGestureData.Trigger.Tag int tag)321 Filter(@AidlInputGestureData.Trigger.Tag int tag) { 322 mTag = tag; 323 } 324 325 @Nullable of(@idlInputGestureData.Trigger.Tag int tag)326 public static Filter of(@AidlInputGestureData.Trigger.Tag int tag) { 327 return switch (tag) { 328 case AidlInputGestureData.Trigger.Tag.key -> KEY; 329 case AidlInputGestureData.Trigger.Tag.touchpadGesture -> TOUCHPAD; 330 default -> null; 331 }; 332 } 333 334 @AidlInputGestureData.Trigger.Tag getTag()335 public int getTag() { 336 return mTag; 337 } 338 matches(@onNull InputGestureData inputGestureData)339 public boolean matches(@NonNull InputGestureData inputGestureData) { 340 return mTag == inputGestureData.mInputGestureData.trigger.getTag(); 341 } 342 } 343 } 344