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