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.DrawableRes; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.pm.ActivityInfo; 25 import android.content.pm.PackageManager; 26 import android.content.res.Resources; 27 import android.graphics.drawable.Drawable; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.util.Log; 31 import android.util.SparseIntArray; 32 import android.view.KeyEvent; 33 34 import java.util.Arrays; 35 import java.util.HashMap; 36 import java.util.Map; 37 import java.util.Objects; 38 39 /** 40 * This class provides access to device specific key glyphs, modifier glyphs and device specific 41 * shortcuts and keys 42 * 43 * @hide 44 */ 45 public final class KeyGlyphMap implements Parcelable { 46 private static final String TAG = "KeyGlyphMap"; 47 48 @NonNull 49 private final ComponentName mComponentName; 50 @NonNull 51 private final SparseIntArray mKeyGlyphs; 52 @NonNull 53 private final SparseIntArray mModifierGlyphs; 54 @NonNull 55 private final int[] mFunctionRowKeys; 56 @NonNull 57 private final Map<KeyCombination, Integer> mHardwareShortcuts; 58 59 public static final @NonNull Parcelable.Creator<KeyGlyphMap> CREATOR = 60 new Parcelable.Creator<>() { 61 public KeyGlyphMap createFromParcel(Parcel in) { 62 return new KeyGlyphMap(in); 63 } 64 65 public KeyGlyphMap[] newArray(int size) { 66 return new KeyGlyphMap[size]; 67 } 68 }; 69 KeyGlyphMap(@onNull ComponentName componentName, @NonNull SparseIntArray keyGlyphs, @NonNull SparseIntArray modifierGlyphs, @NonNull int[] functionRowKeys, @NonNull Map<KeyCombination, Integer> hardwareShortcuts)70 public KeyGlyphMap(@NonNull ComponentName componentName, 71 @NonNull SparseIntArray keyGlyphs, @NonNull SparseIntArray modifierGlyphs, 72 @NonNull int[] functionRowKeys, 73 @NonNull Map<KeyCombination, Integer> hardwareShortcuts) { 74 mComponentName = componentName; 75 mKeyGlyphs = keyGlyphs; 76 mModifierGlyphs = modifierGlyphs; 77 mFunctionRowKeys = functionRowKeys; 78 mHardwareShortcuts = hardwareShortcuts; 79 } 80 KeyGlyphMap(Parcel in)81 public KeyGlyphMap(Parcel in) { 82 mComponentName = in.readParcelable(getClass().getClassLoader(), ComponentName.class); 83 mKeyGlyphs = in.readSparseIntArray(); 84 mModifierGlyphs = in.readSparseIntArray(); 85 mFunctionRowKeys = new int[in.readInt()]; 86 in.readIntArray(mFunctionRowKeys); 87 mHardwareShortcuts = new HashMap<>(in.readInt()); 88 in.readMap(mHardwareShortcuts, getClass().getClassLoader(), KeyCombination.class, 89 Integer.class); 90 } 91 92 @Override writeToParcel(@onNull Parcel dest, int flags)93 public void writeToParcel(@NonNull Parcel dest, int flags) { 94 dest.writeParcelable(mComponentName, 0); 95 dest.writeSparseIntArray(mKeyGlyphs); 96 dest.writeSparseIntArray(mModifierGlyphs); 97 dest.writeInt(mFunctionRowKeys.length); 98 dest.writeIntArray(mFunctionRowKeys); 99 dest.writeInt(mHardwareShortcuts.size()); 100 dest.writeMap(mHardwareShortcuts); 101 } 102 103 @Override describeContents()104 public int describeContents() { 105 return 0; 106 } 107 108 /** 109 * Defines a key combination that includes a keycode and modifier state. 110 */ 111 public static class KeyCombination implements Parcelable { 112 private final int mModifierState; 113 private final int mKeycode; 114 KeyCombination(int modifierState, int keycode)115 public KeyCombination(int modifierState, int keycode) { 116 this.mModifierState = modifierState; 117 this.mKeycode = keycode; 118 } 119 KeyCombination(Parcel in)120 public KeyCombination(Parcel in) { 121 this(in.readInt(), in.readInt()); 122 } 123 124 public static final Creator<KeyCombination> CREATOR = new Creator<>() { 125 @Override 126 public KeyCombination createFromParcel(Parcel in) { 127 return new KeyCombination(in); 128 } 129 130 @Override 131 public KeyCombination[] newArray(int size) { 132 return new KeyCombination[size]; 133 } 134 }; 135 getModifierState()136 public int getModifierState() { 137 return mModifierState; 138 } 139 getKeycode()140 public int getKeycode() { 141 return mKeycode; 142 } 143 144 @Override describeContents()145 public int describeContents() { 146 return 0; 147 } 148 149 @Override writeToParcel(@ndroidx.annotation.NonNull Parcel dest, int flags)150 public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) { 151 dest.writeInt(mModifierState); 152 dest.writeInt(mKeycode); 153 } 154 155 @Override equals(Object o)156 public boolean equals(Object o) { 157 if (this == o) return true; 158 if (!(o instanceof KeyCombination that)) return false; 159 return mModifierState == that.mModifierState && mKeycode == that.mKeycode; 160 } 161 162 @Override hashCode()163 public int hashCode() { 164 return Objects.hash(mModifierState, mKeycode); 165 } 166 } 167 168 /** 169 * Returns keycodes generated from the functional row defined for the keyboard. 170 */ getFunctionRowKeys()171 public int[] getFunctionRowKeys() { 172 return mFunctionRowKeys; 173 } 174 175 /** 176 * Returns hardware defined shortcuts that are handled in the firmware of a particular 177 * keyboard (e.g. Fn+Backspace = Back, etc.) 178 * 179 * @return a map of (modifier + key) combinations to keycode mappings that are handled by the 180 * device hardware/firmware. 181 */ getHardwareShortcuts()182 public Map<KeyCombination, Integer> getHardwareShortcuts() { 183 return mHardwareShortcuts; 184 } 185 186 /** 187 * Provides the drawable resource for the glyph for a keycode. 188 * Returns null if not available. 189 */ 190 @Nullable getDrawableForKeycode(Context context, int keycode)191 public Drawable getDrawableForKeycode(Context context, int keycode) { 192 return getDrawable(context, mKeyGlyphs.get(keycode, 0)); 193 } 194 195 /** 196 * Provides the drawable resource for the glyph for a modifier key. 197 * Returns null if not available. 198 */ 199 @Nullable getDrawableForModifier(Context context, int modifierKeycode)200 public Drawable getDrawableForModifier(Context context, int modifierKeycode) { 201 int modifier = switch (modifierKeycode) { 202 case KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT -> KeyEvent.META_META_ON; 203 case KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT -> KeyEvent.META_CTRL_ON; 204 case KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT -> KeyEvent.META_ALT_ON; 205 case KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT -> 206 KeyEvent.META_SHIFT_ON; 207 case KeyEvent.KEYCODE_FUNCTION -> KeyEvent.META_FUNCTION_ON; 208 case KeyEvent.KEYCODE_SYM -> KeyEvent.META_SYM_ON; 209 case KeyEvent.KEYCODE_CAPS_LOCK -> KeyEvent.META_CAPS_LOCK_ON; 210 case KeyEvent.KEYCODE_NUM_LOCK -> KeyEvent.META_NUM_LOCK_ON; 211 case KeyEvent.KEYCODE_SCROLL_LOCK -> KeyEvent.META_SCROLL_LOCK_ON; 212 default -> 0; 213 }; 214 return getDrawable(context, mModifierGlyphs.get(modifier, 0)); 215 } 216 217 /** 218 * Provides the drawable resource for the glyph for a modifier state (e.g. META_META_ON). 219 * Returns null if not available. 220 */ 221 @Nullable getDrawableForModifierState(Context context, int modifierState)222 public Drawable getDrawableForModifierState(Context context, int modifierState) { 223 return getDrawable(context, mModifierGlyphs.get(modifierState, 0)); 224 } 225 226 @Nullable getDrawable(Context context, @DrawableRes int drawableRes)227 private Drawable getDrawable(Context context, @DrawableRes int drawableRes) { 228 PackageManager pm = context.getPackageManager(); 229 try { 230 ActivityInfo receiver = pm.getReceiverInfo(mComponentName, 231 PackageManager.GET_META_DATA 232 | PackageManager.MATCH_DIRECT_BOOT_AWARE 233 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); 234 Resources resources = pm.getResourcesForApplication(receiver.applicationInfo); 235 return resources.getDrawable(drawableRes, null); 236 } catch (PackageManager.NameNotFoundException ignored) { 237 Log.e(TAG, "Package name not found for " + mComponentName); 238 } catch (Resources.NotFoundException ignored) { 239 Log.e(TAG, "Resource not found for " + mComponentName); 240 } 241 return null; 242 } 243 244 @Override toString()245 public String toString() { 246 return "KeyGlyphMap{" 247 + "mComponentName=" + mComponentName 248 + ", mKeyGlyphs=" + mKeyGlyphs 249 + ", mModifierGlyphs=" + mModifierGlyphs 250 + ", mFunctionRowKeys=" + Arrays.toString(mFunctionRowKeys) 251 + ", mHardwareShortcuts=" + mHardwareShortcuts 252 + '}'; 253 } 254 } 255