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