1/*
2 * Copyright (C) 2010 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
17package com.android.inputmethod.keyboard;
18
19import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
20
21import android.text.InputType;
22import android.text.TextUtils;
23import android.view.inputmethod.EditorInfo;
24import android.view.inputmethod.InputMethodSubtype;
25
26import com.android.inputmethod.compat.EditorInfoCompatUtils;
27import com.android.inputmethod.latin.utils.InputTypeUtils;
28import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
29
30import java.util.Arrays;
31import java.util.Locale;
32
33/**
34 * Unique identifier for each keyboard type.
35 */
36public final class KeyboardId {
37    public static final int MODE_TEXT = 0;
38    public static final int MODE_URL = 1;
39    public static final int MODE_EMAIL = 2;
40    public static final int MODE_IM = 3;
41    public static final int MODE_PHONE = 4;
42    public static final int MODE_NUMBER = 5;
43    public static final int MODE_DATE = 6;
44    public static final int MODE_TIME = 7;
45    public static final int MODE_DATETIME = 8;
46
47    public static final int ELEMENT_ALPHABET = 0;
48    public static final int ELEMENT_ALPHABET_MANUAL_SHIFTED = 1;
49    public static final int ELEMENT_ALPHABET_AUTOMATIC_SHIFTED = 2;
50    public static final int ELEMENT_ALPHABET_SHIFT_LOCKED = 3;
51    public static final int ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED = 4;
52    public static final int ELEMENT_SYMBOLS = 5;
53    public static final int ELEMENT_SYMBOLS_SHIFTED = 6;
54    public static final int ELEMENT_PHONE = 7;
55    public static final int ELEMENT_PHONE_SYMBOLS = 8;
56    public static final int ELEMENT_NUMBER = 9;
57    public static final int ELEMENT_EMOJI_RECENTS = 10;
58    public static final int ELEMENT_EMOJI_CATEGORY1 = 11;
59    public static final int ELEMENT_EMOJI_CATEGORY2 = 12;
60    public static final int ELEMENT_EMOJI_CATEGORY3 = 13;
61    public static final int ELEMENT_EMOJI_CATEGORY4 = 14;
62    public static final int ELEMENT_EMOJI_CATEGORY5 = 15;
63    public static final int ELEMENT_EMOJI_CATEGORY6 = 16;
64
65    public final InputMethodSubtype mSubtype;
66    public final Locale mLocale;
67    public final int mWidth;
68    public final int mHeight;
69    public final int mMode;
70    public final int mElementId;
71    private final EditorInfo mEditorInfo;
72    public final boolean mClobberSettingsKey;
73    public final boolean mShortcutKeyEnabled;
74    public final boolean mShortcutKeyOnSymbols;
75    public final boolean mLanguageSwitchKeyEnabled;
76    public final String mCustomActionLabel;
77    public final boolean mHasShortcutKey;
78
79    private final int mHashCode;
80
81    public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) {
82        mSubtype = params.mSubtype;
83        mLocale = SubtypeLocaleUtils.getSubtypeLocale(mSubtype);
84        mWidth = params.mKeyboardWidth;
85        mHeight = params.mKeyboardHeight;
86        mMode = params.mMode;
87        mElementId = elementId;
88        mEditorInfo = params.mEditorInfo;
89        mClobberSettingsKey = params.mNoSettingsKey;
90        mShortcutKeyEnabled = params.mVoiceKeyEnabled;
91        mShortcutKeyOnSymbols = mShortcutKeyEnabled && !params.mVoiceKeyOnMain;
92        mLanguageSwitchKeyEnabled = params.mLanguageSwitchKeyEnabled;
93        mCustomActionLabel = (mEditorInfo.actionLabel != null)
94                ? mEditorInfo.actionLabel.toString() : null;
95        final boolean alphabetMayHaveShortcutKey = isAlphabetKeyboard(elementId)
96                && !mShortcutKeyOnSymbols;
97        final boolean symbolsMayHaveShortcutKey = (elementId == KeyboardId.ELEMENT_SYMBOLS)
98                && mShortcutKeyOnSymbols;
99        mHasShortcutKey = mShortcutKeyEnabled
100                && (alphabetMayHaveShortcutKey || symbolsMayHaveShortcutKey);
101
102        mHashCode = computeHashCode(this);
103    }
104
105    private static int computeHashCode(final KeyboardId id) {
106        return Arrays.hashCode(new Object[] {
107                id.mElementId,
108                id.mMode,
109                id.mWidth,
110                id.mHeight,
111                id.passwordInput(),
112                id.mClobberSettingsKey,
113                id.mShortcutKeyEnabled,
114                id.mShortcutKeyOnSymbols,
115                id.mLanguageSwitchKeyEnabled,
116                id.isMultiLine(),
117                id.imeAction(),
118                id.mCustomActionLabel,
119                id.navigateNext(),
120                id.navigatePrevious(),
121                id.mSubtype
122        });
123    }
124
125    private boolean equals(final KeyboardId other) {
126        if (other == this)
127            return true;
128        return other.mElementId == mElementId
129                && other.mMode == mMode
130                && other.mWidth == mWidth
131                && other.mHeight == mHeight
132                && other.passwordInput() == passwordInput()
133                && other.mClobberSettingsKey == mClobberSettingsKey
134                && other.mShortcutKeyEnabled == mShortcutKeyEnabled
135                && other.mShortcutKeyOnSymbols == mShortcutKeyOnSymbols
136                && other.mLanguageSwitchKeyEnabled == mLanguageSwitchKeyEnabled
137                && other.isMultiLine() == isMultiLine()
138                && other.imeAction() == imeAction()
139                && TextUtils.equals(other.mCustomActionLabel, mCustomActionLabel)
140                && other.navigateNext() == navigateNext()
141                && other.navigatePrevious() == navigatePrevious()
142                && other.mSubtype.equals(mSubtype);
143    }
144
145    private static boolean isAlphabetKeyboard(final int elementId) {
146        return elementId < ELEMENT_SYMBOLS;
147    }
148
149    public boolean isAlphabetKeyboard() {
150        return isAlphabetKeyboard(mElementId);
151    }
152
153    public boolean navigateNext() {
154        return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0
155                || imeAction() == EditorInfo.IME_ACTION_NEXT;
156    }
157
158    public boolean navigatePrevious() {
159        return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0
160                || imeAction() == EditorInfo.IME_ACTION_PREVIOUS;
161    }
162
163    public boolean passwordInput() {
164        final int inputType = mEditorInfo.inputType;
165        return InputTypeUtils.isPasswordInputType(inputType)
166                || InputTypeUtils.isVisiblePasswordInputType(inputType);
167    }
168
169    public boolean isMultiLine() {
170        return (mEditorInfo.inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
171    }
172
173    public int imeAction() {
174        return InputTypeUtils.getImeOptionsActionIdFromEditorInfo(mEditorInfo);
175    }
176
177    @Override
178    public boolean equals(final Object other) {
179        return other instanceof KeyboardId && equals((KeyboardId) other);
180    }
181
182    @Override
183    public int hashCode() {
184        return mHashCode;
185    }
186
187    @Override
188    public String toString() {
189        return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s %s%s%s%s%s%s%s%s%s]",
190                elementIdToName(mElementId),
191                mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
192                mWidth, mHeight,
193                modeName(mMode),
194                imeAction(),
195                (navigateNext() ? "navigateNext" : ""),
196                (navigatePrevious() ? "navigatePrevious" : ""),
197                (mClobberSettingsKey ? " clobberSettingsKey" : ""),
198                (passwordInput() ? " passwordInput" : ""),
199                (mShortcutKeyEnabled ? " shortcutKeyEnabled" : ""),
200                (mShortcutKeyOnSymbols ? " shortcutKeyOnSymbols" : ""),
201                (mHasShortcutKey ? " hasShortcutKey" : ""),
202                (mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
203                (isMultiLine() ? "isMultiLine" : "")
204        );
205    }
206
207    public static boolean equivalentEditorInfoForKeyboard(final EditorInfo a, final EditorInfo b) {
208        if (a == null && b == null) return true;
209        if (a == null || b == null) return false;
210        return a.inputType == b.inputType
211                && a.imeOptions == b.imeOptions
212                && TextUtils.equals(a.privateImeOptions, b.privateImeOptions);
213    }
214
215    public static String elementIdToName(final int elementId) {
216        switch (elementId) {
217        case ELEMENT_ALPHABET: return "alphabet";
218        case ELEMENT_ALPHABET_MANUAL_SHIFTED: return "alphabetManualShifted";
219        case ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: return "alphabetAutomaticShifted";
220        case ELEMENT_ALPHABET_SHIFT_LOCKED: return "alphabetShiftLocked";
221        case ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: return "alphabetShiftLockShifted";
222        case ELEMENT_SYMBOLS: return "symbols";
223        case ELEMENT_SYMBOLS_SHIFTED: return "symbolsShifted";
224        case ELEMENT_PHONE: return "phone";
225        case ELEMENT_PHONE_SYMBOLS: return "phoneSymbols";
226        case ELEMENT_NUMBER: return "number";
227        case ELEMENT_EMOJI_RECENTS: return "emojiRecents";
228        case ELEMENT_EMOJI_CATEGORY1: return "emojiCategory1";
229        case ELEMENT_EMOJI_CATEGORY2: return "emojiCategory2";
230        case ELEMENT_EMOJI_CATEGORY3: return "emojiCategory3";
231        case ELEMENT_EMOJI_CATEGORY4: return "emojiCategory4";
232        case ELEMENT_EMOJI_CATEGORY5: return "emojiCategory5";
233        case ELEMENT_EMOJI_CATEGORY6: return "emojiCategory6";
234        default: return null;
235        }
236    }
237
238    public static String modeName(final int mode) {
239        switch (mode) {
240        case MODE_TEXT: return "text";
241        case MODE_URL: return "url";
242        case MODE_EMAIL: return "email";
243        case MODE_IM: return "im";
244        case MODE_PHONE: return "phone";
245        case MODE_NUMBER: return "number";
246        case MODE_DATE: return "date";
247        case MODE_TIME: return "time";
248        case MODE_DATETIME: return "datetime";
249        default: return null;
250        }
251    }
252
253    public static String actionName(final int actionId) {
254        return (actionId == InputTypeUtils.IME_ACTION_CUSTOM_LABEL) ? "actionCustomLabel"
255                : EditorInfoCompatUtils.imeActionName(actionId);
256    }
257}
258