1 /* 2 * Copyright (C) 2012 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.os.LocaleList; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 24 import java.util.HashMap; 25 import java.util.Locale; 26 import java.util.Map; 27 import java.util.Objects; 28 29 /** 30 * Describes a keyboard layout. 31 * 32 * @hide 33 */ 34 public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayout> { 35 36 /** Undefined keyboard layout */ 37 public static final String LAYOUT_TYPE_UNDEFINED = "undefined"; 38 39 /** Qwerty-based keyboard layout */ 40 public static final String LAYOUT_TYPE_QWERTY = "qwerty"; 41 42 /** Qwertz-based keyboard layout */ 43 public static final String LAYOUT_TYPE_QWERTZ = "qwertz"; 44 45 /** Azerty-based keyboard layout */ 46 public static final String LAYOUT_TYPE_AZERTY = "azerty"; 47 48 /** Dvorak keyboard layout */ 49 public static final String LAYOUT_TYPE_DVORAK = "dvorak"; 50 51 /** Colemak keyboard layout */ 52 public static final String LAYOUT_TYPE_COLEMAK = "colemak"; 53 54 /** Workman keyboard layout */ 55 public static final String LAYOUT_TYPE_WORKMAN = "workman"; 56 57 /** Turkish-F keyboard layout */ 58 public static final String LAYOUT_TYPE_TURKISH_F = "turkish_f"; 59 60 /** Turkish-Q keyboard layout */ 61 public static final String LAYOUT_TYPE_TURKISH_Q = "turkish_q"; 62 63 /** Keyboard layout that has been enhanced with a large number of extra characters */ 64 public static final String LAYOUT_TYPE_EXTENDED = "extended"; 65 66 private final String mDescriptor; 67 private final String mLabel; 68 private final String mCollection; 69 private final int mPriority; 70 @NonNull 71 private final LocaleList mLocales; 72 private final LayoutType mLayoutType; 73 private final int mVendorId; 74 private final int mProductId; 75 76 /** Currently supported Layout types in the KCM files */ 77 public enum LayoutType { 78 UNDEFINED(0, LAYOUT_TYPE_UNDEFINED), 79 QWERTY(1, LAYOUT_TYPE_QWERTY), 80 QWERTZ(2, LAYOUT_TYPE_QWERTZ), 81 AZERTY(3, LAYOUT_TYPE_AZERTY), 82 DVORAK(4, LAYOUT_TYPE_DVORAK), 83 COLEMAK(5, LAYOUT_TYPE_COLEMAK), 84 WORKMAN(6, LAYOUT_TYPE_WORKMAN), 85 TURKISH_Q(7, LAYOUT_TYPE_TURKISH_Q), 86 TURKISH_F(8, LAYOUT_TYPE_TURKISH_F), 87 EXTENDED(9, LAYOUT_TYPE_EXTENDED); 88 89 private final int mValue; 90 private final String mName; 91 private static final Map<Integer, LayoutType> VALUE_TO_ENUM_MAP = new HashMap<>(); 92 private static final Map<String, LayoutType> NAME_TO_ENUM_MAP = new HashMap<>(); 93 static { 94 for (LayoutType type : LayoutType.values()) { VALUE_TO_ENUM_MAP.put(type.mValue, type)95 VALUE_TO_ENUM_MAP.put(type.mValue, type); NAME_TO_ENUM_MAP.put(type.mName, type)96 NAME_TO_ENUM_MAP.put(type.mName, type); 97 } 98 } 99 of(int value)100 private static LayoutType of(int value) { 101 return VALUE_TO_ENUM_MAP.getOrDefault(value, UNDEFINED); 102 } 103 LayoutType(int value, String name)104 LayoutType(int value, String name) { 105 this.mValue = value; 106 this.mName = name; 107 } 108 getValue()109 private int getValue() { 110 return mValue; 111 } 112 getName()113 private String getName() { 114 return mName; 115 } 116 117 /** 118 * Returns enum value for provided layout type 119 * @param layoutName name of the layout type 120 * @return int value corresponding to the LayoutType enum that matches the layout name. 121 * (LayoutType.UNDEFINED if no match found) 122 */ getLayoutTypeEnumValue(String layoutName)123 public static int getLayoutTypeEnumValue(String layoutName) { 124 return NAME_TO_ENUM_MAP.getOrDefault(layoutName, UNDEFINED).getValue(); 125 } 126 127 /** 128 * Returns name for provided layout type enum value 129 * @param enumValue value representation for LayoutType enum 130 * @return Layout name corresponding to the enum value (LAYOUT_TYPE_UNDEFINED if not found) 131 */ getLayoutNameFromValue(int enumValue)132 public static String getLayoutNameFromValue(int enumValue) { 133 return VALUE_TO_ENUM_MAP.getOrDefault(enumValue, UNDEFINED).getName(); 134 } 135 } 136 137 @NonNull 138 public static final Parcelable.Creator<KeyboardLayout> CREATOR = new Parcelable.Creator<>() { 139 public KeyboardLayout createFromParcel(Parcel source) { 140 return new KeyboardLayout(source); 141 } 142 public KeyboardLayout[] newArray(int size) { 143 return new KeyboardLayout[size]; 144 } 145 }; 146 KeyboardLayout(String descriptor, String label, String collection, int priority, LocaleList locales, int layoutValue, int vid, int pid)147 public KeyboardLayout(String descriptor, String label, String collection, int priority, 148 LocaleList locales, int layoutValue, int vid, int pid) { 149 mDescriptor = descriptor; 150 mLabel = label; 151 mCollection = collection; 152 mPriority = priority; 153 mLocales = locales; 154 mLayoutType = LayoutType.of(layoutValue); 155 mVendorId = vid; 156 mProductId = pid; 157 } 158 KeyboardLayout(Parcel source)159 private KeyboardLayout(Parcel source) { 160 mDescriptor = source.readString(); 161 mLabel = source.readString(); 162 mCollection = source.readString(); 163 mPriority = source.readInt(); 164 mLocales = LocaleList.CREATOR.createFromParcel(source); 165 mLayoutType = LayoutType.of(source.readInt()); 166 mVendorId = source.readInt(); 167 mProductId = source.readInt(); 168 } 169 170 /** 171 * Gets the keyboard layout descriptor, which can be used to retrieve 172 * the keyboard layout again later using 173 * {@link InputManager#getKeyboardLayout(String)}. 174 * 175 * @return The keyboard layout descriptor. 176 */ getDescriptor()177 public String getDescriptor() { 178 return mDescriptor; 179 } 180 181 /** 182 * Gets the keyboard layout descriptive label to show in the user interface. 183 * @return The keyboard layout descriptive label. 184 */ getLabel()185 public String getLabel() { 186 return mLabel; 187 } 188 189 /** 190 * Gets the name of the collection to which the keyboard layout belongs. This is 191 * the label of the broadcast receiver or application that provided the keyboard layout. 192 * @return The keyboard layout collection name. 193 */ getCollection()194 public String getCollection() { 195 return mCollection; 196 } 197 198 /** 199 * Gets the locales that this keyboard layout is intended for. 200 * This may be empty if a locale has not been assigned to this keyboard layout. 201 * @return The keyboard layout's intended locale. 202 */ getLocales()203 public LocaleList getLocales() { 204 return mLocales; 205 } 206 207 /** 208 * Gets the layout type that this keyboard layout is intended for. 209 * This may be "undefined" if a layoutType has not been assigned to this keyboard layout. 210 * @return The keyboard layout's intended layout type. 211 */ getLayoutType()212 public String getLayoutType() { 213 return mLayoutType.getName(); 214 } 215 216 /** 217 * Gets the vendor ID of the hardware device this keyboard layout is intended for. 218 * Returns -1 if this is not specific to any piece of hardware. 219 * @return The hardware vendor ID of the keyboard layout's intended device. 220 */ getVendorId()221 public int getVendorId() { 222 return mVendorId; 223 } 224 225 /** 226 * Gets the product ID of the hardware device this keyboard layout is intended for. 227 * Returns -1 if this is not specific to any piece of hardware. 228 * @return The hardware product ID of the keyboard layout's intended device. 229 */ getProductId()230 public int getProductId() { 231 return mProductId; 232 } 233 234 /** 235 * Returns if the Keyboard layout follows the ANSI Physical key layout. 236 */ isAnsiLayout()237 public boolean isAnsiLayout() { 238 for (int i = 0; i < mLocales.size(); i++) { 239 Locale locale = mLocales.get(i); 240 if (locale != null && locale.getCountry().equalsIgnoreCase("us") 241 && mLayoutType != LayoutType.EXTENDED) { 242 return true; 243 } 244 } 245 return false; 246 } 247 248 /** 249 * Returns if the Keyboard layout follows the JIS Physical key layout. 250 */ isJisLayout()251 public boolean isJisLayout() { 252 for (int i = 0; i < mLocales.size(); i++) { 253 Locale locale = mLocales.get(i); 254 if (locale != null && locale.getCountry().equalsIgnoreCase("jp")) { 255 return true; 256 } 257 } 258 return false; 259 } 260 261 @Override describeContents()262 public int describeContents() { 263 return 0; 264 } 265 266 @Override writeToParcel(Parcel dest, int flags)267 public void writeToParcel(Parcel dest, int flags) { 268 dest.writeString(mDescriptor); 269 dest.writeString(mLabel); 270 dest.writeString(mCollection); 271 dest.writeInt(mPriority); 272 mLocales.writeToParcel(dest, 0); 273 dest.writeInt(mLayoutType.getValue()); 274 dest.writeInt(mVendorId); 275 dest.writeInt(mProductId); 276 } 277 278 @Override compareTo(KeyboardLayout another)279 public int compareTo(KeyboardLayout another) { 280 // Note that these arguments are intentionally flipped since you want higher priority 281 // keyboards to be listed before lower priority keyboards. 282 int result = Integer.compare(another.mPriority, mPriority); 283 if (result == 0) { 284 result = Integer.compare(mLayoutType.mValue, another.mLayoutType.mValue); 285 } 286 if (result == 0) { 287 result = mLabel.compareToIgnoreCase(another.mLabel); 288 } 289 if (result == 0) { 290 result = mCollection.compareToIgnoreCase(another.mCollection); 291 } 292 return result; 293 } 294 295 @Override toString()296 public String toString() { 297 String collectionString = mCollection.isEmpty() ? "" : " - " + mCollection; 298 return "KeyboardLayout " + mLabel + collectionString 299 + ", descriptor: " + mDescriptor 300 + ", priority: " + mPriority 301 + ", locales: " + mLocales.toString() 302 + ", layout type: " + mLayoutType.getName() 303 + ", vendorId: " + mVendorId 304 + ", productId: " + mProductId; 305 } 306 307 /** 308 * Check if the provided layout type is supported/valid. 309 * 310 * @param layoutName name of layout type 311 * @return {@code true} if the provided layout type is supported/valid. 312 */ isLayoutTypeValid(@onNull String layoutName)313 public static boolean isLayoutTypeValid(@NonNull String layoutName) { 314 Objects.requireNonNull(layoutName, "Provided layout name should not be null"); 315 for (LayoutType layoutType : LayoutType.values()) { 316 if (layoutName.equals(layoutType.getName())) { 317 return true; 318 } 319 } 320 // Layout doesn't match any supported layout types 321 return false; 322 } 323 } 324