/* * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.hardware.input; import android.annotation.NonNull; import android.annotation.Nullable; import android.text.TextUtils; import android.util.SparseIntArray; import android.view.KeyCharacterMap; import android.view.KeyEvent; /** * A complimentary class to {@link KeyboardLayoutPreviewDrawable} describing the physical key layout * of a Physical keyboard and provides information regarding the scan codes produced by the physical * keys. */ final class PhysicalKeyLayout { private static final String TAG = "KeyboardLayoutPreview"; private static final int SCANCODE_1 = 2; private static final int SCANCODE_2 = 3; private static final int SCANCODE_3 = 4; private static final int SCANCODE_4 = 5; private static final int SCANCODE_5 = 6; private static final int SCANCODE_6 = 7; private static final int SCANCODE_7 = 8; private static final int SCANCODE_8 = 9; private static final int SCANCODE_9 = 10; private static final int SCANCODE_0 = 11; private static final int SCANCODE_MINUS = 12; private static final int SCANCODE_EQUALS = 13; private static final int SCANCODE_Q = 16; private static final int SCANCODE_W = 17; private static final int SCANCODE_E = 18; private static final int SCANCODE_R = 19; private static final int SCANCODE_T = 20; private static final int SCANCODE_Y = 21; private static final int SCANCODE_U = 22; private static final int SCANCODE_I = 23; private static final int SCANCODE_O = 24; private static final int SCANCODE_P = 25; private static final int SCANCODE_LEFT_BRACKET = 26; private static final int SCANCODE_RIGHT_BRACKET = 27; private static final int SCANCODE_A = 30; private static final int SCANCODE_S = 31; private static final int SCANCODE_D = 32; private static final int SCANCODE_F = 33; private static final int SCANCODE_G = 34; private static final int SCANCODE_H = 35; private static final int SCANCODE_J = 36; private static final int SCANCODE_K = 37; private static final int SCANCODE_L = 38; private static final int SCANCODE_SEMICOLON = 39; private static final int SCANCODE_APOSTROPHE = 40; private static final int SCANCODE_GRAVE = 41; private static final int SCANCODE_BACKSLASH1 = 43; private static final int SCANCODE_Z = 44; private static final int SCANCODE_X = 45; private static final int SCANCODE_C = 46; private static final int SCANCODE_V = 47; private static final int SCANCODE_B = 48; private static final int SCANCODE_N = 49; private static final int SCANCODE_M = 50; private static final int SCANCODE_COMMA = 51; private static final int SCANCODE_PERIOD = 52; private static final int SCANCODE_SLASH = 53; private static final int SCANCODE_BACKSLASH2 = 86; private static final int SCANCODE_YEN = 124; private static final SparseIntArray DEFAULT_KEYCODE_FOR_SCANCODE = new SparseIntArray(); static { DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_1, KeyEvent.KEYCODE_1); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_2, KeyEvent.KEYCODE_2); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_3, KeyEvent.KEYCODE_3); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_4, KeyEvent.KEYCODE_4); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_5, KeyEvent.KEYCODE_5); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_6, KeyEvent.KEYCODE_6); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_7, KeyEvent.KEYCODE_7); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_8, KeyEvent.KEYCODE_8); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_9, KeyEvent.KEYCODE_9); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_0, KeyEvent.KEYCODE_0); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_MINUS, KeyEvent.KEYCODE_MINUS); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_EQUALS, KeyEvent.KEYCODE_EQUALS); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Q, KeyEvent.KEYCODE_Q); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_W, KeyEvent.KEYCODE_W); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_E, KeyEvent.KEYCODE_E); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_R, KeyEvent.KEYCODE_R); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_T, KeyEvent.KEYCODE_T); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Y, KeyEvent.KEYCODE_Y); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_U, KeyEvent.KEYCODE_U); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_I, KeyEvent.KEYCODE_I); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_O, KeyEvent.KEYCODE_O); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_P, KeyEvent.KEYCODE_P); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_LEFT_BRACKET, KeyEvent.KEYCODE_LEFT_BRACKET); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_RIGHT_BRACKET, KeyEvent.KEYCODE_RIGHT_BRACKET); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_A, KeyEvent.KEYCODE_A); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_S, KeyEvent.KEYCODE_S); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_D, KeyEvent.KEYCODE_D); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_F, KeyEvent.KEYCODE_F); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_G, KeyEvent.KEYCODE_G); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_H, KeyEvent.KEYCODE_H); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_J, KeyEvent.KEYCODE_J); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_K, KeyEvent.KEYCODE_K); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_L, KeyEvent.KEYCODE_L); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_SEMICOLON, KeyEvent.KEYCODE_SEMICOLON); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_APOSTROPHE, KeyEvent.KEYCODE_APOSTROPHE); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_GRAVE, KeyEvent.KEYCODE_GRAVE); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_BACKSLASH1, KeyEvent.KEYCODE_BACKSLASH); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Z, KeyEvent.KEYCODE_Z); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_X, KeyEvent.KEYCODE_X); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_C, KeyEvent.KEYCODE_C); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_V, KeyEvent.KEYCODE_V); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_B, KeyEvent.KEYCODE_B); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_N, KeyEvent.KEYCODE_N); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_M, KeyEvent.KEYCODE_M); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_COMMA, KeyEvent.KEYCODE_COMMA); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_PERIOD, KeyEvent.KEYCODE_PERIOD); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_SLASH, KeyEvent.KEYCODE_SLASH); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_BACKSLASH2, KeyEvent.KEYCODE_BACKSLASH); DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_YEN, KeyEvent.KEYCODE_YEN); } private LayoutKey[][] mKeys = null; private EnterKey mEnterKey = null; public PhysicalKeyLayout(@NonNull KeyCharacterMap kcm, @Nullable KeyboardLayout layout) { initLayoutKeys(kcm, layout); } private void initLayoutKeys(KeyCharacterMap kcm, KeyboardLayout layout) { if (layout == null) { createIsoLayout(kcm); return; } if (layout.isAnsiLayout()) { createAnsiLayout(kcm); } else if (layout.isJisLayout()) { createJisLayout(kcm); } else { createIsoLayout(kcm); } } public LayoutKey[][] getKeys() { return mKeys; } /** * @return Special enter key (if required) that can span multiple rows like ISO enter key. */ @Nullable public EnterKey getEnterKey() { return mEnterKey; } private void createAnsiLayout(KeyCharacterMap kcm) { mKeys = new LayoutKey[][]{ { getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1), getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4), getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7), getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0), getKey(kcm, SCANCODE_MINUS), getKey(kcm, SCANCODE_EQUALS), getKey(KeyEvent.KEYCODE_DEL, 1.5F) }, { getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_Q), getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R), getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U), getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P), getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET), getKey(kcm, SCANCODE_BACKSLASH1) }, { getKey(KeyEvent.KEYCODE_CAPS_LOCK, 1.75F), getKey(kcm, SCANCODE_A), getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D), getKey(kcm, SCANCODE_F), getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H), getKey(kcm, SCANCODE_J), getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L), getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE), getKey(KeyEvent.KEYCODE_ENTER, 1.75F) }, { getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 2.5F), getKey(kcm, SCANCODE_Z), getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C), getKey(kcm, SCANCODE_V), getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N), getKey(kcm, SCANCODE_M), getKey(kcm, SCANCODE_COMMA), getKey(kcm, SCANCODE_PERIOD), getKey(kcm, SCANCODE_SLASH), getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.5F), }, { getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F), getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F), getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F), getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F), getKey(KeyEvent.KEYCODE_SPACE, 6.5F), getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F), getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F), getKey(KeyEvent.KEYCODE_MENU, 1.0F), getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F), } }; } private void createIsoLayout(KeyCharacterMap kcm) { mKeys = new LayoutKey[][]{ { getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1), getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4), getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7), getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0), getKey(kcm, SCANCODE_MINUS), getKey(kcm, SCANCODE_EQUALS), getKey(KeyEvent.KEYCODE_DEL, 1.5F) }, { getKey(KeyEvent.KEYCODE_TAB, 1.15F), getKey(kcm, SCANCODE_Q), getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R), getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U), getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P), getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET), getKey(KeyEvent.KEYCODE_ENTER, 1.35F) }, { getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_A), getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D), getKey(kcm, SCANCODE_F), getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H), getKey(kcm, SCANCODE_J), getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L), getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE), getKey(kcm, SCANCODE_BACKSLASH1), getKey(KeyEvent.KEYCODE_ENTER, 1.0F) }, { getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 1.15F), getKey(kcm, SCANCODE_BACKSLASH2), getKey(kcm, SCANCODE_Z), getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C), getKey(kcm, SCANCODE_V), getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N), getKey(kcm, SCANCODE_M), getKey(kcm, SCANCODE_COMMA), getKey(kcm, SCANCODE_PERIOD), getKey(kcm, SCANCODE_SLASH), getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.35F) }, { getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F), getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F), getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F), getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F), getKey(KeyEvent.KEYCODE_SPACE, 6.5F), getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F), getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F), getKey(KeyEvent.KEYCODE_MENU, 1.0F), getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F), } }; mEnterKey = new EnterKey(1, 13, 1.35F, 1.0F); } private void createJisLayout(KeyCharacterMap kcm) { mKeys = new LayoutKey[][]{ { getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1), getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4), getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7), getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0), getKey(kcm, SCANCODE_MINUS, 0.8F), getKey(kcm, SCANCODE_EQUALS, 0.8f), getKey(kcm, SCANCODE_YEN, 0.8f), getKey(KeyEvent.KEYCODE_DEL, 1.1F) }, { getKey(KeyEvent.KEYCODE_TAB, 1.15F), getKey(kcm, SCANCODE_Q), getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R), getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U), getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P), getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET), getKey(KeyEvent.KEYCODE_ENTER, 1.35F) }, { getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_A), getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D), getKey(kcm, SCANCODE_F), getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H), getKey(kcm, SCANCODE_J), getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L), getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE), getKey(kcm, SCANCODE_BACKSLASH2), getKey(KeyEvent.KEYCODE_ENTER, 1.0F) }, { getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 1.15F), getKey(kcm, SCANCODE_Z), getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C), getKey(kcm, SCANCODE_V), getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N), getKey(kcm, SCANCODE_M), getKey(kcm, SCANCODE_COMMA), getKey(kcm, SCANCODE_PERIOD), getKey(kcm, SCANCODE_SLASH), getKey(kcm, SCANCODE_BACKSLASH1), getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.35F) }, { getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F), getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F), getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F), getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F), getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F), getKey(KeyEvent.KEYCODE_SPACE, 3.5F), getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F), getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F), getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F), getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F), getKey(KeyEvent.KEYCODE_MENU, 1.0F), getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F), } }; mEnterKey = new EnterKey(1, 13, 1.35F, 1.0F); } private static LayoutKey getKey(KeyCharacterMap kcm, int scanCode, float keyWeight) { int keyCode = kcm.getMappedKeyOrDefault(scanCode, DEFAULT_KEYCODE_FOR_SCANCODE.get(scanCode, KeyEvent.KEYCODE_UNKNOWN)); return new LayoutKey(keyCode, scanCode, keyWeight, new KeyGlyph(kcm, keyCode)); } private static LayoutKey getKey(KeyCharacterMap kcm, int scanCode) { return getKey(kcm, scanCode, 1.0F); } private static String getKeyText(KeyCharacterMap kcm, int keyCode, int modifierState) { if (isSpecialKey(keyCode)) { return ""; } int utf8Char = (kcm.get(keyCode, modifierState) & KeyCharacterMap.COMBINING_ACCENT_MASK); if (utf8Char == 0) { return ""; } if (Character.isValidCodePoint(utf8Char)) { return String.valueOf(Character.toChars(utf8Char)); } return "□"; } private static LayoutKey getKey(int keyCode, float keyWeight) { return new LayoutKey(keyCode, keyCode, keyWeight, null); } /** * Util function that tells if a key corresponds to a special key which are keys on a Physical * layout that perform some special action like modifier keys, enter key, space key, character * set changing keys, etc. */ private static boolean isSpecialKey(int keyCode) { switch (keyCode) { case KeyEvent.KEYCODE_DEL: case KeyEvent.KEYCODE_TAB: case KeyEvent.KEYCODE_CAPS_LOCK: case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: case KeyEvent.KEYCODE_CTRL_LEFT: case KeyEvent.KEYCODE_CTRL_RIGHT: case KeyEvent.KEYCODE_FUNCTION: case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: case KeyEvent.KEYCODE_META_LEFT: case KeyEvent.KEYCODE_META_RIGHT: case KeyEvent.KEYCODE_SPACE: case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_UNKNOWN: return true; } return false; } public static boolean isSpecialKey(LayoutKey key) { return isSpecialKey(key.keyCode); } public static boolean isKeyPositionUnsure(LayoutKey key) { switch (key.scanCode) { case SCANCODE_GRAVE: case SCANCODE_BACKSLASH1: case SCANCODE_BACKSLASH2: return true; } return false; } public record LayoutKey(int keyCode, int scanCode, float keyWeight, KeyGlyph glyph) {} public record EnterKey(int row, int column, float topKeyWeight, float bottomKeyWeight) {} public static class KeyGlyph { private final String mBaseText; private final String mShiftText; private final String mAltGrText; private final String mAltGrShiftText; public KeyGlyph(KeyCharacterMap kcm, int keyCode) { mBaseText = getKeyText(kcm, keyCode, KeyEvent.META_CAPS_LOCK_ON); mShiftText = getKeyText(kcm, keyCode, KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON); mAltGrText = getKeyText(kcm, keyCode, KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_CAPS_LOCK_ON); mAltGrShiftText = getKeyText(kcm, keyCode, KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON); } public String getBaseText() { return mBaseText; } public String getShiftText() { return mShiftText; } public String getAltGrText() { return mAltGrText; } public String getAltGrShiftText() { return mAltGrShiftText; } public boolean hasBaseText() { return !TextUtils.isEmpty(mBaseText); } public boolean hasValidShiftText() { return !TextUtils.isEmpty(mShiftText) && !TextUtils.equals(mBaseText, mShiftText); } public boolean hasValidAltGrText() { return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText) && !TextUtils.equals(mShiftText, mAltGrText); } public boolean hasValidAltGrShiftText() { return !TextUtils.isEmpty(mAltGrShiftText) && !TextUtils.equals(mBaseText, mAltGrShiftText) && !TextUtils.equals(mAltGrText, mAltGrShiftText) && !TextUtils.equals(mShiftText, mAltGrShiftText); } } }