1 /*
2  * Copyright (C) 2007 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.view;
18 
19 import android.annotation.Nullable;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.hardware.input.InputManagerGlobal;
22 import android.os.Build;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.text.method.MetaKeyKeyListener;
26 import android.util.AndroidRuntimeException;
27 import android.util.SparseIntArray;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 
31 import java.text.Normalizer;
32 
33 /**
34  * Describes the keys provided by a keyboard device and their associated labels.
35  */
36 public class KeyCharacterMap implements Parcelable {
37     /**
38      * The id of the device's primary built in keyboard is always 0.
39      *
40      * @deprecated This constant should no longer be used because there is no
41      * guarantee that a device has a built-in keyboard that can be used for
42      * typing text.  There might not be a built-in keyboard, the built-in keyboard
43      * might be a {@link #NUMERIC} or {@link #SPECIAL_FUNCTION} keyboard, or there
44      * might be multiple keyboards installed including external keyboards.
45      * When interpreting key presses received from the framework, applications should
46      * use the device id specified in the {@link KeyEvent} received.
47      * When synthesizing key presses for delivery elsewhere or when translating key presses
48      * from unknown keyboards, applications should use the special {@link #VIRTUAL_KEYBOARD}
49      * device id.
50      */
51     @Deprecated
52     public static final int BUILT_IN_KEYBOARD = 0;
53 
54     /**
55      * The id of a generic virtual keyboard with a full layout that can be used to
56      * synthesize key events.  Typically used with {@link #getEvents}.
57      */
58     public static final int VIRTUAL_KEYBOARD = -1;
59 
60     /**
61      * A numeric (12-key) keyboard.
62      * <p>
63      * A numeric keyboard supports text entry using a multi-tap approach.
64      * It may be necessary to tap a key multiple times to generate the desired letter
65      * or symbol.
66      * </p><p>
67      * This type of keyboard is generally designed for thumb typing.
68      * </p>
69      */
70     public static final int NUMERIC = 1;
71 
72     /**
73      * A keyboard with all the letters, but with more than one letter per key.
74      * <p>
75      * This type of keyboard is generally designed for thumb typing.
76      * </p>
77      */
78     public static final int PREDICTIVE = 2;
79 
80     /**
81      * A keyboard with all the letters, and maybe some numbers.
82      * <p>
83      * An alphabetic keyboard supports text entry directly but may have a condensed
84      * layout with a small form factor.  In contrast to a {@link #FULL full keyboard}, some
85      * symbols may only be accessible using special on-screen character pickers.
86      * In addition, to improve typing speed and accuracy, the framework provides
87      * special affordances for alphabetic keyboards such as auto-capitalization
88      * and toggled / locked shift and alt keys.
89      * </p><p>
90      * This type of keyboard is generally designed for thumb typing.
91      * </p>
92      */
93     public static final int ALPHA = 3;
94 
95     /**
96      * A full PC-style keyboard.
97      * <p>
98      * A full keyboard behaves like a PC keyboard.  All symbols are accessed directly
99      * by pressing keys on the keyboard without on-screen support or affordances such
100      * as auto-capitalization.
101      * </p><p>
102      * This type of keyboard is generally designed for full two hand typing.
103      * </p>
104      */
105     public static final int FULL = 4;
106 
107     /**
108      * A keyboard that is only used to control special functions rather than for typing.
109      * <p>
110      * A special function keyboard consists only of non-printing keys such as
111      * HOME and POWER that are not actually used for typing.
112      * </p>
113      */
114     public static final int SPECIAL_FUNCTION = 5;
115 
116     /**
117      * This private-use character is used to trigger Unicode character
118      * input by hex digits.
119      */
120     public static final char HEX_INPUT = '\uEF00';
121 
122     /**
123      * This private-use character is used to bring up a character picker for
124      * miscellaneous symbols.
125      */
126     public static final char PICKER_DIALOG_INPUT = '\uEF01';
127 
128     /**
129      * Modifier keys may be chorded with character keys.
130      *
131      * @see #getModifierBehavior()
132      */
133     public static final int MODIFIER_BEHAVIOR_CHORDED = 0;
134 
135     /**
136      * Modifier keys may be chorded with character keys or they may toggle
137      * into latched or locked states when pressed independently.
138      *
139      * @see #getModifierBehavior()
140      */
141     public static final int MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED = 1;
142 
143     /*
144      * This bit will be set in the return value of {@link #get(int, int)} if the
145      * key is a "dead key."
146      */
147     public static final int COMBINING_ACCENT = 0x80000000;
148 
149     /**
150      * Mask the return value from {@link #get(int, int)} with this value to get
151      * a printable representation of the accent character of a "dead key."
152      */
153     public static final int COMBINING_ACCENT_MASK = 0x7FFFFFFF;
154 
155     /* Characters used to display placeholders for dead keys. */
156     private static final int ACCENT_ACUTE = '\u00B4';
157     private static final int ACCENT_BREVE = '\u02D8';
158     private static final int ACCENT_CARON = '\u02C7';
159     private static final int ACCENT_CEDILLA = '\u00B8';
160     private static final int ACCENT_CIRCUMFLEX = '\u02C6';
161     private static final int ACCENT_COMMA_ABOVE = '\u1FBD';
162     private static final int ACCENT_COMMA_ABOVE_RIGHT = '\u02BC';
163     private static final int ACCENT_DOT_ABOVE = '\u02D9';
164     private static final int ACCENT_DOT_BELOW = '.'; // approximate
165     private static final int ACCENT_DOUBLE_ACUTE = '\u02DD';
166     private static final int ACCENT_GRAVE = '\u02CB';
167     private static final int ACCENT_HOOK_ABOVE = '\u02C0';
168     private static final int ACCENT_HORN = '\''; // approximate
169     private static final int ACCENT_MACRON = '\u00AF';
170     private static final int ACCENT_MACRON_BELOW = '\u02CD';
171     private static final int ACCENT_OGONEK = '\u02DB';
172     private static final int ACCENT_REVERSED_COMMA_ABOVE = '\u02BD';
173     private static final int ACCENT_RING_ABOVE = '\u02DA';
174     private static final int ACCENT_STROKE = '-'; // approximate
175     private static final int ACCENT_TILDE = '\u02DC';
176     private static final int ACCENT_TURNED_COMMA_ABOVE = '\u02BB';
177     private static final int ACCENT_UMLAUT = '\u00A8';
178     private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8';
179     private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC';
180     private static final int ACCENT_APOSTROPHE = '\'';
181     private static final int ACCENT_QUOTATION_MARK = '"';
182 
183     /* Legacy dead key display characters used in previous versions of the API.
184      * We still support these characters by mapping them to their non-legacy version. */
185     private static final int ACCENT_GRAVE_LEGACY = '`';
186     private static final int ACCENT_CIRCUMFLEX_LEGACY = '^';
187     private static final int ACCENT_TILDE_LEGACY = '~';
188 
189     private static final int CHAR_SPACE = ' ';
190 
191     /**
192      * Maps Unicode combining diacritical to display-form dead key.
193      */
194     private static final SparseIntArray sCombiningToAccent = new SparseIntArray();
195     private static final SparseIntArray sAccentToCombining = new SparseIntArray();
196     static {
197         addCombining('\u0300', ACCENT_GRAVE);
198         addCombining('\u0301', ACCENT_ACUTE);
199         addCombining('\u0302', ACCENT_CIRCUMFLEX);
200         addCombining('\u0303', ACCENT_TILDE);
201         addCombining('\u0304', ACCENT_MACRON);
202         addCombining('\u0306', ACCENT_BREVE);
203         addCombining('\u0307', ACCENT_DOT_ABOVE);
204         addCombining('\u0308', ACCENT_UMLAUT);
205         addCombining('\u0309', ACCENT_HOOK_ABOVE);
206         addCombining('\u030A', ACCENT_RING_ABOVE);
207         addCombining('\u030B', ACCENT_DOUBLE_ACUTE);
208         addCombining('\u030C', ACCENT_CARON);
209         //addCombining('\u030F', ACCENT_DOUBLE_GRAVE);
210         //addCombining('\u0310', ACCENT_CANDRABINDU);
211         //addCombining('\u0311', ACCENT_INVERTED_BREVE);
212         addCombining('\u0312', ACCENT_TURNED_COMMA_ABOVE);
213         addCombining('\u0313', ACCENT_COMMA_ABOVE);
214         addCombining('\u0314', ACCENT_REVERSED_COMMA_ABOVE);
215         addCombining('\u0315', ACCENT_COMMA_ABOVE_RIGHT);
216         addCombining('\u031B', ACCENT_HORN);
217         addCombining('\u0323', ACCENT_DOT_BELOW);
218         //addCombining('\u0326', ACCENT_COMMA_BELOW);
219         addCombining('\u0327', ACCENT_CEDILLA);
220         addCombining('\u0328', ACCENT_OGONEK);
221         addCombining('\u0329', ACCENT_VERTICAL_LINE_BELOW);
222         addCombining('\u0331', ACCENT_MACRON_BELOW);
223         addCombining('\u0335', ACCENT_STROKE);
224         //addCombining('\u0342', ACCENT_PERISPOMENI);
225         //addCombining('\u0344', ACCENT_DIALYTIKA_TONOS);
226         //addCombining('\u0345', ACCENT_YPOGEGRAMMENI);
227 
228         // One-way mappings to equivalent preferred accents.
229         sCombiningToAccent.append('\u0340', ACCENT_GRAVE);
230         sCombiningToAccent.append('\u0341', ACCENT_ACUTE);
231         sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE);
232         sCombiningToAccent.append('\u030D', ACCENT_APOSTROPHE);
233         sCombiningToAccent.append('\u030E', ACCENT_QUOTATION_MARK);
234 
235         // One-way legacy mappings to preserve compatibility with older applications.
sAccentToCombining.append(ACCENT_GRAVE_LEGACY, B)236         sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300');
sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, B)237         sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302');
sAccentToCombining.append(ACCENT_TILDE_LEGACY, B)238         sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303');
239 
240         // One-way mappings to use the preferred accent
sAccentToCombining.append(ACCENT_APOSTROPHE, B)241         sAccentToCombining.append(ACCENT_APOSTROPHE, '\u0301');
sAccentToCombining.append(ACCENT_QUOTATION_MARK, B)242         sAccentToCombining.append(ACCENT_QUOTATION_MARK, '\u0308');
243     }
244 
addCombining(int combining, int accent)245     private static void addCombining(int combining, int accent) {
246         sCombiningToAccent.append(combining, accent);
247         sAccentToCombining.append(accent, combining);
248     }
249 
250     /**
251      * Maps combinations of (display-form) combining key and second character
252      * to combined output character.
253      * These mappings are derived from the Unicode NFC tables as needed.
254      */
255     private static final SparseIntArray sDeadKeyCache = new SparseIntArray();
256     private static final StringBuilder sDeadKeyBuilder = new StringBuilder();
257     static {
258         // Non-standard decompositions.
259         // Stroke modifier for Finnish multilingual keyboard and others.
addDeadKey(ACCENT_STROKE, B, B)260         addDeadKey(ACCENT_STROKE, 'D', '\u0110');
addDeadKey(ACCENT_STROKE, B, B)261         addDeadKey(ACCENT_STROKE, 'G', '\u01e4');
addDeadKey(ACCENT_STROKE, B, B)262         addDeadKey(ACCENT_STROKE, 'H', '\u0126');
addDeadKey(ACCENT_STROKE, B, B)263         addDeadKey(ACCENT_STROKE, 'I', '\u0197');
addDeadKey(ACCENT_STROKE, B, B)264         addDeadKey(ACCENT_STROKE, 'L', '\u0141');
addDeadKey(ACCENT_STROKE, B, B)265         addDeadKey(ACCENT_STROKE, 'O', '\u00d8');
addDeadKey(ACCENT_STROKE, B, B)266         addDeadKey(ACCENT_STROKE, 'T', '\u0166');
addDeadKey(ACCENT_STROKE, B, B)267         addDeadKey(ACCENT_STROKE, 'd', '\u0111');
addDeadKey(ACCENT_STROKE, B, B)268         addDeadKey(ACCENT_STROKE, 'g', '\u01e5');
addDeadKey(ACCENT_STROKE, B, B)269         addDeadKey(ACCENT_STROKE, 'h', '\u0127');
addDeadKey(ACCENT_STROKE, B, B)270         addDeadKey(ACCENT_STROKE, 'i', '\u0268');
addDeadKey(ACCENT_STROKE, B, B)271         addDeadKey(ACCENT_STROKE, 'l', '\u0142');
addDeadKey(ACCENT_STROKE, B, B)272         addDeadKey(ACCENT_STROKE, 'o', '\u00f8');
addDeadKey(ACCENT_STROKE, B, B)273         addDeadKey(ACCENT_STROKE, 't', '\u0167');
274     }
275 
addDeadKey(int accent, int c, int result)276     private static void addDeadKey(int accent, int c, int result) {
277         final int combining = sAccentToCombining.get(accent);
278         if (combining == 0) {
279             throw new IllegalStateException("Invalid dead key declaration.");
280         }
281         final int combination = (combining << 16) | c;
282         sDeadKeyCache.put(combination, result);
283     }
284 
285     public static final @android.annotation.NonNull Parcelable.Creator<KeyCharacterMap> CREATOR =
286             new Parcelable.Creator<KeyCharacterMap>() {
287         public KeyCharacterMap createFromParcel(Parcel in) {
288             return new KeyCharacterMap(in);
289         }
290         public KeyCharacterMap[] newArray(int size) {
291             return new KeyCharacterMap[size];
292         }
293     };
294 
295     private long mPtr;
296 
nativeReadFromParcel(Parcel in)297     private static native long nativeReadFromParcel(Parcel in);
nativeWriteToParcel(long ptr, Parcel out)298     private static native void nativeWriteToParcel(long ptr, Parcel out);
nativeDispose(long ptr)299     private static native void nativeDispose(long ptr);
300 
nativeGetCharacter(long ptr, int keyCode, int metaState)301     private static native char nativeGetCharacter(long ptr, int keyCode, int metaState);
nativeGetFallbackAction(long ptr, int keyCode, int metaState, FallbackAction outFallbackAction)302     private static native boolean nativeGetFallbackAction(long ptr, int keyCode, int metaState,
303             FallbackAction outFallbackAction);
nativeGetNumber(long ptr, int keyCode)304     private static native char nativeGetNumber(long ptr, int keyCode);
nativeGetMatch(long ptr, int keyCode, char[] chars, int metaState)305     private static native char nativeGetMatch(long ptr, int keyCode, char[] chars, int metaState);
nativeGetDisplayLabel(long ptr, int keyCode)306     private static native char nativeGetDisplayLabel(long ptr, int keyCode);
nativeGetKeyboardType(long ptr)307     private static native int nativeGetKeyboardType(long ptr);
nativeGetEvents(long ptr, char[] chars)308     private static native KeyEvent[] nativeGetEvents(long ptr, char[] chars);
nativeObtainEmptyKeyCharacterMap(int deviceId)309     private static native KeyCharacterMap nativeObtainEmptyKeyCharacterMap(int deviceId);
nativeEquals(long ptr1, long ptr2)310     private static native boolean nativeEquals(long ptr1, long ptr2);
311 
KeyCharacterMap(Parcel in)312     private KeyCharacterMap(Parcel in) {
313         if (in == null) {
314             throw new IllegalArgumentException("parcel must not be null");
315         }
316         mPtr = nativeReadFromParcel(in);
317         if (mPtr == 0) {
318             throw new RuntimeException("Could not read KeyCharacterMap from parcel.");
319         }
320     }
321 
322     // Called from native
323     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
KeyCharacterMap(long ptr)324     private KeyCharacterMap(long ptr) {
325         mPtr = ptr;
326     }
327 
328     @Override
finalize()329     protected void finalize() throws Throwable {
330         if (mPtr != 0) {
331             nativeDispose(mPtr);
332             mPtr = 0;
333         }
334     }
335 
336     /**
337      * Obtain empty key character map
338      * @param deviceId The input device ID
339      * @return The KeyCharacterMap object
340      * @hide
341      */
342     @VisibleForTesting
343     @Nullable
obtainEmptyMap(int deviceId)344     public static KeyCharacterMap obtainEmptyMap(int deviceId) {
345         return nativeObtainEmptyKeyCharacterMap(deviceId);
346     }
347 
348     /**
349      * Loads the key character maps for the keyboard with the specified device id.
350      *
351      * @param deviceId The device id of the keyboard.
352      * @return The associated key character map.
353      * @throws {@link UnavailableException} if the key character map
354      * could not be loaded because it was malformed or the default key character map
355      * is missing from the system.
356      */
load(int deviceId)357     public static KeyCharacterMap load(int deviceId) {
358         final InputManagerGlobal im = InputManagerGlobal.getInstance();
359         InputDevice inputDevice = im.getInputDevice(deviceId);
360         if (inputDevice == null) {
361             inputDevice = im.getInputDevice(VIRTUAL_KEYBOARD);
362             if (inputDevice == null) {
363                 throw new UnavailableException(
364                         "Could not load key character map for device " + deviceId);
365             }
366         }
367         return inputDevice.getKeyCharacterMap();
368     }
369 
370     /**
371      * Gets the Unicode character generated by the specified key and meta
372      * key state combination.
373      * <p>
374      * Returns the Unicode character that the specified key would produce
375      * when the specified meta bits (see {@link MetaKeyKeyListener})
376      * were active.
377      * </p><p>
378      * Returns 0 if the key is not one that is used to type Unicode
379      * characters.
380      * </p><p>
381      * If the return value has bit {@link #COMBINING_ACCENT} set, the
382      * key is a "dead key" that should be combined with another to
383      * actually produce a character -- see {@link #getDeadChar} --
384      * after masking with {@link #COMBINING_ACCENT_MASK}.
385      * </p>
386      *
387      * @param keyCode The key code.
388      * @param metaState The meta key modifier state.
389      * @return The associated character or combining accent, or 0 if none.
390      */
get(int keyCode, int metaState)391     public int get(int keyCode, int metaState) {
392         metaState = KeyEvent.normalizeMetaState(metaState);
393         char ch = nativeGetCharacter(mPtr, keyCode, metaState);
394 
395         int map = sCombiningToAccent.get(ch);
396         if (map != 0) {
397             return map | COMBINING_ACCENT;
398         } else {
399             return ch;
400         }
401     }
402 
403     /**
404      * Gets the fallback action to perform if the application does not
405      * handle the specified key.
406      * <p>
407      * When an application does not handle a particular key, the system may
408      * translate the key to an alternate fallback key (specified in the
409      * fallback action) and dispatch it to the application.
410      * The event containing the fallback key is flagged
411      * with {@link KeyEvent#FLAG_FALLBACK}.
412      * </p>
413      *
414      * @param keyCode The key code.
415      * @param metaState The meta key modifier state.
416      * @return The fallback action, or null if none.  Remember to recycle the fallback action.
417      *
418      * @hide
419      */
getFallbackAction(int keyCode, int metaState)420     public FallbackAction getFallbackAction(int keyCode, int metaState) {
421         FallbackAction action = FallbackAction.obtain();
422         metaState = KeyEvent.normalizeMetaState(metaState);
423         if (nativeGetFallbackAction(mPtr, keyCode, metaState, action)) {
424             action.metaState = KeyEvent.normalizeMetaState(action.metaState);
425             return action;
426         }
427         action.recycle();
428         return null;
429     }
430 
431     /**
432      * Gets the number or symbol associated with the key.
433      * <p>
434      * The character value is returned, not the numeric value.
435      * If the key is not a number, but is a symbol, the symbol is retuned.
436      * </p><p>
437      * This method is intended to to support dial pads and other numeric or
438      * symbolic entry on keyboards where certain keys serve dual function
439      * as alphabetic and symbolic keys.  This method returns the number
440      * or symbol associated with the key independent of whether the user
441      * has pressed the required modifier.
442      * </p><p>
443      * For example, on one particular keyboard the keys on the top QWERTY row generate
444      * numbers when ALT is pressed such that ALT-Q maps to '1'.  So for that keyboard
445      * when {@link #getNumber} is called with {@link KeyEvent#KEYCODE_Q} it returns '1'
446      * so that the user can type numbers without pressing ALT when it makes sense.
447      * </p>
448      *
449      * @param keyCode The key code.
450      * @return The associated numeric or symbolic character, or 0 if none.
451      */
getNumber(int keyCode)452     public char getNumber(int keyCode) {
453         return nativeGetNumber(mPtr, keyCode);
454     }
455 
456     /**
457      * Gets the first character in the character array that can be generated
458      * by the specified key code.
459      * <p>
460      * This is a convenience function that returns the same value as
461      * {@link #getMatch(int,char[],int) getMatch(keyCode, chars, 0)}.
462      * </p>
463      *
464      * @param keyCode The keycode.
465      * @param chars The array of matching characters to consider.
466      * @return The matching associated character, or 0 if none.
467      * @throws {@link IllegalArgumentException} if the passed array of characters is null.
468      */
getMatch(int keyCode, char[] chars)469     public char getMatch(int keyCode, char[] chars) {
470         return getMatch(keyCode, chars, 0);
471     }
472 
473     /**
474      * Gets the first character in the character array that can be generated
475      * by the specified key code.  If there are multiple choices, prefers
476      * the one that would be generated with the specified meta key modifier state.
477      *
478      * @param keyCode The key code.
479      * @param chars The array of matching characters to consider.
480      * @param metaState The preferred meta key modifier state.
481      * @return The matching associated character, or 0 if none.
482      * @throws {@link IllegalArgumentException} if the passed array of characters is null.
483      */
getMatch(int keyCode, char[] chars, int metaState)484     public char getMatch(int keyCode, char[] chars, int metaState) {
485         if (chars == null) {
486             throw new IllegalArgumentException("chars must not be null.");
487         }
488 
489         metaState = KeyEvent.normalizeMetaState(metaState);
490         return nativeGetMatch(mPtr, keyCode, chars, metaState);
491     }
492 
493     /**
494      * Gets the primary character for this key.
495      * In other words, the label that is physically printed on it.
496      *
497      * @param keyCode The key code.
498      * @return The display label character, or 0 if none (eg. for non-printing keys).
499      */
getDisplayLabel(int keyCode)500     public char getDisplayLabel(int keyCode) {
501         return nativeGetDisplayLabel(mPtr, keyCode);
502     }
503 
504     /**
505      * Get the character that is produced by combining the dead key producing accent
506      * with the key producing character c.
507      * For example, getDeadChar('`', 'e') returns &egrave;.
508      * getDeadChar('^', ' ') returns '^' and getDeadChar('^', '^') returns '^'.
509      *
510      * @param accent The accent character.  eg. '`'
511      * @param c The basic character.
512      * @return The combined character, or 0 if the characters cannot be combined.
513      */
getDeadChar(int accent, int c)514     public static int getDeadChar(int accent, int c) {
515         if (c == accent || CHAR_SPACE == c) {
516             // The same dead character typed twice or a dead character followed by a
517             // space should both produce the non-combining version of the combining char.
518             // In this case we don't even need to compute the combining character.
519             return accent;
520         }
521 
522         int combining = sAccentToCombining.get(accent);
523         if (combining == 0) {
524             return 0;
525         }
526 
527         final int combination = (combining << 16) | c;
528         int combined;
529         synchronized (sDeadKeyCache) {
530             combined = sDeadKeyCache.get(combination, -1);
531             if (combined == -1) {
532                 sDeadKeyBuilder.setLength(0);
533                 sDeadKeyBuilder.append((char)c);
534                 sDeadKeyBuilder.append((char)combining);
535                 String result = Normalizer.normalize(sDeadKeyBuilder, Normalizer.Form.NFC);
536                 combined = result.codePointCount(0, result.length()) == 1
537                         ? result.codePointAt(0) : 0;
538                 sDeadKeyCache.put(combination, combined);
539             }
540         }
541         return combined;
542     }
543 
544     /**
545      * Describes the character mappings associated with a key.
546      *
547      * @deprecated instead use {@link KeyCharacterMap#getDisplayLabel(int)},
548      * {@link KeyCharacterMap#getNumber(int)} and {@link KeyCharacterMap#get(int, int)}.
549      */
550     @Deprecated
551     public static class KeyData {
552         public static final int META_LENGTH = 4;
553 
554         /**
555          * The display label (see {@link #getDisplayLabel}).
556          */
557         public char displayLabel;
558         /**
559          * The "number" value (see {@link #getNumber}).
560          */
561         public char number;
562         /**
563          * The character that will be generated in various meta states
564          * (the same ones used for {@link #get} and defined as
565          * {@link KeyEvent#META_SHIFT_ON} and {@link KeyEvent#META_ALT_ON}).
566          *      <table>
567          *          <tr><th>Index</th><th align="left">Value</th></tr>
568          *          <tr><td>0</td><td>no modifiers</td></tr>
569          *          <tr><td>1</td><td>caps</td></tr>
570          *          <tr><td>2</td><td>alt</td></tr>
571          *          <tr><td>3</td><td>caps + alt</td></tr>
572          *      </table>
573          */
574         public char[] meta = new char[META_LENGTH];
575     }
576 
577     /**
578      * Get the character conversion data for a given key code.
579      *
580      * @param keyCode The keyCode to query.
581      * @param results A {@link KeyData} instance that will be filled with the results.
582      * @return True if the key was mapped.  If the key was not mapped, results is not modified.
583      *
584      * @deprecated instead use {@link KeyCharacterMap#getDisplayLabel(int)},
585      * {@link KeyCharacterMap#getNumber(int)} or {@link KeyCharacterMap#get(int, int)}.
586      */
587     @Deprecated
getKeyData(int keyCode, KeyData results)588     public boolean getKeyData(int keyCode, KeyData results) {
589         if (results.meta.length < KeyData.META_LENGTH) {
590             throw new IndexOutOfBoundsException(
591                     "results.meta.length must be >= " + KeyData.META_LENGTH);
592         }
593 
594         char displayLabel = nativeGetDisplayLabel(mPtr, keyCode);
595         if (displayLabel == 0) {
596             return false;
597         }
598 
599         results.displayLabel = displayLabel;
600         results.number = nativeGetNumber(mPtr, keyCode);
601         results.meta[0] = nativeGetCharacter(mPtr, keyCode, 0);
602         results.meta[1] = nativeGetCharacter(mPtr, keyCode, KeyEvent.META_SHIFT_ON);
603         results.meta[2] = nativeGetCharacter(mPtr, keyCode, KeyEvent.META_ALT_ON);
604         results.meta[3] = nativeGetCharacter(mPtr, keyCode,
605                 KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON);
606         return true;
607     }
608 
609     /**
610      * Get an array of KeyEvent objects that if put into the input stream
611      * could plausibly generate the provided sequence of characters.  It is
612      * not guaranteed that the sequence is the only way to generate these
613      * events or that it is optimal.
614      * <p>
615      * This function is primarily offered for instrumentation and testing purposes.
616      * It may fail to map characters to key codes.  In particular, the key character
617      * map for the {@link #BUILT_IN_KEYBOARD built-in keyboard} device id may be empty.
618      * Consider using the key character map associated with the
619      * {@link #VIRTUAL_KEYBOARD virtual keyboard} device id instead.
620      * </p><p>
621      * For robust text entry, do not use this function.  Instead construct a
622      * {@link KeyEvent} with action code {@link KeyEvent#ACTION_MULTIPLE} that contains
623      * the desired string using {@link KeyEvent#KeyEvent(long, String, int, int)}.
624      * </p>
625      *
626      * @param chars The sequence of characters to generate.
627      * @return An array of {@link KeyEvent} objects, or null if the given char array
628      *         can not be generated using the current key character map.
629      * @throws {@link IllegalArgumentException} if the passed array of characters is null.
630      */
getEvents(char[] chars)631     public KeyEvent[] getEvents(char[] chars) {
632         if (chars == null) {
633             throw new IllegalArgumentException("chars must not be null.");
634         }
635         return nativeGetEvents(mPtr, chars);
636     }
637 
638     /**
639      * Returns true if the specified key produces a glyph.
640      *
641      * @param keyCode The key code.
642      * @return True if the key is a printing key.
643      */
isPrintingKey(int keyCode)644     public boolean isPrintingKey(int keyCode) {
645         int type = Character.getType(nativeGetDisplayLabel(mPtr, keyCode));
646 
647         switch (type)
648         {
649             case Character.SPACE_SEPARATOR:
650             case Character.LINE_SEPARATOR:
651             case Character.PARAGRAPH_SEPARATOR:
652             case Character.CONTROL:
653             case Character.FORMAT:
654                 return false;
655             default:
656                 return true;
657         }
658     }
659 
660     /**
661      * Gets the keyboard type.
662      * Returns {@link #NUMERIC}, {@link #PREDICTIVE}, {@link #ALPHA}, {@link #FULL}
663      * or {@link #SPECIAL_FUNCTION}.
664      * <p>
665      * Different keyboard types have different semantics.  Refer to the documentation
666      * associated with the keyboard type constants for details.
667      * </p>
668      *
669      * @return The keyboard type.
670      */
getKeyboardType()671     public int getKeyboardType() {
672         return nativeGetKeyboardType(mPtr);
673     }
674 
675     /**
676      * Gets a constant that describes the behavior of this keyboard's modifier keys
677      * such as {@link KeyEvent#KEYCODE_SHIFT_LEFT}.
678      * <p>
679      * Currently there are two behaviors that may be combined:
680      * </p>
681      * <ul>
682      * <li>Chorded behavior: When the modifier key is pressed together with one or more
683      * character keys, the keyboard inserts the modified keys and
684      * then resets the modifier state when the modifier key is released.</li>
685      * <li>Toggled behavior: When the modifier key is pressed and released on its own
686      * it first toggles into a latched state.  When latched, the modifier will apply
687      * to next character key that is pressed and will then reset itself to the initial state.
688      * If the modifier is already latched and the modifier key is pressed and release on
689      * its own again, then it toggles into a locked state.  When locked, the modifier will
690      * apply to all subsequent character keys that are pressed until unlocked by pressing
691      * the modifier key on its own one more time to reset it to the initial state.
692      * Toggled behavior is useful for small profile keyboards designed for thumb typing.
693      * </ul>
694      * <p>
695      * This function currently returns {@link #MODIFIER_BEHAVIOR_CHORDED} when the
696      * {@link #getKeyboardType() keyboard type} is {@link #FULL} or {@link #SPECIAL_FUNCTION} and
697      * {@link #MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED} otherwise.
698      * In the future, the function may also take into account global keyboard
699      * accessibility settings, other user preferences, or new device capabilities.
700      * </p>
701      *
702      * @return The modifier behavior for this keyboard.
703      *
704      * @see #MODIFIER_BEHAVIOR_CHORDED
705      * @see #MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED
706      */
getModifierBehavior()707     public int getModifierBehavior() {
708         switch (getKeyboardType()) {
709             case FULL:
710             case SPECIAL_FUNCTION:
711                 return MODIFIER_BEHAVIOR_CHORDED;
712             default:
713                 return MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED;
714         }
715     }
716 
717     /**
718      * Queries the framework about whether any physical keys exist on any currently attached input
719      * devices that are capable of producing the given key code.
720      *
721      * @param keyCode The key code to query.
722      * @return True if at least one attached keyboard supports the specified key code.
723      */
deviceHasKey(int keyCode)724     public static boolean deviceHasKey(int keyCode) {
725         return InputManagerGlobal.getInstance().deviceHasKeys(new int[] { keyCode })[0];
726     }
727 
728     /**
729      * Queries the framework about whether any physical keys exist on any currently attached input
730      * devices that are capable of producing the given array of key codes.
731      *
732      * @param keyCodes The array of key codes to query.
733      * @return A new array of the same size as the key codes array whose elements
734      * are set to true if at least one attached keyboard supports the corresponding key code
735      * at the same index in the key codes array.
736      */
deviceHasKeys(int[] keyCodes)737     public static boolean[] deviceHasKeys(int[] keyCodes) {
738         return InputManagerGlobal.getInstance().deviceHasKeys(keyCodes);
739     }
740 
741     @Override
writeToParcel(Parcel out, int flags)742     public void writeToParcel(Parcel out, int flags) {
743         if (out == null) {
744             throw new IllegalArgumentException("parcel must not be null");
745         }
746         nativeWriteToParcel(mPtr, out);
747     }
748 
749     @Override
describeContents()750     public int describeContents() {
751         return 0;
752     }
753 
754     @Override
equals(Object obj)755     public boolean equals(Object obj) {
756         if (obj == null || !(obj instanceof KeyCharacterMap)) {
757             return false;
758         }
759         KeyCharacterMap peer = (KeyCharacterMap) obj;
760         if (mPtr == 0 || peer.mPtr == 0) {
761             return mPtr == peer.mPtr;
762         }
763         return nativeEquals(mPtr, peer.mPtr);
764     }
765 
766     /**
767      * Thrown by {@link KeyCharacterMap#load} when a key character map could not be loaded.
768      */
769     public static class UnavailableException extends AndroidRuntimeException {
UnavailableException(String msg)770         public UnavailableException(String msg) {
771             super(msg);
772         }
773     }
774 
775     /**
776      * Specifies a substitute key code and meta state as a fallback action
777      * for an unhandled key.
778      * @hide
779      */
780     public static final class FallbackAction {
781         private static final int MAX_RECYCLED = 10;
782         private static final Object sRecycleLock = new Object();
783         private static FallbackAction sRecycleBin;
784         private static int sRecycledCount;
785 
786         private FallbackAction next;
787 
788         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
789         public int keyCode;
790         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
791         public int metaState;
792 
FallbackAction()793         private FallbackAction() {
794         }
795 
obtain()796         public static FallbackAction obtain() {
797             final FallbackAction target;
798             synchronized (sRecycleLock) {
799                 if (sRecycleBin == null) {
800                     target = new FallbackAction();
801                 } else {
802                     target = sRecycleBin;
803                     sRecycleBin = target.next;
804                     sRecycledCount--;
805                     target.next = null;
806                 }
807             }
808             return target;
809         }
810 
recycle()811         public void recycle() {
812             synchronized (sRecycleLock) {
813                 if (sRecycledCount < MAX_RECYCLED) {
814                     next = sRecycleBin;
815                     sRecycleBin = this;
816                     sRecycledCount += 1;
817                 } else {
818                     next = null;
819                 }
820             }
821         }
822     }
823 }
824