diff options
author | 2024-12-16 21:45:41 -0500 | |
---|---|---|
committer | 2025-01-11 14:17:35 -0500 | |
commit | e9a0e66716dab4dd3184d009d8920de1961efdfa (patch) | |
tree | 02dcc096643d74645bf28459c2834c3d4a2ad7f2 /java/src/com/android/inputmethod | |
parent | fb3b9360d70596d7e921de8bf7d3ca99564a077e (diff) | |
download | latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.gz latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.xz latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.zip |
Rename to Kelar Keyboard (org.kelar.inputmethod.latin)
Diffstat (limited to 'java/src/com/android/inputmethod')
303 files changed, 0 insertions, 63689 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityLongPressTimer.java b/java/src/com/android/inputmethod/accessibility/AccessibilityLongPressTimer.java deleted file mode 100644 index 37d910edb..000000000 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityLongPressTimer.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.accessibility; - -import android.content.Context; -import android.os.Handler; -import android.os.Message; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.latin.R; - -// Handling long press timer to show a more keys keyboard. -final class AccessibilityLongPressTimer extends Handler { - public interface LongPressTimerCallback { - public void performLongClickOn(Key key); - } - - private static final int MSG_LONG_PRESS = 1; - - private final LongPressTimerCallback mCallback; - private final long mConfigAccessibilityLongPressTimeout; - - public AccessibilityLongPressTimer(final LongPressTimerCallback callback, - final Context context) { - super(); - mCallback = callback; - mConfigAccessibilityLongPressTimeout = context.getResources().getInteger( - R.integer.config_accessibility_long_press_key_timeout); - } - - @Override - public void handleMessage(final Message msg) { - switch (msg.what) { - case MSG_LONG_PRESS: - cancelLongPress(); - mCallback.performLongClickOn((Key)msg.obj); - return; - default: - super.handleMessage(msg); - return; - } - } - - public void startLongPress(final Key key) { - cancelLongPress(); - final Message longPressMessage = obtainMessage(MSG_LONG_PRESS, key); - sendMessageDelayed(longPressMessage, mConfigAccessibilityLongPressTimeout); - } - - public void cancelLongPress() { - removeMessages(MSG_LONG_PRESS); - } -} diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java deleted file mode 100644 index 31e142e72..000000000 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.accessibility; - -import android.content.Context; -import android.media.AudioManager; -import android.os.Build; -import android.os.SystemClock; -import android.provider.Settings; -import androidx.core.view.accessibility.AccessibilityEventCompat; -import android.text.TextUtils; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.inputmethod.EditorInfo; - -import com.android.inputmethod.compat.SettingsSecureCompatUtils; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.utils.InputTypeUtils; - -public final class AccessibilityUtils { - private static final String TAG = AccessibilityUtils.class.getSimpleName(); - private static final String CLASS = AccessibilityUtils.class.getName(); - private static final String PACKAGE = - AccessibilityUtils.class.getPackage().getName(); - - private static final AccessibilityUtils sInstance = new AccessibilityUtils(); - - private Context mContext; - private AccessibilityManager mAccessibilityManager; - private AudioManager mAudioManager; - - /** The most recent auto-correction. */ - private String mAutoCorrectionWord; - - /** The most recent typed word for auto-correction. */ - private String mTypedWord; - - /* - * Setting this constant to {@code false} will disable all keyboard - * accessibility code, regardless of whether Accessibility is turned on in - * the system settings. It should ONLY be used in the event of an emergency. - */ - private static final boolean ENABLE_ACCESSIBILITY = true; - - public static void init(final Context context) { - if (!ENABLE_ACCESSIBILITY) return; - - // These only need to be initialized if the kill switch is off. - sInstance.initInternal(context); - } - - public static AccessibilityUtils getInstance() { - return sInstance; - } - - private AccessibilityUtils() { - // This class is not publicly instantiable. - } - - private void initInternal(final Context context) { - mContext = context; - mAccessibilityManager = - (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); - mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - } - - /** - * Returns {@code true} if accessibility is enabled. Currently, this means - * that the kill switch is off and system accessibility is turned on. - * - * @return {@code true} if accessibility is enabled. - */ - public boolean isAccessibilityEnabled() { - return ENABLE_ACCESSIBILITY && mAccessibilityManager.isEnabled(); - } - - /** - * Returns {@code true} if touch exploration is enabled. Currently, this - * means that the kill switch is off, the device supports touch exploration, - * and system accessibility is turned on. - * - * @return {@code true} if touch exploration is enabled. - */ - public boolean isTouchExplorationEnabled() { - return isAccessibilityEnabled() && mAccessibilityManager.isTouchExplorationEnabled(); - } - - /** - * Returns {@true} if the provided event is a touch exploration (e.g. hover) - * event. This is used to determine whether the event should be processed by - * the touch exploration code within the keyboard. - * - * @param event The event to check. - * @return {@true} is the event is a touch exploration event - */ - public static boolean isTouchExplorationEvent(final MotionEvent event) { - final int action = event.getAction(); - return action == MotionEvent.ACTION_HOVER_ENTER - || action == MotionEvent.ACTION_HOVER_EXIT - || action == MotionEvent.ACTION_HOVER_MOVE; - } - - /** - * Returns whether the device should obscure typed password characters. - * Typically this means speaking "dot" in place of non-control characters. - * - * @return {@code true} if the device should obscure password characters. - */ - @SuppressWarnings("deprecation") - public boolean shouldObscureInput(final EditorInfo editorInfo) { - if (editorInfo == null) return false; - - // The user can optionally force speaking passwords. - if (SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD != null) { - final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(), - SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0; - if (speakPassword) return false; - } - - // Always speak if the user is listening through headphones. - if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) { - return false; - } - - // Don't speak if the IME is connected to a password field. - return InputTypeUtils.isPasswordInputType(editorInfo.inputType); - } - - /** - * Sets the current auto-correction word and typed word. These may be used - * to provide the user with a spoken description of what auto-correction - * will occur when a key is typed. - * - * @param suggestedWords the list of suggested auto-correction words - */ - public void setAutoCorrection(final SuggestedWords suggestedWords) { - if (suggestedWords.mWillAutoCorrect) { - mAutoCorrectionWord = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION); - final SuggestedWords.SuggestedWordInfo typedWordInfo = suggestedWords.mTypedWordInfo; - if (null == typedWordInfo) { - mTypedWord = null; - } else { - mTypedWord = typedWordInfo.mWord; - } - } else { - mAutoCorrectionWord = null; - mTypedWord = null; - } - } - - /** - * Obtains a description for an auto-correction key, taking into account the - * currently typed word and auto-correction. - * - * @param keyCodeDescription spoken description of the key that will insert - * an auto-correction - * @param shouldObscure whether the key should be obscured - * @return a description including a description of the auto-correction, if - * needed - */ - public String getAutoCorrectionDescription( - final String keyCodeDescription, final boolean shouldObscure) { - if (!TextUtils.isEmpty(mAutoCorrectionWord)) { - if (!TextUtils.equals(mAutoCorrectionWord, mTypedWord)) { - if (shouldObscure) { - // This should never happen, but just in case... - return mContext.getString(R.string.spoken_auto_correct_obscured, - keyCodeDescription); - } - return mContext.getString(R.string.spoken_auto_correct, keyCodeDescription, - mTypedWord, mAutoCorrectionWord); - } - } - - return keyCodeDescription; - } - - /** - * Sends the specified text to the {@link AccessibilityManager} to be - * spoken. - * - * @param view The source view. - * @param text The text to speak. - */ - public void announceForAccessibility(final View view, final CharSequence text) { - if (!mAccessibilityManager.isEnabled()) { - Log.e(TAG, "Attempted to speak when accessibility was disabled!"); - return; - } - - // The following is a hack to avoid using the heavy-weight TextToSpeech - // class. Instead, we're just forcing a fake AccessibilityEvent into - // the screen reader to make it speak. - final AccessibilityEvent event = AccessibilityEvent.obtain(); - - event.setPackageName(PACKAGE); - event.setClassName(CLASS); - event.setEventTime(SystemClock.uptimeMillis()); - event.setEnabled(true); - event.getText().add(text); - - // Platforms starting at SDK version 16 (Build.VERSION_CODES.JELLY_BEAN) should use - // announce events. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - event.setEventType(AccessibilityEventCompat.TYPE_ANNOUNCEMENT); - } else { - event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED); - } - - final ViewParent viewParent = view.getParent(); - if ((viewParent == null) || !(viewParent instanceof ViewGroup)) { - Log.e(TAG, "Failed to obtain ViewParent in announceForAccessibility"); - return; - } - - viewParent.requestSendAccessibilityEvent(view, event); - } - - /** - * Handles speaking the "connect a headset to hear passwords" notification - * when connecting to a password field. - * - * @param view The source view. - * @param editorInfo The input connection's editor info attribute. - * @param restarting Whether the connection is being restarted. - */ - public void onStartInputViewInternal(final View view, final EditorInfo editorInfo, - final boolean restarting) { - if (shouldObscureInput(editorInfo)) { - final CharSequence text = mContext.getText(R.string.spoken_use_headphones); - announceForAccessibility(view, text); - } - } - - /** - * Sends the specified {@link AccessibilityEvent} if accessibility is - * enabled. No operation if accessibility is disabled. - * - * @param event The event to send. - */ - public void requestSendAccessibilityEvent(final AccessibilityEvent event) { - if (mAccessibilityManager.isEnabled()) { - mAccessibilityManager.sendAccessibilityEvent(event); - } - } -} diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java deleted file mode 100644 index bbda9f8e2..000000000 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.accessibility; - -import android.content.Context; -import android.content.res.Resources; -import android.text.TextUtils; -import android.util.Log; -import android.util.SparseIntArray; -import android.view.inputmethod.EditorInfo; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; - -import java.util.Locale; - -final class KeyCodeDescriptionMapper { - private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName(); - private static final String SPOKEN_LETTER_RESOURCE_NAME_FORMAT = "spoken_accented_letter_%04X"; - private static final String SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT = "spoken_symbol_%04X"; - private static final String SPOKEN_EMOJI_RESOURCE_NAME_FORMAT = "spoken_emoji_%04X"; - private static final String SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX = "spoken_emoticon"; - private static final String SPOKEN_EMOTICON_CODE_POINT_FORMAT = "_%02X"; - - // The resource ID of the string spoken for obscured keys - private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot; - - private static final KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper(); - - public static KeyCodeDescriptionMapper getInstance() { - return sInstance; - } - - // Sparse array of spoken description resource IDs indexed by key codes - private final SparseIntArray mKeyCodeMap = new SparseIntArray(); - - private KeyCodeDescriptionMapper() { - // Special non-character codes defined in Keyboard - mKeyCodeMap.put(Constants.CODE_SPACE, R.string.spoken_description_space); - mKeyCodeMap.put(Constants.CODE_DELETE, R.string.spoken_description_delete); - mKeyCodeMap.put(Constants.CODE_ENTER, R.string.spoken_description_return); - mKeyCodeMap.put(Constants.CODE_SETTINGS, R.string.spoken_description_settings); - mKeyCodeMap.put(Constants.CODE_SHIFT, R.string.spoken_description_shift); - mKeyCodeMap.put(Constants.CODE_SHORTCUT, R.string.spoken_description_mic); - mKeyCodeMap.put(Constants.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol); - mKeyCodeMap.put(Constants.CODE_TAB, R.string.spoken_description_tab); - mKeyCodeMap.put(Constants.CODE_LANGUAGE_SWITCH, - R.string.spoken_description_language_switch); - mKeyCodeMap.put(Constants.CODE_ACTION_NEXT, R.string.spoken_description_action_next); - mKeyCodeMap.put(Constants.CODE_ACTION_PREVIOUS, - R.string.spoken_description_action_previous); - mKeyCodeMap.put(Constants.CODE_EMOJI, R.string.spoken_description_emoji); - // Because the upper-case and lower-case mappings of the following letters is depending on - // the locale, the upper case descriptions should be defined here. The lower case - // descriptions are handled in {@link #getSpokenLetterDescriptionId(Context,int)}. - // U+0049: "I" LATIN CAPITAL LETTER I - // U+0069: "i" LATIN SMALL LETTER I - // U+0130: "İ" LATIN CAPITAL LETTER I WITH DOT ABOVE - // U+0131: "ı" LATIN SMALL LETTER DOTLESS I - mKeyCodeMap.put(0x0049, R.string.spoken_letter_0049); - mKeyCodeMap.put(0x0130, R.string.spoken_letter_0130); - } - - /** - * Returns the localized description of the action performed by a specified - * key based on the current keyboard state. - * - * @param context The package's context. - * @param keyboard The keyboard on which the key resides. - * @param key The key from which to obtain a description. - * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. - * @return a character sequence describing the action performed by pressing the key - */ - public String getDescriptionForKey(final Context context, final Keyboard keyboard, - final Key key, final boolean shouldObscure) { - final int code = key.getCode(); - - if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { - final String description = getDescriptionForSwitchAlphaSymbol(context, keyboard); - if (description != null) { - return description; - } - } - - if (code == Constants.CODE_SHIFT) { - return getDescriptionForShiftKey(context, keyboard); - } - - if (code == Constants.CODE_ENTER) { - // The following function returns the correct description in all action and - // regular enter cases, taking care of all modes. - return getDescriptionForActionKey(context, keyboard, key); - } - - if (code == Constants.CODE_OUTPUT_TEXT) { - final String outputText = key.getOutputText(); - final String description = getSpokenEmoticonDescription(context, outputText); - return TextUtils.isEmpty(description) ? outputText : description; - } - - // Just attempt to speak the description. - if (code != Constants.CODE_UNSPECIFIED) { - // If the key description should be obscured, now is the time to do it. - final boolean isDefinedNonCtrl = Character.isDefined(code) - && !Character.isISOControl(code); - if (shouldObscure && isDefinedNonCtrl) { - return context.getString(OBSCURED_KEY_RES_ID); - } - final String description = getDescriptionForCodePoint(context, code); - if (description != null) { - return description; - } - if (!TextUtils.isEmpty(key.getLabel())) { - return key.getLabel(); - } - return context.getString(R.string.spoken_description_unknown); - } - return null; - } - - /** - * Returns a context-specific description for the CODE_SWITCH_ALPHA_SYMBOL - * key or {@code null} if there is not a description provided for the - * current keyboard context. - * - * @param context The package's context. - * @param keyboard The keyboard on which the key resides. - * @return a character sequence describing the action performed by pressing the key - */ - private static String getDescriptionForSwitchAlphaSymbol(final Context context, - final Keyboard keyboard) { - final KeyboardId keyboardId = keyboard.mId; - final int elementId = keyboardId.mElementId; - final int resId; - - switch (elementId) { - case KeyboardId.ELEMENT_ALPHABET: - case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: - case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: - case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: - case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: - resId = R.string.spoken_description_to_symbol; - break; - case KeyboardId.ELEMENT_SYMBOLS: - case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: - resId = R.string.spoken_description_to_alpha; - break; - case KeyboardId.ELEMENT_PHONE: - resId = R.string.spoken_description_to_symbol; - break; - case KeyboardId.ELEMENT_PHONE_SYMBOLS: - resId = R.string.spoken_description_to_numeric; - break; - default: - Log.e(TAG, "Missing description for keyboard element ID:" + elementId); - return null; - } - return context.getString(resId); - } - - /** - * Returns a context-sensitive description of the "Shift" key. - * - * @param context The package's context. - * @param keyboard The keyboard on which the key resides. - * @return A context-sensitive description of the "Shift" key. - */ - private static String getDescriptionForShiftKey(final Context context, - final Keyboard keyboard) { - final KeyboardId keyboardId = keyboard.mId; - final int elementId = keyboardId.mElementId; - final int resId; - - switch (elementId) { - case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: - case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: - resId = R.string.spoken_description_caps_lock; - break; - case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: - case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: - resId = R.string.spoken_description_shift_shifted; - break; - case KeyboardId.ELEMENT_SYMBOLS: - resId = R.string.spoken_description_symbols_shift; - break; - case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: - resId = R.string.spoken_description_symbols_shift_shifted; - break; - default: - resId = R.string.spoken_description_shift; - } - return context.getString(resId); - } - - /** - * Returns a context-sensitive description of the "Enter" action key. - * - * @param context The package's context. - * @param keyboard The keyboard on which the key resides. - * @param key The key to describe. - * @return Returns a context-sensitive description of the "Enter" action key. - */ - private static String getDescriptionForActionKey(final Context context, final Keyboard keyboard, - final Key key) { - final KeyboardId keyboardId = keyboard.mId; - final int actionId = keyboardId.imeAction(); - final int resId; - - // Always use the label, if available. - if (!TextUtils.isEmpty(key.getLabel())) { - return key.getLabel().trim(); - } - - // Otherwise, use the action ID. - switch (actionId) { - case EditorInfo.IME_ACTION_SEARCH: - resId = R.string.spoken_description_search; - break; - case EditorInfo.IME_ACTION_GO: - resId = R.string.label_go_key; - break; - case EditorInfo.IME_ACTION_SEND: - resId = R.string.label_send_key; - break; - case EditorInfo.IME_ACTION_NEXT: - resId = R.string.label_next_key; - break; - case EditorInfo.IME_ACTION_DONE: - resId = R.string.label_done_key; - break; - case EditorInfo.IME_ACTION_PREVIOUS: - resId = R.string.label_previous_key; - break; - default: - resId = R.string.spoken_description_return; - } - return context.getString(resId); - } - - /** - * Returns a localized character sequence describing what will happen when - * the specified key is pressed based on its key code point. - * - * @param context The package's context. - * @param codePoint The code point from which to obtain a description. - * @return a character sequence describing the code point. - */ - public String getDescriptionForCodePoint(final Context context, final int codePoint) { - // If the key description should be obscured, now is the time to do it. - final int index = mKeyCodeMap.indexOfKey(codePoint); - if (index >= 0) { - return context.getString(mKeyCodeMap.valueAt(index)); - } - final String accentedLetter = getSpokenAccentedLetterDescription(context, codePoint); - if (accentedLetter != null) { - return accentedLetter; - } - // Here, <code>code</code> may be a base (non-accented) letter. - final String unsupportedSymbol = getSpokenSymbolDescription(context, codePoint); - if (unsupportedSymbol != null) { - return unsupportedSymbol; - } - final String emojiDescription = getSpokenEmojiDescription(context, codePoint); - if (emojiDescription != null) { - return emojiDescription; - } - if (Character.isDefined(codePoint) && !Character.isISOControl(codePoint)) { - return StringUtils.newSingleCodePointString(codePoint); - } - return null; - } - - // TODO: Remove this method once TTS supports those accented letters' verbalization. - private String getSpokenAccentedLetterDescription(final Context context, final int code) { - final boolean isUpperCase = Character.isUpperCase(code); - final int baseCode = isUpperCase ? Character.toLowerCase(code) : code; - final int baseIndex = mKeyCodeMap.indexOfKey(baseCode); - final int resId = (baseIndex >= 0) ? mKeyCodeMap.valueAt(baseIndex) - : getSpokenDescriptionId(context, baseCode, SPOKEN_LETTER_RESOURCE_NAME_FORMAT); - if (resId == 0) { - return null; - } - final String spokenText = context.getString(resId); - return isUpperCase ? context.getString(R.string.spoken_description_upper_case, spokenText) - : spokenText; - } - - // TODO: Remove this method once TTS supports those symbols' verbalization. - private String getSpokenSymbolDescription(final Context context, final int code) { - final int resId = getSpokenDescriptionId(context, code, SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT); - if (resId == 0) { - return null; - } - final String spokenText = context.getString(resId); - if (!TextUtils.isEmpty(spokenText)) { - return spokenText; - } - // If a translated description is empty, fall back to unknown symbol description. - return context.getString(R.string.spoken_symbol_unknown); - } - - // TODO: Remove this method once TTS supports emoji verbalization. - private String getSpokenEmojiDescription(final Context context, final int code) { - final int resId = getSpokenDescriptionId(context, code, SPOKEN_EMOJI_RESOURCE_NAME_FORMAT); - if (resId == 0) { - return null; - } - final String spokenText = context.getString(resId); - if (!TextUtils.isEmpty(spokenText)) { - return spokenText; - } - // If a translated description is empty, fall back to unknown emoji description. - return context.getString(R.string.spoken_emoji_unknown); - } - - private int getSpokenDescriptionId(final Context context, final int code, - final String resourceNameFormat) { - final String resourceName = String.format(Locale.ROOT, resourceNameFormat, code); - final Resources resources = context.getResources(); - // Note that the resource package name may differ from the context package name. - final String resourcePackageName = resources.getResourcePackageName( - R.string.spoken_description_unknown); - final int resId = resources.getIdentifier(resourceName, "string", resourcePackageName); - if (resId != 0) { - mKeyCodeMap.append(code, resId); - } - return resId; - } - - // TODO: Remove this method once TTS supports emoticon verbalization. - private static String getSpokenEmoticonDescription(final Context context, - final String outputText) { - final StringBuilder sb = new StringBuilder(SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX); - final int textLength = outputText.length(); - for (int index = 0; index < textLength; index = outputText.offsetByCodePoints(index, 1)) { - final int codePoint = outputText.codePointAt(index); - sb.append(String.format(Locale.ROOT, SPOKEN_EMOTICON_CODE_POINT_FORMAT, codePoint)); - } - final String resourceName = sb.toString(); - final Resources resources = context.getResources(); - // Note that the resource package name may differ from the context package name. - final String resourcePackageName = resources.getResourcePackageName( - R.string.spoken_description_unknown); - final int resId = resources.getIdentifier(resourceName, "string", resourcePackageName); - return (resId == 0) ? null : resources.getString(resId); - } -} diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java deleted file mode 100644 index 5c03d26a9..000000000 --- a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.accessibility; - -import android.content.Context; -import android.os.SystemClock; -import androidx.core.view.AccessibilityDelegateCompat; -import androidx.core.view.ViewCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.KeyDetector; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardView; - -/** - * This class represents a delegate that can be registered in a class that extends - * {@link KeyboardView} to enhance accessibility support via composition rather via inheritance. - * - * To implement accessibility mode, the target keyboard view has to:<p> - * - Call {@link #setKeyboard(Keyboard)} when a new keyboard is set to the keyboard view. - * - Dispatch a hover event by calling {@link #onHoverEnter(MotionEvent)}. - * - * @param <KV> The keyboard view class type. - */ -public class KeyboardAccessibilityDelegate<KV extends KeyboardView> - extends AccessibilityDelegateCompat { - private static final String TAG = KeyboardAccessibilityDelegate.class.getSimpleName(); - protected static final boolean DEBUG_HOVER = false; - - protected final KV mKeyboardView; - protected final KeyDetector mKeyDetector; - private Keyboard mKeyboard; - private KeyboardAccessibilityNodeProvider<KV> mAccessibilityNodeProvider; - private Key mLastHoverKey; - - public static final int HOVER_EVENT_POINTER_ID = 0; - - public KeyboardAccessibilityDelegate(final KV keyboardView, final KeyDetector keyDetector) { - super(); - mKeyboardView = keyboardView; - mKeyDetector = keyDetector; - - // Ensure that the view has an accessibility delegate. - ViewCompat.setAccessibilityDelegate(keyboardView, this); - } - - /** - * Called when the keyboard layout changes. - * <p> - * <b>Note:</b> This method will be called even if accessibility is not - * enabled. - * @param keyboard The keyboard that is being set to the wrapping view. - */ - public void setKeyboard(final Keyboard keyboard) { - if (keyboard == null) { - return; - } - if (mAccessibilityNodeProvider != null) { - mAccessibilityNodeProvider.setKeyboard(keyboard); - } - mKeyboard = keyboard; - } - - protected final Keyboard getKeyboard() { - return mKeyboard; - } - - protected final void setLastHoverKey(final Key key) { - mLastHoverKey = key; - } - - protected final Key getLastHoverKey() { - return mLastHoverKey; - } - - /** - * Sends a window state change event with the specified string resource id. - * - * @param resId The string resource id of the text to send with the event. - */ - protected void sendWindowStateChanged(final int resId) { - if (resId == 0) { - return; - } - final Context context = mKeyboardView.getContext(); - sendWindowStateChanged(context.getString(resId)); - } - - /** - * Sends a window state change event with the specified text. - * - * @param text The text to send with the event. - */ - protected void sendWindowStateChanged(final String text) { - final AccessibilityEvent stateChange = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - mKeyboardView.onInitializeAccessibilityEvent(stateChange); - stateChange.getText().add(text); - stateChange.setContentDescription(null); - - final ViewParent parent = mKeyboardView.getParent(); - if (parent != null) { - parent.requestSendAccessibilityEvent(mKeyboardView, stateChange); - } - } - - /** - * Delegate method for View.getAccessibilityNodeProvider(). This method is called in SDK - * version 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) and higher to obtain the virtual - * node hierarchy provider. - * - * @param host The host view for the provider. - * @return The accessibility node provider for the current keyboard. - */ - @Override - public KeyboardAccessibilityNodeProvider<KV> getAccessibilityNodeProvider(final View host) { - return getAccessibilityNodeProvider(); - } - - /** - * @return A lazily-instantiated node provider for this view delegate. - */ - protected KeyboardAccessibilityNodeProvider<KV> getAccessibilityNodeProvider() { - // Instantiate the provide only when requested. Since the system - // will call this method multiple times it is a good practice to - // cache the provider instance. - if (mAccessibilityNodeProvider == null) { - mAccessibilityNodeProvider = - new KeyboardAccessibilityNodeProvider<>(mKeyboardView, this); - } - return mAccessibilityNodeProvider; - } - - /** - * Get a key that a hover event is on. - * - * @param event The hover event. - * @return key The key that the <code>event</code> is on. - */ - protected final Key getHoverKeyOf(final MotionEvent event) { - final int actionIndex = event.getActionIndex(); - final int x = (int)event.getX(actionIndex); - final int y = (int)event.getY(actionIndex); - return mKeyDetector.detectHitKey(x, y); - } - - /** - * Receives hover events when touch exploration is turned on in SDK versions ICS and higher. - * - * @param event The hover event. - * @return {@code true} if the event is handled. - */ - public boolean onHoverEvent(final MotionEvent event) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_HOVER_ENTER: - onHoverEnter(event); - break; - case MotionEvent.ACTION_HOVER_MOVE: - onHoverMove(event); - break; - case MotionEvent.ACTION_HOVER_EXIT: - onHoverExit(event); - break; - default: - Log.w(getClass().getSimpleName(), "Unknown hover event: " + event); - break; - } - return true; - } - - /** - * Process {@link MotionEvent#ACTION_HOVER_ENTER} event. - * - * @param event A hover enter event. - */ - protected void onHoverEnter(final MotionEvent event) { - final Key key = getHoverKeyOf(event); - if (DEBUG_HOVER) { - Log.d(TAG, "onHoverEnter: key=" + key); - } - if (key != null) { - onHoverEnterTo(key); - } - setLastHoverKey(key); - } - - /** - * Process {@link MotionEvent#ACTION_HOVER_MOVE} event. - * - * @param event A hover move event. - */ - protected void onHoverMove(final MotionEvent event) { - final Key lastKey = getLastHoverKey(); - final Key key = getHoverKeyOf(event); - if (key != lastKey) { - if (lastKey != null) { - onHoverExitFrom(lastKey); - } - if (key != null) { - onHoverEnterTo(key); - } - } - if (key != null) { - onHoverMoveWithin(key); - } - setLastHoverKey(key); - } - - /** - * Process {@link MotionEvent#ACTION_HOVER_EXIT} event. - * - * @param event A hover exit event. - */ - protected void onHoverExit(final MotionEvent event) { - final Key lastKey = getLastHoverKey(); - if (DEBUG_HOVER) { - Log.d(TAG, "onHoverExit: key=" + getHoverKeyOf(event) + " last=" + lastKey); - } - if (lastKey != null) { - onHoverExitFrom(lastKey); - } - final Key key = getHoverKeyOf(event); - // Make sure we're not getting an EXIT event because the user slid - // off the keyboard area, then force a key press. - if (key != null) { - onHoverExitFrom(key); - } - setLastHoverKey(null); - } - - /** - * Perform click on a key. - * - * @param key A key to be registered. - */ - public void performClickOn(final Key key) { - if (DEBUG_HOVER) { - Log.d(TAG, "performClickOn: key=" + key); - } - simulateTouchEvent(MotionEvent.ACTION_DOWN, key); - simulateTouchEvent(MotionEvent.ACTION_UP, key); - } - - /** - * Simulating a touch event by injecting a synthesized touch event into {@link KeyboardView}. - * - * @param touchAction The action of the synthesizing touch event. - * @param key The key that a synthesized touch event is on. - */ - private void simulateTouchEvent(final int touchAction, final Key key) { - final int x = key.getHitBox().centerX(); - final int y = key.getHitBox().centerY(); - final long eventTime = SystemClock.uptimeMillis(); - final MotionEvent touchEvent = MotionEvent.obtain( - eventTime, eventTime, touchAction, x, y, 0 /* metaState */); - mKeyboardView.onTouchEvent(touchEvent); - touchEvent.recycle(); - } - - /** - * Handles a hover enter event on a key. - * - * @param key The currently hovered key. - */ - protected void onHoverEnterTo(final Key key) { - if (DEBUG_HOVER) { - Log.d(TAG, "onHoverEnterTo: key=" + key); - } - key.onPressed(); - mKeyboardView.invalidateKey(key); - final KeyboardAccessibilityNodeProvider<KV> provider = getAccessibilityNodeProvider(); - provider.onHoverEnterTo(key); - provider.performActionForKey(key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS); - } - - /** - * Handles a hover move event on a key. - * - * @param key The currently hovered key. - */ - protected void onHoverMoveWithin(final Key key) { } - - /** - * Handles a hover exit event on a key. - * - * @param key The currently hovered key. - */ - protected void onHoverExitFrom(final Key key) { - if (DEBUG_HOVER) { - Log.d(TAG, "onHoverExitFrom: key=" + key); - } - key.onReleased(); - mKeyboardView.invalidateKey(key); - final KeyboardAccessibilityNodeProvider<KV> provider = getAccessibilityNodeProvider(); - provider.onHoverExitFrom(key); - } - - /** - * Perform long click on a key. - * - * @param key A key to be long pressed on. - */ - public void performLongClickOn(final Key key) { - // A extended class should override this method to implement long press. - } -} diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java deleted file mode 100644 index cc244c305..000000000 --- a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.accessibility; - -import android.graphics.Rect; -import android.os.Bundle; -import androidx.core.view.ViewCompat; -import androidx.core.view.accessibility.AccessibilityEventCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import androidx.core.view.accessibility.AccessibilityNodeProviderCompat; -import androidx.core.view.accessibility.AccessibilityRecordCompat; -import android.util.Log; -import android.view.View; -import android.view.accessibility.AccessibilityEvent; -import android.view.inputmethod.EditorInfo; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.latin.common.CoordinateUtils; -import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.settings.SettingsValues; - -import java.util.List; - -/** - * Exposes a virtual view sub-tree for {@link KeyboardView} and generates - * {@link AccessibilityEvent}s for individual {@link Key}s. - * <p> - * A virtual sub-tree is composed of imaginary {@link View}s that are reported - * as a part of the view hierarchy for accessibility purposes. This enables - * custom views that draw complex content to report them selves as a tree of - * virtual views, thus conveying their logical structure. - * </p> - */ -final class KeyboardAccessibilityNodeProvider<KV extends KeyboardView> - extends AccessibilityNodeProviderCompat { - private static final String TAG = KeyboardAccessibilityNodeProvider.class.getSimpleName(); - - // From {@link android.view.accessibility.AccessibilityNodeInfo#UNDEFINED_ITEM_ID}. - private static final int UNDEFINED = Integer.MAX_VALUE; - - private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper; - private final AccessibilityUtils mAccessibilityUtils; - - /** Temporary rect used to calculate in-screen bounds. */ - private final Rect mTempBoundsInScreen = new Rect(); - - /** The parent view's cached on-screen location. */ - private final int[] mParentLocation = CoordinateUtils.newInstance(); - - /** The virtual view identifier for the focused node. */ - private int mAccessibilityFocusedView = UNDEFINED; - - /** The virtual view identifier for the hovering node. */ - private int mHoveringNodeId = UNDEFINED; - - /** The keyboard view to provide an accessibility node info. */ - private final KV mKeyboardView; - /** The accessibility delegate. */ - private final KeyboardAccessibilityDelegate<KV> mDelegate; - - /** The current keyboard. */ - private Keyboard mKeyboard; - - public KeyboardAccessibilityNodeProvider(final KV keyboardView, - final KeyboardAccessibilityDelegate<KV> delegate) { - super(); - mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance(); - mAccessibilityUtils = AccessibilityUtils.getInstance(); - mKeyboardView = keyboardView; - mDelegate = delegate; - - // Since this class is constructed lazily, we might not get a subsequent - // call to setKeyboard() and therefore need to call it now. - setKeyboard(keyboardView.getKeyboard()); - } - - /** - * Sets the keyboard represented by this node provider. - * - * @param keyboard The keyboard that is being set to the keyboard view. - */ - public void setKeyboard(final Keyboard keyboard) { - mKeyboard = keyboard; - } - - private Key getKeyOf(final int virtualViewId) { - if (mKeyboard == null) { - return null; - } - final List<Key> sortedKeys = mKeyboard.getSortedKeys(); - // Use a virtual view id as an index of the sorted keys list. - if (virtualViewId >= 0 && virtualViewId < sortedKeys.size()) { - return sortedKeys.get(virtualViewId); - } - return null; - } - - private int getVirtualViewIdOf(final Key key) { - if (mKeyboard == null) { - return View.NO_ID; - } - final List<Key> sortedKeys = mKeyboard.getSortedKeys(); - final int size = sortedKeys.size(); - for (int index = 0; index < size; index++) { - if (sortedKeys.get(index) == key) { - // Use an index of the sorted keys list as a virtual view id. - return index; - } - } - return View.NO_ID; - } - - /** - * Creates and populates an {@link AccessibilityEvent} for the specified key - * and event type. - * - * @param key A key on the host keyboard view. - * @param eventType The event type to create. - * @return A populated {@link AccessibilityEvent} for the key. - * @see AccessibilityEvent - */ - public AccessibilityEvent createAccessibilityEvent(final Key key, final int eventType) { - final int virtualViewId = getVirtualViewIdOf(key); - final String keyDescription = getKeyDescription(key); - final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); - event.setPackageName(mKeyboardView.getContext().getPackageName()); - event.setClassName(key.getClass().getName()); - event.setContentDescription(keyDescription); - event.setEnabled(true); - final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event); - record.setSource(mKeyboardView, virtualViewId); - return event; - } - - public void onHoverEnterTo(final Key key) { - final int id = getVirtualViewIdOf(key); - if (id == View.NO_ID) { - return; - } - // Start hovering on the key. Because our accessibility model is lift-to-type, we should - // report the node info without click and long click actions to avoid unnecessary - // announcements. - mHoveringNodeId = id; - // Invalidate the node info of the key. - sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED); - sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER); - } - - public void onHoverExitFrom(final Key key) { - mHoveringNodeId = UNDEFINED; - // Invalidate the node info of the key to be able to revert the change we have done - // in {@link #onHoverEnterTo(Key)}. - sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED); - sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT); - } - - /** - * Returns an {@link AccessibilityNodeInfoCompat} representing a virtual - * view, i.e. a descendant of the host View, with the given <code>virtualViewId</code> or - * the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}. - * <p> - * A virtual descendant is an imaginary View that is reported as a part of - * the view hierarchy for accessibility purposes. This enables custom views - * that draw complex content to report them selves as a tree of virtual - * views, thus conveying their logical structure. - * </p> - * <p> - * The implementer is responsible for obtaining an accessibility node info - * from the pool of reusable instances and setting the desired properties of - * the node info before returning it. - * </p> - * - * @param virtualViewId A client defined virtual view id. - * @return A populated {@link AccessibilityNodeInfoCompat} for a virtual descendant or the host - * View. - * @see AccessibilityNodeInfoCompat - */ - @Override - public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(final int virtualViewId) { - if (virtualViewId == UNDEFINED) { - return null; - } - if (virtualViewId == View.NO_ID) { - // We are requested to create an AccessibilityNodeInfo describing - // this View, i.e. the root of the virtual sub-tree. - final AccessibilityNodeInfoCompat rootInfo = - AccessibilityNodeInfoCompat.obtain(mKeyboardView); - ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, rootInfo); - updateParentLocation(); - - // Add the virtual children of the root View. - final List<Key> sortedKeys = mKeyboard.getSortedKeys(); - final int size = sortedKeys.size(); - for (int index = 0; index < size; index++) { - final Key key = sortedKeys.get(index); - if (key.isSpacer()) { - continue; - } - // Use an index of the sorted keys list as a virtual view id. - rootInfo.addChild(mKeyboardView, index); - } - return rootInfo; - } - - // Find the key that corresponds to the given virtual view id. - final Key key = getKeyOf(virtualViewId); - if (key == null) { - Log.e(TAG, "Invalid virtual view ID: " + virtualViewId); - return null; - } - final String keyDescription = getKeyDescription(key); - final Rect boundsInParent = key.getHitBox(); - - // Calculate the key's in-screen bounds. - mTempBoundsInScreen.set(boundsInParent); - mTempBoundsInScreen.offset( - CoordinateUtils.x(mParentLocation), CoordinateUtils.y(mParentLocation)); - final Rect boundsInScreen = mTempBoundsInScreen; - - // Obtain and initialize an AccessibilityNodeInfo with information about the virtual view. - final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); - info.setPackageName(mKeyboardView.getContext().getPackageName()); - // info.setTextEntryKey(true); - info.setClassName(key.getClass().getName()); - info.setContentDescription(keyDescription); - info.setBoundsInParent(boundsInParent); - info.setBoundsInScreen(boundsInScreen); - info.setParent(mKeyboardView); - info.setSource(mKeyboardView, virtualViewId); - info.setEnabled(key.isEnabled()); - info.setVisibleToUser(true); - info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); - if (key.isLongPressEnabled()) { - info.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK); - } - - if (mAccessibilityFocusedView == virtualViewId) { - info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS); - } else { - info.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS); - } - return info; - } - - @Override - public boolean performAction(final int virtualViewId, final int action, - final Bundle arguments) { - final Key key = getKeyOf(virtualViewId); - if (key == null) { - return false; - } - return performActionForKey(key, action); - } - - /** - * Performs the specified accessibility action for the given key. - * - * @param key The on which to perform the action. - * @param action The action to perform. - * @return The result of performing the action, or false if the action is not supported. - */ - boolean performActionForKey(final Key key, final int action) { - switch (action) { - case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS: - mAccessibilityFocusedView = getVirtualViewIdOf(key); - sendAccessibilityEventForKey( - key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED); - return true; - case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS: - mAccessibilityFocusedView = UNDEFINED; - sendAccessibilityEventForKey( - key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); - return true; - case AccessibilityNodeInfoCompat.ACTION_CLICK: - sendAccessibilityEventForKey(key, AccessibilityEvent.TYPE_VIEW_CLICKED); - mDelegate.performClickOn(key); - return true; - case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK: - sendAccessibilityEventForKey(key, AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); - mDelegate.performLongClickOn(key); - return true; - default: - return false; - } - } - - /** - * Sends an accessibility event for the given {@link Key}. - * - * @param key The key that's sending the event. - * @param eventType The type of event to send. - */ - void sendAccessibilityEventForKey(final Key key, final int eventType) { - final AccessibilityEvent event = createAccessibilityEvent(key, eventType); - mAccessibilityUtils.requestSendAccessibilityEvent(event); - } - - /** - * Returns the context-specific description for a {@link Key}. - * - * @param key The key to describe. - * @return The context-specific description of the key. - */ - private String getKeyDescription(final Key key) { - final EditorInfo editorInfo = mKeyboard.mId.mEditorInfo; - final boolean shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo); - final SettingsValues currentSettings = Settings.getInstance().getCurrent(); - final String keyCodeDescription = mKeyCodeDescriptionMapper.getDescriptionForKey( - mKeyboardView.getContext(), mKeyboard, key, shouldObscure); - if (currentSettings.isWordSeparator(key.getCode())) { - return mAccessibilityUtils.getAutoCorrectionDescription( - keyCodeDescription, shouldObscure); - } - return keyCodeDescription; - } - - /** - * Updates the parent's on-screen location. - */ - private void updateParentLocation() { - mKeyboardView.getLocationOnScreen(mParentLocation); - } -} diff --git a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java deleted file mode 100644 index 3234993cf..000000000 --- a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.accessibility; - -import android.content.Context; -import android.graphics.Rect; -import android.os.SystemClock; -import android.util.Log; -import android.util.SparseIntArray; -import android.view.MotionEvent; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.KeyDetector; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.keyboard.MainKeyboardView; -import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; - -/** - * This class represents a delegate that can be registered in {@link MainKeyboardView} to enhance - * accessibility support via composition rather via inheritance. - */ -public final class MainKeyboardAccessibilityDelegate - extends KeyboardAccessibilityDelegate<MainKeyboardView> - implements AccessibilityLongPressTimer.LongPressTimerCallback { - private static final String TAG = MainKeyboardAccessibilityDelegate.class.getSimpleName(); - - /** Map of keyboard modes to resource IDs. */ - private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray(); - - static { - KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATE, R.string.keyboard_mode_date); - KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATETIME, R.string.keyboard_mode_date_time); - KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_EMAIL, R.string.keyboard_mode_email); - KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_IM, R.string.keyboard_mode_im); - KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_NUMBER, R.string.keyboard_mode_number); - KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_PHONE, R.string.keyboard_mode_phone); - KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TEXT, R.string.keyboard_mode_text); - KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TIME, R.string.keyboard_mode_time); - KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_URL, R.string.keyboard_mode_url); - } - - /** The most recently set keyboard mode. */ - private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN; - private static final int KEYBOARD_IS_HIDDEN = -1; - // The rectangle region to ignore hover events. - private final Rect mBoundsToIgnoreHoverEvent = new Rect(); - - - public MainKeyboardAccessibilityDelegate(final MainKeyboardView mainKeyboardView, - final KeyDetector keyDetector) { - super(mainKeyboardView, keyDetector); - } - - /** - * {@inheritDoc} - */ - @Override - public void setKeyboard(final Keyboard keyboard) { - if (keyboard == null) { - return; - } - final Keyboard lastKeyboard = getKeyboard(); - super.setKeyboard(keyboard); - final int lastKeyboardMode = mLastKeyboardMode; - mLastKeyboardMode = keyboard.mId.mMode; - - // Since this method is called even when accessibility is off, make sure - // to check the state before announcing anything. - if (!AccessibilityUtils.getInstance().isAccessibilityEnabled()) { - return; - } - // Announce the language name only when the language is changed. - if (lastKeyboard == null || !keyboard.mId.mSubtype.equals(lastKeyboard.mId.mSubtype)) { - announceKeyboardLanguage(keyboard); - return; - } - // Announce the mode only when the mode is changed. - if (keyboard.mId.mMode != lastKeyboardMode) { - announceKeyboardMode(keyboard); - return; - } - // Announce the keyboard type only when the type is changed. - if (keyboard.mId.mElementId != lastKeyboard.mId.mElementId) { - announceKeyboardType(keyboard, lastKeyboard); - return; - } - } - - /** - * Called when the keyboard is hidden and accessibility is enabled. - */ - public void onHideWindow() { - if (mLastKeyboardMode != KEYBOARD_IS_HIDDEN) { - announceKeyboardHidden(); - } - mLastKeyboardMode = KEYBOARD_IS_HIDDEN; - } - - /** - * Announces which language of keyboard is being displayed. - * - * @param keyboard The new keyboard. - */ - private void announceKeyboardLanguage(final Keyboard keyboard) { - final String languageText = SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale( - keyboard.mId.mSubtype.getRawSubtype()); - sendWindowStateChanged(languageText); - } - - /** - * Announces which type of keyboard is being displayed. - * If the keyboard type is unknown, no announcement is made. - * - * @param keyboard The new keyboard. - */ - private void announceKeyboardMode(final Keyboard keyboard) { - final Context context = mKeyboardView.getContext(); - final int modeTextResId = KEYBOARD_MODE_RES_IDS.get(keyboard.mId.mMode); - if (modeTextResId == 0) { - return; - } - final String modeText = context.getString(modeTextResId); - final String text = context.getString(R.string.announce_keyboard_mode, modeText); - sendWindowStateChanged(text); - } - - /** - * Announces which type of keyboard is being displayed. - * - * @param keyboard The new keyboard. - * @param lastKeyboard The last keyboard. - */ - private void announceKeyboardType(final Keyboard keyboard, final Keyboard lastKeyboard) { - final int lastElementId = lastKeyboard.mId.mElementId; - final int resId; - switch (keyboard.mId.mElementId) { - case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: - case KeyboardId.ELEMENT_ALPHABET: - if (lastElementId == KeyboardId.ELEMENT_ALPHABET - || lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { - // Transition between alphabet mode and automatic shifted mode should be silently - // ignored because it can be determined by each key's talk back announce. - return; - } - resId = R.string.spoken_description_mode_alpha; - break; - case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: - if (lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { - // Resetting automatic shifted mode by pressing the shift key causes the transition - // from automatic shifted to manual shifted that should be silently ignored. - return; - } - resId = R.string.spoken_description_shiftmode_on; - break; - case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: - if (lastElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED) { - // Resetting caps locked mode by pressing the shift key causes the transition - // from shift locked to shift lock shifted that should be silently ignored. - return; - } - resId = R.string.spoken_description_shiftmode_locked; - break; - case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: - resId = R.string.spoken_description_shiftmode_locked; - break; - case KeyboardId.ELEMENT_SYMBOLS: - resId = R.string.spoken_description_mode_symbol; - break; - case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: - resId = R.string.spoken_description_mode_symbol_shift; - break; - case KeyboardId.ELEMENT_PHONE: - resId = R.string.spoken_description_mode_phone; - break; - case KeyboardId.ELEMENT_PHONE_SYMBOLS: - resId = R.string.spoken_description_mode_phone_shift; - break; - default: - return; - } - sendWindowStateChanged(resId); - } - - /** - * Announces that the keyboard has been hidden. - */ - private void announceKeyboardHidden() { - sendWindowStateChanged(R.string.announce_keyboard_hidden); - } - - @Override - public void performClickOn(final Key key) { - final int x = key.getHitBox().centerX(); - final int y = key.getHitBox().centerY(); - if (DEBUG_HOVER) { - Log.d(TAG, "performClickOn: key=" + key - + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)); - } - if (mBoundsToIgnoreHoverEvent.contains(x, y)) { - // This hover exit event points to the key that should be ignored. - // Clear the ignoring region to handle further hover events. - mBoundsToIgnoreHoverEvent.setEmpty(); - return; - } - super.performClickOn(key); - } - - @Override - protected void onHoverEnterTo(final Key key) { - final int x = key.getHitBox().centerX(); - final int y = key.getHitBox().centerY(); - if (DEBUG_HOVER) { - Log.d(TAG, "onHoverEnterTo: key=" + key - + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)); - } - if (mBoundsToIgnoreHoverEvent.contains(x, y)) { - return; - } - // This hover enter event points to the key that isn't in the ignoring region. - // Further hover events should be handled. - mBoundsToIgnoreHoverEvent.setEmpty(); - super.onHoverEnterTo(key); - } - - @Override - protected void onHoverExitFrom(final Key key) { - final int x = key.getHitBox().centerX(); - final int y = key.getHitBox().centerY(); - if (DEBUG_HOVER) { - Log.d(TAG, "onHoverExitFrom: key=" + key - + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)); - } - super.onHoverExitFrom(key); - } - - @Override - public void performLongClickOn(final Key key) { - if (DEBUG_HOVER) { - Log.d(TAG, "performLongClickOn: key=" + key); - } - final PointerTracker tracker = PointerTracker.getPointerTracker(HOVER_EVENT_POINTER_ID); - final long eventTime = SystemClock.uptimeMillis(); - final int x = key.getHitBox().centerX(); - final int y = key.getHitBox().centerY(); - final MotionEvent downEvent = MotionEvent.obtain( - eventTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0 /* metaState */); - // Inject a fake down event to {@link PointerTracker} to handle a long press correctly. - tracker.processMotionEvent(downEvent, mKeyDetector); - downEvent.recycle(); - // Invoke {@link PointerTracker#onLongPressed()} as if a long press timeout has passed. - tracker.onLongPressed(); - // If {@link Key#hasNoPanelAutoMoreKeys()} is true (such as "0 +" key on the phone layout) - // or a key invokes IME switcher dialog, we should just ignore the next - // {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether - // {@link PointerTracker} is in operation or not. - if (tracker.isInOperation()) { - // This long press shows a more keys keyboard and further hover events should be - // handled. - mBoundsToIgnoreHoverEvent.setEmpty(); - return; - } - // This long press has handled at {@link MainKeyboardView#onLongPress(PointerTracker)}. - // We should ignore further hover events on this key. - mBoundsToIgnoreHoverEvent.set(key.getHitBox()); - if (key.hasNoPanelAutoMoreKey()) { - // This long press has registered a code point without showing a more keys keyboard. - // We should talk back the code point if possible. - final int codePointOfNoPanelAutoMoreKey = key.getMoreKeys()[0].mCode; - final String text = KeyCodeDescriptionMapper.getInstance().getDescriptionForCodePoint( - mKeyboardView.getContext(), codePointOfNoPanelAutoMoreKey); - if (text != null) { - sendWindowStateChanged(text); - } - } - } -} diff --git a/java/src/com/android/inputmethod/accessibility/MoreKeysKeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MoreKeysKeyboardAccessibilityDelegate.java deleted file mode 100644 index 4022da343..000000000 --- a/java/src/com/android/inputmethod/accessibility/MoreKeysKeyboardAccessibilityDelegate.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.accessibility; - -import android.graphics.Rect; -import android.util.Log; -import android.view.MotionEvent; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.KeyDetector; -import com.android.inputmethod.keyboard.MoreKeysKeyboardView; -import com.android.inputmethod.keyboard.PointerTracker; - -/** - * This class represents a delegate that can be registered in {@link MoreKeysKeyboardView} to - * enhance accessibility support via composition rather via inheritance. - */ -public class MoreKeysKeyboardAccessibilityDelegate - extends KeyboardAccessibilityDelegate<MoreKeysKeyboardView> { - private static final String TAG = MoreKeysKeyboardAccessibilityDelegate.class.getSimpleName(); - - private final Rect mMoreKeysKeyboardValidBounds = new Rect(); - private static final int CLOSING_INSET_IN_PIXEL = 1; - private int mOpenAnnounceResId; - private int mCloseAnnounceResId; - - public MoreKeysKeyboardAccessibilityDelegate(final MoreKeysKeyboardView moreKeysKeyboardView, - final KeyDetector keyDetector) { - super(moreKeysKeyboardView, keyDetector); - } - - public void setOpenAnnounce(final int resId) { - mOpenAnnounceResId = resId; - } - - public void setCloseAnnounce(final int resId) { - mCloseAnnounceResId = resId; - } - - public void onShowMoreKeysKeyboard() { - sendWindowStateChanged(mOpenAnnounceResId); - } - - public void onDismissMoreKeysKeyboard() { - sendWindowStateChanged(mCloseAnnounceResId); - } - - @Override - protected void onHoverEnter(final MotionEvent event) { - if (DEBUG_HOVER) { - Log.d(TAG, "onHoverEnter: key=" + getHoverKeyOf(event)); - } - super.onHoverEnter(event); - final int actionIndex = event.getActionIndex(); - final int x = (int)event.getX(actionIndex); - final int y = (int)event.getY(actionIndex); - final int pointerId = event.getPointerId(actionIndex); - final long eventTime = event.getEventTime(); - mKeyboardView.onDownEvent(x, y, pointerId, eventTime); - } - - @Override - protected void onHoverMove(final MotionEvent event) { - super.onHoverMove(event); - final int actionIndex = event.getActionIndex(); - final int x = (int)event.getX(actionIndex); - final int y = (int)event.getY(actionIndex); - final int pointerId = event.getPointerId(actionIndex); - final long eventTime = event.getEventTime(); - mKeyboardView.onMoveEvent(x, y, pointerId, eventTime); - } - - @Override - protected void onHoverExit(final MotionEvent event) { - final Key lastKey = getLastHoverKey(); - if (DEBUG_HOVER) { - Log.d(TAG, "onHoverExit: key=" + getHoverKeyOf(event) + " last=" + lastKey); - } - if (lastKey != null) { - super.onHoverExitFrom(lastKey); - } - setLastHoverKey(null); - final int actionIndex = event.getActionIndex(); - final int x = (int)event.getX(actionIndex); - final int y = (int)event.getY(actionIndex); - final int pointerId = event.getPointerId(actionIndex); - final long eventTime = event.getEventTime(); - // A hover exit event at one pixel width or height area on the edges of more keys keyboard - // are treated as closing. - mMoreKeysKeyboardValidBounds.set(0, 0, mKeyboardView.getWidth(), mKeyboardView.getHeight()); - mMoreKeysKeyboardValidBounds.inset(CLOSING_INSET_IN_PIXEL, CLOSING_INSET_IN_PIXEL); - if (mMoreKeysKeyboardValidBounds.contains(x, y)) { - // Invoke {@link MoreKeysKeyboardView#onUpEvent(int,int,int,long)} as if this hover - // exit event selects a key. - mKeyboardView.onUpEvent(x, y, pointerId, eventTime); - // TODO: Should fix this reference. This is a hack to clear the state of - // {@link PointerTracker}. - PointerTracker.dismissAllMoreKeysPanels(); - return; - } - // Close the more keys keyboard. - // TODO: Should fix this reference. This is a hack to clear the state of - // {@link PointerTracker}. - PointerTracker.dismissAllMoreKeysPanels(); - } -} diff --git a/java/src/com/android/inputmethod/compat/ActivityManagerCompatUtils.java b/java/src/com/android/inputmethod/compat/ActivityManagerCompatUtils.java deleted file mode 100644 index 385e3e025..000000000 --- a/java/src/com/android/inputmethod/compat/ActivityManagerCompatUtils.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.compat; - -import android.app.ActivityManager; -import android.content.Context; - -import java.lang.reflect.Method; - -public class ActivityManagerCompatUtils { - private static final Object LOCK = new Object(); - private static volatile Boolean sBoolean = null; - private static final Method METHOD_isLowRamDevice = CompatUtils.getMethod( - ActivityManager.class, "isLowRamDevice"); - - private ActivityManagerCompatUtils() { - // Do not instantiate this class. - } - - public static boolean isLowRamDevice(Context context) { - if (sBoolean == null) { - synchronized(LOCK) { - if (sBoolean == null) { - final ActivityManager am = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - sBoolean = (Boolean)CompatUtils.invoke(am, false, METHOD_isLowRamDevice); - } - } - } - return sBoolean; - } -} diff --git a/java/src/com/android/inputmethod/compat/AppWorkaroundsHelper.java b/java/src/com/android/inputmethod/compat/AppWorkaroundsHelper.java deleted file mode 100644 index f5e56eb4b..000000000 --- a/java/src/com/android/inputmethod/compat/AppWorkaroundsHelper.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.compat; - -import android.content.pm.PackageInfo; - -@SuppressWarnings("unused") -public class AppWorkaroundsHelper { - private AppWorkaroundsHelper() { - // This helper class is not publicly instantiable. - } - - public static boolean evaluateIsBrokenByRecorrection(final PackageInfo info) { - return false; - } -} diff --git a/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java b/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java deleted file mode 100644 index 6e43cc9a7..000000000 --- a/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.compat; - -import android.content.pm.PackageInfo; -import android.os.Build.VERSION_CODES; - -/** - * A class to encapsulate work-arounds specific to particular apps. - */ -public class AppWorkaroundsUtils { - private final PackageInfo mPackageInfo; // May be null - private final boolean mIsBrokenByRecorrection; - - public AppWorkaroundsUtils(final PackageInfo packageInfo) { - mPackageInfo = packageInfo; - mIsBrokenByRecorrection = AppWorkaroundsHelper.evaluateIsBrokenByRecorrection( - packageInfo); - } - - public boolean isBrokenByRecorrection() { - return mIsBrokenByRecorrection; - } - - public boolean isBeforeJellyBean() { - if (null == mPackageInfo || null == mPackageInfo.applicationInfo) { - return false; - } - return mPackageInfo.applicationInfo.targetSdkVersion < VERSION_CODES.JELLY_BEAN; - } - - @Override - public String toString() { - if (null == mPackageInfo || null == mPackageInfo.applicationInfo) { - return ""; - } - final StringBuilder s = new StringBuilder(); - s.append("Target application : ") - .append(mPackageInfo.applicationInfo.name) - .append("\nPackage : ") - .append(mPackageInfo.applicationInfo.packageName) - .append("\nTarget app sdk version : ") - .append(mPackageInfo.applicationInfo.targetSdkVersion); - return s.toString(); - } -} diff --git a/java/src/com/android/inputmethod/compat/BuildCompatUtils.java b/java/src/com/android/inputmethod/compat/BuildCompatUtils.java deleted file mode 100644 index 5d56f12ae..000000000 --- a/java/src/com/android/inputmethod/compat/BuildCompatUtils.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.compat; - -import android.os.Build; - -public final class BuildCompatUtils { - private BuildCompatUtils() { - // This utility class is not publicly instantiable. - } - - private static final boolean IS_RELEASE_BUILD = Build.VERSION.CODENAME.equals("REL"); - - /** - * The "effective" API version. - * {@link android.os.Build.VERSION#SDK_INT} if the platform is a release build. - * {@link android.os.Build.VERSION#SDK_INT} plus 1 if the platform is a development build. - */ - public static final int EFFECTIVE_SDK_INT = IS_RELEASE_BUILD - ? Build.VERSION.SDK_INT - : Build.VERSION.SDK_INT + 1; -} diff --git a/java/src/com/android/inputmethod/compat/CharacterCompat.java b/java/src/com/android/inputmethod/compat/CharacterCompat.java deleted file mode 100644 index 609fe1638..000000000 --- a/java/src/com/android/inputmethod/compat/CharacterCompat.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.compat; - -import java.lang.reflect.Method; - -public final class CharacterCompat { - // Note that Character.isAlphabetic(int), has been introduced in API level 19 - // (Build.VERSION_CODE.KITKAT). - private static final Method METHOD_isAlphabetic = CompatUtils.getMethod( - Character.class, "isAlphabetic", int.class); - - private CharacterCompat() { - // This utility class is not publicly instantiable. - } - - public static boolean isAlphabetic(final int code) { - if (METHOD_isAlphabetic != null) { - return (Boolean)CompatUtils.invoke(null, false, METHOD_isAlphabetic, code); - } - switch (Character.getType(code)) { - case Character.UPPERCASE_LETTER: - case Character.LOWERCASE_LETTER: - case Character.TITLECASE_LETTER: - case Character.MODIFIER_LETTER: - case Character.OTHER_LETTER: - case Character.LETTER_NUMBER: - return true; - default: - return false; - } - } -} diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java deleted file mode 100644 index 5db80190c..000000000 --- a/java/src/com/android/inputmethod/compat/CompatUtils.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.compat; - -import android.text.TextUtils; -import android.util.Log; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public final class CompatUtils { - private static final String TAG = CompatUtils.class.getSimpleName(); - - private CompatUtils() { - // This utility class is not publicly instantiable. - } - - public static Class<?> getClass(final String className) { - try { - return Class.forName(className); - } catch (final ClassNotFoundException e) { - return null; - } - } - - public static Method getMethod(final Class<?> targetClass, final String name, - final Class<?>... parameterTypes) { - if (targetClass == null || TextUtils.isEmpty(name)) { - return null; - } - try { - return targetClass.getMethod(name, parameterTypes); - } catch (final SecurityException | NoSuchMethodException e) { - // ignore - } - return null; - } - - public static Field getField(final Class<?> targetClass, final String name) { - if (targetClass == null || TextUtils.isEmpty(name)) { - return null; - } - try { - return targetClass.getField(name); - } catch (final SecurityException | NoSuchFieldException e) { - // ignore - } - return null; - } - - public static Constructor<?> getConstructor(final Class<?> targetClass, - final Class<?> ... types) { - if (targetClass == null || types == null) { - return null; - } - try { - return targetClass.getConstructor(types); - } catch (final SecurityException | NoSuchMethodException e) { - // ignore - } - return null; - } - - public static Object newInstance(final Constructor<?> constructor, final Object ... args) { - if (constructor == null) { - return null; - } - try { - return constructor.newInstance(args); - } catch (final InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { - Log.e(TAG, "Exception in newInstance", e); - } - return null; - } - - public static Object invoke(final Object receiver, final Object defaultValue, - final Method method, final Object... args) { - if (method == null) { - return defaultValue; - } - try { - return method.invoke(receiver, args); - } catch (final IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { - Log.e(TAG, "Exception in invoke", e); - } - return defaultValue; - } - - public static Object getFieldValue(final Object receiver, final Object defaultValue, - final Field field) { - if (field == null) { - return defaultValue; - } - try { - return field.get(receiver); - } catch (final IllegalAccessException | IllegalArgumentException e) { - Log.e(TAG, "Exception in getFieldValue", e); - } - return defaultValue; - } - - public static void setFieldValue(final Object receiver, final Field field, final Object value) { - if (field == null) { - return; - } - try { - field.set(receiver, value); - } catch (final IllegalAccessException | IllegalArgumentException e) { - Log.e(TAG, "Exception in setFieldValue", e); - } - } - - public static ClassWrapper getClassWrapper(final String className) { - return new ClassWrapper(getClass(className)); - } - - public static final class ClassWrapper { - private final Class<?> mClass; - public ClassWrapper(final Class<?> targetClass) { - mClass = targetClass; - } - - public boolean exists() { - return mClass != null; - } - - public <T> ToObjectMethodWrapper<T> getMethod(final String name, - final T defaultValue, final Class<?>... parameterTypes) { - return new ToObjectMethodWrapper<>(CompatUtils.getMethod(mClass, name, parameterTypes), - defaultValue); - } - - public ToIntMethodWrapper getPrimitiveMethod(final String name, final int defaultValue, - final Class<?>... parameterTypes) { - return new ToIntMethodWrapper(CompatUtils.getMethod(mClass, name, parameterTypes), - defaultValue); - } - - public ToFloatMethodWrapper getPrimitiveMethod(final String name, final float defaultValue, - final Class<?>... parameterTypes) { - return new ToFloatMethodWrapper(CompatUtils.getMethod(mClass, name, parameterTypes), - defaultValue); - } - - public ToBooleanMethodWrapper getPrimitiveMethod(final String name, - final boolean defaultValue, final Class<?>... parameterTypes) { - return new ToBooleanMethodWrapper(CompatUtils.getMethod(mClass, name, parameterTypes), - defaultValue); - } - } - - public static final class ToObjectMethodWrapper<T> { - private final Method mMethod; - private final T mDefaultValue; - public ToObjectMethodWrapper(final Method method, final T defaultValue) { - mMethod = method; - mDefaultValue = defaultValue; - } - @SuppressWarnings("unchecked") - public T invoke(final Object receiver, final Object... args) { - return (T) CompatUtils.invoke(receiver, mDefaultValue, mMethod, args); - } - } - - public static final class ToIntMethodWrapper { - private final Method mMethod; - private final int mDefaultValue; - public ToIntMethodWrapper(final Method method, final int defaultValue) { - mMethod = method; - mDefaultValue = defaultValue; - } - public int invoke(final Object receiver, final Object... args) { - return (int) CompatUtils.invoke(receiver, mDefaultValue, mMethod, args); - } - } - - public static final class ToFloatMethodWrapper { - private final Method mMethod; - private final float mDefaultValue; - public ToFloatMethodWrapper(final Method method, final float defaultValue) { - mMethod = method; - mDefaultValue = defaultValue; - } - public float invoke(final Object receiver, final Object... args) { - return (float) CompatUtils.invoke(receiver, mDefaultValue, mMethod, args); - } - } - - public static final class ToBooleanMethodWrapper { - private final Method mMethod; - private final boolean mDefaultValue; - public ToBooleanMethodWrapper(final Method method, final boolean defaultValue) { - mMethod = method; - mDefaultValue = defaultValue; - } - public boolean invoke(final Object receiver, final Object... args) { - return (boolean) CompatUtils.invoke(receiver, mDefaultValue, mMethod, args); - } - } -} diff --git a/java/src/com/android/inputmethod/compat/ConnectivityManagerCompatUtils.java b/java/src/com/android/inputmethod/compat/ConnectivityManagerCompatUtils.java deleted file mode 100644 index b561f7a14..000000000 --- a/java/src/com/android/inputmethod/compat/ConnectivityManagerCompatUtils.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.compat; - -import android.net.ConnectivityManager; - -import java.lang.reflect.Method; - -public final class ConnectivityManagerCompatUtils { - // ConnectivityManager#isActiveNetworkMetered() has been introduced - // in API level 16 (Build.VERSION_CODES.JELLY_BEAN). - private static final Method METHOD_isActiveNetworkMetered = CompatUtils.getMethod( - ConnectivityManager.class, "isActiveNetworkMetered"); - - public static boolean isActiveNetworkMetered(final ConnectivityManager manager) { - return (Boolean)CompatUtils.invoke(manager, - // If the API telling whether the network is metered or not is not available, - // then the closest thing is "if it's a mobile connection". - manager.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_MOBILE, - METHOD_isActiveNetworkMetered); - } -} diff --git a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java deleted file mode 100644 index 01a9e6712..000000000 --- a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.compat; - -import android.annotation.TargetApi; -import android.graphics.Matrix; -import android.graphics.RectF; -import android.os.Build; -import android.view.inputmethod.CursorAnchorInfo; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * A wrapper for {@link CursorAnchorInfo}, which has been introduced in API Level 21. You can use - * this wrapper to avoid direct dependency on newly introduced types. - */ -public class CursorAnchorInfoCompatWrapper { - - /** - * The insertion marker or character bounds have at least one visible region. - */ - public static final int FLAG_HAS_VISIBLE_REGION = 0x01; - - /** - * The insertion marker or character bounds have at least one invisible (clipped) region. - */ - public static final int FLAG_HAS_INVISIBLE_REGION = 0x02; - - /** - * The insertion marker or character bounds is placed at right-to-left (RTL) character. - */ - public static final int FLAG_IS_RTL = 0x04; - - CursorAnchorInfoCompatWrapper() { - // This class is not publicly instantiable. - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Nullable - public static CursorAnchorInfoCompatWrapper wrap(@Nullable final CursorAnchorInfo instance) { - if (BuildCompatUtils.EFFECTIVE_SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return null; - } - if (instance == null) { - return null; - } - return new RealWrapper(instance); - } - - public int getSelectionStart() { - throw new UnsupportedOperationException("not supported."); - } - - public int getSelectionEnd() { - throw new UnsupportedOperationException("not supported."); - } - - public CharSequence getComposingText() { - throw new UnsupportedOperationException("not supported."); - } - - public int getComposingTextStart() { - throw new UnsupportedOperationException("not supported."); - } - - public Matrix getMatrix() { - throw new UnsupportedOperationException("not supported."); - } - - @SuppressWarnings("unused") - public RectF getCharacterBounds(final int index) { - throw new UnsupportedOperationException("not supported."); - } - - @SuppressWarnings("unused") - public int getCharacterBoundsFlags(final int index) { - throw new UnsupportedOperationException("not supported."); - } - - public float getInsertionMarkerBaseline() { - throw new UnsupportedOperationException("not supported."); - } - - public float getInsertionMarkerBottom() { - throw new UnsupportedOperationException("not supported."); - } - - public float getInsertionMarkerHorizontal() { - throw new UnsupportedOperationException("not supported."); - } - - public float getInsertionMarkerTop() { - throw new UnsupportedOperationException("not supported."); - } - - public int getInsertionMarkerFlags() { - throw new UnsupportedOperationException("not supported."); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static final class RealWrapper extends CursorAnchorInfoCompatWrapper { - - @Nonnull - private final CursorAnchorInfo mInstance; - - public RealWrapper(@Nonnull final CursorAnchorInfo info) { - mInstance = info; - } - - @Override - public int getSelectionStart() { - return mInstance.getSelectionStart(); - } - - @Override - public int getSelectionEnd() { - return mInstance.getSelectionEnd(); - } - - @Override - public CharSequence getComposingText() { - return mInstance.getComposingText(); - } - - @Override - public int getComposingTextStart() { - return mInstance.getComposingTextStart(); - } - - @Override - public Matrix getMatrix() { - return mInstance.getMatrix(); - } - - @Override - public RectF getCharacterBounds(final int index) { - return mInstance.getCharacterBounds(index); - } - - @Override - public int getCharacterBoundsFlags(final int index) { - return mInstance.getCharacterBoundsFlags(index); - } - - @Override - public float getInsertionMarkerBaseline() { - return mInstance.getInsertionMarkerBaseline(); - } - - @Override - public float getInsertionMarkerBottom() { - return mInstance.getInsertionMarkerBottom(); - } - - @Override - public float getInsertionMarkerHorizontal() { - return mInstance.getInsertionMarkerHorizontal(); - } - - @Override - public float getInsertionMarkerTop() { - return mInstance.getInsertionMarkerTop(); - } - - @Override - public int getInsertionMarkerFlags() { - return mInstance.getInsertionMarkerFlags(); - } - } -} diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java deleted file mode 100644 index 56ce8f5e6..000000000 --- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.compat; - -import android.view.inputmethod.EditorInfo; - -import java.lang.reflect.Field; -import java.util.Locale; - -public final class EditorInfoCompatUtils { - // Note that EditorInfo.IME_FLAG_FORCE_ASCII has been introduced - // in API level 16 (Build.VERSION_CODES.JELLY_BEAN). - private static final Field FIELD_IME_FLAG_FORCE_ASCII = CompatUtils.getField( - EditorInfo.class, "IME_FLAG_FORCE_ASCII"); - private static final Integer OBJ_IME_FLAG_FORCE_ASCII = (Integer) CompatUtils.getFieldValue( - null /* receiver */, null /* defaultValue */, FIELD_IME_FLAG_FORCE_ASCII); - private static final Field FIELD_HINT_LOCALES = CompatUtils.getField( - EditorInfo.class, "hintLocales"); - - private EditorInfoCompatUtils() { - // This utility class is not publicly instantiable. - } - - public static boolean hasFlagForceAscii(final int imeOptions) { - if (OBJ_IME_FLAG_FORCE_ASCII == null) return false; - return (imeOptions & OBJ_IME_FLAG_FORCE_ASCII) != 0; - } - - public static String imeActionName(final int imeOptions) { - final int actionId = imeOptions & EditorInfo.IME_MASK_ACTION; - switch (actionId) { - case EditorInfo.IME_ACTION_UNSPECIFIED: - return "actionUnspecified"; - case EditorInfo.IME_ACTION_NONE: - return "actionNone"; - case EditorInfo.IME_ACTION_GO: - return "actionGo"; - case EditorInfo.IME_ACTION_SEARCH: - return "actionSearch"; - case EditorInfo.IME_ACTION_SEND: - return "actionSend"; - case EditorInfo.IME_ACTION_NEXT: - return "actionNext"; - case EditorInfo.IME_ACTION_DONE: - return "actionDone"; - case EditorInfo.IME_ACTION_PREVIOUS: - return "actionPrevious"; - default: - return "actionUnknown(" + actionId + ")"; - } - } - - public static String imeOptionsName(final int imeOptions) { - final String action = imeActionName(imeOptions); - final StringBuilder flags = new StringBuilder(); - if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { - flags.append("flagNoEnterAction|"); - } - if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { - flags.append("flagNavigateNext|"); - } - if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0) { - flags.append("flagNavigatePrevious|"); - } - if (hasFlagForceAscii(imeOptions)) { - flags.append("flagForceAscii|"); - } - return (action != null) ? flags + action : flags.toString(); - } - - public static Locale getPrimaryHintLocale(final EditorInfo editorInfo) { - if (editorInfo == null) { - return null; - } - final Object localeList = CompatUtils.getFieldValue(editorInfo, null, FIELD_HINT_LOCALES); - if (localeList == null) { - return null; - } - if (LocaleListCompatUtils.isEmpty(localeList)) { - return null; - } - return LocaleListCompatUtils.get(localeList, 0); - } -} diff --git a/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java b/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java deleted file mode 100644 index a5c71b22f..000000000 --- a/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.compat; - -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; - -public final class InputConnectionCompatUtils { - private static final CompatUtils.ClassWrapper sInputConnectionType; - private static final CompatUtils.ToBooleanMethodWrapper sRequestCursorUpdatesMethod; - static { - sInputConnectionType = new CompatUtils.ClassWrapper(InputConnection.class); - sRequestCursorUpdatesMethod = sInputConnectionType.getPrimitiveMethod( - "requestCursorUpdates", false, int.class); - } - - public static boolean isRequestCursorUpdatesAvailable() { - return sRequestCursorUpdatesMethod != null; - } - - /** - * Local copies of some constants in InputConnection until the SDK becomes publicly available. - */ - private static int CURSOR_UPDATE_IMMEDIATE = 1 << 0; - private static int CURSOR_UPDATE_MONITOR = 1 << 1; - - private static boolean requestCursorUpdatesImpl(final InputConnection inputConnection, - final int cursorUpdateMode) { - if (!isRequestCursorUpdatesAvailable()) { - return false; - } - return sRequestCursorUpdatesMethod.invoke(inputConnection, cursorUpdateMode); - } - - /** - * Requests the editor to call back {@link InputMethodManager#updateCursorAnchorInfo}. - * @param inputConnection the input connection to which the request is to be sent. - * @param enableMonitor {@code true} to request the editor to call back the method whenever the - * cursor/anchor position is changed. - * @param requestImmediateCallback {@code true} to request the editor to call back the method - * as soon as possible to notify the current cursor/anchor position to the input method. - * @return {@code false} if the request is not handled. Otherwise returns {@code true}. - */ - public static boolean requestCursorUpdates(final InputConnection inputConnection, - final boolean enableMonitor, final boolean requestImmediateCallback) { - final int cursorUpdateMode = (enableMonitor ? CURSOR_UPDATE_MONITOR : 0) - | (requestImmediateCallback ? CURSOR_UPDATE_IMMEDIATE : 0); - return requestCursorUpdatesImpl(inputConnection, cursorUpdateMode); - } -} diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java deleted file mode 100644 index aa20c0336..000000000 --- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.compat; - -import android.content.Context; -import android.os.IBinder; -import android.view.inputmethod.InputMethodManager; - -import java.lang.reflect.Method; - -public final class InputMethodManagerCompatWrapper { - // Note that InputMethodManager.switchToNextInputMethod() has been introduced - // in API level 16 (Build.VERSION_CODES.JELLY_BEAN). - private static final Method METHOD_switchToNextInputMethod = CompatUtils.getMethod( - InputMethodManager.class, "switchToNextInputMethod", IBinder.class, boolean.class); - - // Note that InputMethodManager.shouldOfferSwitchingToNextInputMethod() has been introduced - // in API level 19 (Build.VERSION_CODES.KITKAT). - private static final Method METHOD_shouldOfferSwitchingToNextInputMethod = - CompatUtils.getMethod(InputMethodManager.class, - "shouldOfferSwitchingToNextInputMethod", IBinder.class); - - public final InputMethodManager mImm; - - public InputMethodManagerCompatWrapper(final Context context) { - mImm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE); - } - - public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) { - return (Boolean)CompatUtils.invoke(mImm, false /* defaultValue */, - METHOD_switchToNextInputMethod, token, onlyCurrentIme); - } - - public boolean shouldOfferSwitchingToNextInputMethod(final IBinder token) { - return (Boolean)CompatUtils.invoke(mImm, false /* defaultValue */, - METHOD_shouldOfferSwitchingToNextInputMethod, token); - } -} diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java deleted file mode 100644 index 48047ddbf..000000000 --- a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.compat; - -import android.inputmethodservice.InputMethodService; - -import java.lang.reflect.Method; - -public final class InputMethodServiceCompatUtils { - // Note that {@link InputMethodService#enableHardwareAcceleration} has been introduced - // in API level 17 (Build.VERSION_CODES.JELLY_BEAN_MR1). - private static final Method METHOD_enableHardwareAcceleration = - CompatUtils.getMethod(InputMethodService.class, "enableHardwareAcceleration"); - - private InputMethodServiceCompatUtils() { - // This utility class is not publicly instantiable. - } - - public static boolean enableHardwareAcceleration(final InputMethodService ims) { - return (Boolean)CompatUtils.invoke(ims, false /* defaultValue */, - METHOD_enableHardwareAcceleration); - } -} diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java deleted file mode 100644 index d123a1799..000000000 --- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.compat; - -import android.os.Build; -import android.text.TextUtils; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.RichInputMethodSubtype; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.LocaleUtils; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.Locale; - -import javax.annotation.Nonnull; - -public final class InputMethodSubtypeCompatUtils { - private static final String TAG = InputMethodSubtypeCompatUtils.class.getSimpleName(); - // Note that InputMethodSubtype(int nameId, int iconId, String locale, String mode, - // String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) - // has been introduced in API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1). - private static final Constructor<?> CONSTRUCTOR_INPUT_METHOD_SUBTYPE = - CompatUtils.getConstructor(InputMethodSubtype.class, - int.class, int.class, String.class, String.class, String.class, boolean.class, - boolean.class, int.class); - static { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - if (CONSTRUCTOR_INPUT_METHOD_SUBTYPE == null) { - android.util.Log.w(TAG, "Warning!!! Constructor is not defined."); - } - } - } - - // Note that {@link InputMethodSubtype#isAsciiCapable()} has been introduced in API level 19 - // (Build.VERSION_CODE.KITKAT). - private static final Method METHOD_isAsciiCapable = CompatUtils.getMethod( - InputMethodSubtype.class, "isAsciiCapable"); - - private InputMethodSubtypeCompatUtils() { - // This utility class is not publicly instantiable. - } - - @SuppressWarnings("deprecation") - @Nonnull - public static InputMethodSubtype newInputMethodSubtype(int nameId, int iconId, String locale, - String mode, String extraValue, boolean isAuxiliary, - boolean overridesImplicitlyEnabledSubtype, int id) { - if (CONSTRUCTOR_INPUT_METHOD_SUBTYPE == null - || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { - return new InputMethodSubtype(nameId, iconId, locale, mode, extraValue, isAuxiliary, - overridesImplicitlyEnabledSubtype); - } - return (InputMethodSubtype) CompatUtils.newInstance(CONSTRUCTOR_INPUT_METHOD_SUBTYPE, - nameId, iconId, locale, mode, extraValue, isAuxiliary, - overridesImplicitlyEnabledSubtype, id); - } - - public static boolean isAsciiCapable(final RichInputMethodSubtype subtype) { - return isAsciiCapable(subtype.getRawSubtype()); - } - - public static boolean isAsciiCapable(final InputMethodSubtype subtype) { - return isAsciiCapableWithAPI(subtype) - || subtype.containsExtraValueKey(Constants.Subtype.ExtraValue.ASCII_CAPABLE); - } - - // Note that InputMethodSubtype.getLanguageTag() is expected to be available in Android N+. - private static final Method GET_LANGUAGE_TAG = - CompatUtils.getMethod(InputMethodSubtype.class, "getLanguageTag"); - - public static Locale getLocaleObject(final InputMethodSubtype subtype) { - // Locale.forLanguageTag() is available only in Android L and later. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - final String languageTag = (String) CompatUtils.invoke(subtype, null, GET_LANGUAGE_TAG); - if (!TextUtils.isEmpty(languageTag)) { - return Locale.forLanguageTag(languageTag); - } - } - return LocaleUtils.constructLocaleFromString(subtype.getLocale()); - } - - @UsedForTesting - public static boolean isAsciiCapableWithAPI(final InputMethodSubtype subtype) { - return (Boolean)CompatUtils.invoke(subtype, false, METHOD_isAsciiCapable); - } -} diff --git a/java/src/com/android/inputmethod/compat/IntentCompatUtils.java b/java/src/com/android/inputmethod/compat/IntentCompatUtils.java deleted file mode 100644 index 965a2a891..000000000 --- a/java/src/com/android/inputmethod/compat/IntentCompatUtils.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.compat; - -import android.content.Intent; - -public final class IntentCompatUtils { - // Note that Intent.ACTION_USER_INITIALIZE have been introduced in API level 17 - // (Build.VERSION_CODE.JELLY_BEAN_MR1). - private static final String ACTION_USER_INITIALIZE = - (String)CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */, - CompatUtils.getField(Intent.class, "ACTION_USER_INITIALIZE")); - - private IntentCompatUtils() { - // This utility class is not publicly instantiable. - } - - public static boolean is_ACTION_USER_INITIALIZE(final String action) { - return ACTION_USER_INITIALIZE != null && ACTION_USER_INITIALIZE.equals(action); - } -} diff --git a/java/src/com/android/inputmethod/compat/LocaleListCompatUtils.java b/java/src/com/android/inputmethod/compat/LocaleListCompatUtils.java deleted file mode 100644 index 81ff0f994..000000000 --- a/java/src/com/android/inputmethod/compat/LocaleListCompatUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.inputmethod.compat; - -import java.lang.reflect.Method; -import java.util.Locale; - -public final class LocaleListCompatUtils { - private static final Class CLASS_LocaleList = CompatUtils.getClass("android.os.LocaleList"); - private static final Method METHOD_get = - CompatUtils.getMethod(CLASS_LocaleList, "get", int.class); - private static final Method METHOD_isEmpty = - CompatUtils.getMethod(CLASS_LocaleList, "isEmpty"); - - private LocaleListCompatUtils() { - // This utility class is not publicly instantiable. - } - - public static boolean isEmpty(final Object localeList) { - return (Boolean) CompatUtils.invoke(localeList, Boolean.FALSE, METHOD_isEmpty); - } - - public static Locale get(final Object localeList, final int index) { - return (Locale) CompatUtils.invoke(localeList, null, METHOD_get, index); - } -} diff --git a/java/src/com/android/inputmethod/compat/LocaleSpanCompatUtils.java b/java/src/com/android/inputmethod/compat/LocaleSpanCompatUtils.java deleted file mode 100644 index 58e5a36b6..000000000 --- a/java/src/com/android/inputmethod/compat/LocaleSpanCompatUtils.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.compat; - -import android.text.Spannable; -import android.text.Spanned; -import android.text.style.LocaleSpan; -import android.util.Log; - -import com.android.inputmethod.annotations.UsedForTesting; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Locale; - -@UsedForTesting -public final class LocaleSpanCompatUtils { - private static final String TAG = LocaleSpanCompatUtils.class.getSimpleName(); - - // Note that LocaleSpan(Locale locale) has been introduced in API level 17 - // (Build.VERSION_CODE.JELLY_BEAN_MR1). - private static Class<?> getLocaleSpanClass() { - try { - return Class.forName("android.text.style.LocaleSpan"); - } catch (ClassNotFoundException e) { - return null; - } - } - private static final Class<?> LOCALE_SPAN_TYPE; - private static final Constructor<?> LOCALE_SPAN_CONSTRUCTOR; - private static final Method LOCALE_SPAN_GET_LOCALE; - static { - LOCALE_SPAN_TYPE = getLocaleSpanClass(); - LOCALE_SPAN_CONSTRUCTOR = CompatUtils.getConstructor(LOCALE_SPAN_TYPE, Locale.class); - LOCALE_SPAN_GET_LOCALE = CompatUtils.getMethod(LOCALE_SPAN_TYPE, "getLocale"); - } - - @UsedForTesting - public static boolean isLocaleSpanAvailable() { - return (LOCALE_SPAN_CONSTRUCTOR != null && LOCALE_SPAN_GET_LOCALE != null); - } - - @UsedForTesting - public static Object newLocaleSpan(final Locale locale) { - return CompatUtils.newInstance(LOCALE_SPAN_CONSTRUCTOR, locale); - } - - @UsedForTesting - public static Locale getLocaleFromLocaleSpan(final Object localeSpan) { - return (Locale) CompatUtils.invoke(localeSpan, null, LOCALE_SPAN_GET_LOCALE); - } - - /** - * Ensures that the specified range is covered with only one {@link LocaleSpan} with the given - * locale. If the region is already covered by one or more {@link LocaleSpan}, their ranges are - * updated so that each character has only one locale. - * @param spannable the spannable object to be updated. - * @param start the start index from which {@link LocaleSpan} is attached (inclusive). - * @param end the end index to which {@link LocaleSpan} is attached (exclusive). - * @param locale the locale to be attached to the specified range. - */ - @UsedForTesting - public static void updateLocaleSpan(final Spannable spannable, final int start, - final int end, final Locale locale) { - if (end < start) { - Log.e(TAG, "Invalid range: start=" + start + " end=" + end); - return; - } - if (!isLocaleSpanAvailable()) { - return; - } - // A brief summary of our strategy; - // 1. Enumerate all LocaleSpans between [start - 1, end + 1]. - // 2. For each LocaleSpan S: - // - Update the range of S so as not to cover [start, end] if S doesn't have the - // expected locale. - // - Mark S as "to be merged" if S has the expected locale. - // 3. Merge all the LocaleSpans that are marked as "to be merged" into one LocaleSpan. - // If no appropriate span is found, create a new one with newLocaleSpan method. - final int searchStart = Math.max(start - 1, 0); - final int searchEnd = Math.min(end + 1, spannable.length()); - // LocaleSpans found in the target range. See the step 1 in the above comment. - final Object[] existingLocaleSpans = spannable.getSpans(searchStart, searchEnd, - LOCALE_SPAN_TYPE); - // LocaleSpans that are marked as "to be merged". See the step 2 in the above comment. - final ArrayList<Object> existingLocaleSpansToBeMerged = new ArrayList<>(); - boolean isStartExclusive = true; - boolean isEndExclusive = true; - int newStart = start; - int newEnd = end; - for (final Object existingLocaleSpan : existingLocaleSpans) { - final Locale attachedLocale = getLocaleFromLocaleSpan(existingLocaleSpan); - if (!locale.equals(attachedLocale)) { - // This LocaleSpan does not have the expected locale. Update its range if it has - // an intersection with the range [start, end] (the first case of the step 2 in the - // above comment). - removeLocaleSpanFromRange(existingLocaleSpan, spannable, start, end); - continue; - } - final int spanStart = spannable.getSpanStart(existingLocaleSpan); - final int spanEnd = spannable.getSpanEnd(existingLocaleSpan); - if (spanEnd < spanStart) { - Log.e(TAG, "Invalid span: spanStart=" + spanStart + " spanEnd=" + spanEnd); - continue; - } - if (spanEnd < start || end < spanStart) { - // No intersection found. - continue; - } - - // Here existingLocaleSpan has the expected locale and an intersection with the - // range [start, end] (the second case of the the step 2 in the above comment). - final int spanFlag = spannable.getSpanFlags(existingLocaleSpan); - if (spanStart < newStart) { - newStart = spanStart; - isStartExclusive = ((spanFlag & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (newEnd < spanEnd) { - newEnd = spanEnd; - isEndExclusive = ((spanFlag & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - existingLocaleSpansToBeMerged.add(existingLocaleSpan); - } - - int originalLocaleSpanFlag = 0; - Object localeSpan = null; - if (existingLocaleSpansToBeMerged.isEmpty()) { - // If there is no LocaleSpan that is marked as to be merged, create a new one. - localeSpan = newLocaleSpan(locale); - } else { - // Reuse the first LocaleSpan to avoid unnecessary object instantiation. - localeSpan = existingLocaleSpansToBeMerged.get(0); - originalLocaleSpanFlag = spannable.getSpanFlags(localeSpan); - // No need to keep other instances. - for (int i = 1; i < existingLocaleSpansToBeMerged.size(); ++i) { - spannable.removeSpan(existingLocaleSpansToBeMerged.get(i)); - } - } - final int localeSpanFlag = getSpanFlag(originalLocaleSpanFlag, isStartExclusive, - isEndExclusive); - spannable.setSpan(localeSpan, newStart, newEnd, localeSpanFlag); - } - - private static void removeLocaleSpanFromRange(final Object localeSpan, - final Spannable spannable, final int removeStart, final int removeEnd) { - if (!isLocaleSpanAvailable()) { - return; - } - final int spanStart = spannable.getSpanStart(localeSpan); - final int spanEnd = spannable.getSpanEnd(localeSpan); - if (spanStart > spanEnd) { - Log.e(TAG, "Invalid span: spanStart=" + spanStart + " spanEnd=" + spanEnd); - return; - } - if (spanEnd < removeStart) { - // spanStart < spanEnd < removeStart < removeEnd - return; - } - if (removeEnd < spanStart) { - // spanStart < removeEnd < spanStart < spanEnd - return; - } - final int spanFlags = spannable.getSpanFlags(localeSpan); - if (spanStart < removeStart) { - if (removeEnd < spanEnd) { - // spanStart < removeStart < removeEnd < spanEnd - final Locale locale = getLocaleFromLocaleSpan(localeSpan); - spannable.setSpan(localeSpan, spanStart, removeStart, spanFlags); - final Object attionalLocaleSpan = newLocaleSpan(locale); - spannable.setSpan(attionalLocaleSpan, removeEnd, spanEnd, spanFlags); - return; - } - // spanStart < removeStart < spanEnd <= removeEnd - spannable.setSpan(localeSpan, spanStart, removeStart, spanFlags); - return; - } - if (removeEnd < spanEnd) { - // removeStart <= spanStart < removeEnd < spanEnd - spannable.setSpan(localeSpan, removeEnd, spanEnd, spanFlags); - return; - } - // removeStart <= spanStart < spanEnd < removeEnd - spannable.removeSpan(localeSpan); - } - - private static int getSpanFlag(final int originalFlag, - final boolean isStartExclusive, final boolean isEndExclusive) { - return (originalFlag & ~Spanned.SPAN_POINT_MARK_MASK) | - getSpanPointMarkFlag(isStartExclusive, isEndExclusive); - } - - private static int getSpanPointMarkFlag(final boolean isStartExclusive, - final boolean isEndExclusive) { - if (isStartExclusive) { - return isEndExclusive ? Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - : Spanned.SPAN_EXCLUSIVE_INCLUSIVE; - } - return isEndExclusive ? Spanned.SPAN_INCLUSIVE_EXCLUSIVE - : Spanned.SPAN_INCLUSIVE_INCLUSIVE; - } -} diff --git a/java/src/com/android/inputmethod/compat/LooperCompatUtils.java b/java/src/com/android/inputmethod/compat/LooperCompatUtils.java deleted file mode 100644 index d647dbbd3..000000000 --- a/java/src/com/android/inputmethod/compat/LooperCompatUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.compat; - -import android.os.Looper; - -import java.lang.reflect.Method; - -/** - * Helper to call Looper#quitSafely, which was introduced in API - * level 18 (Build.VERSION_CODES.JELLY_BEAN_MR2). - * - * In unit tests, we create lots of instances of LatinIME, which means we need to clean up - * some Loopers lest we leak file descriptors. In normal use on a device though, this is never - * necessary (although it does not hurt). - */ -public final class LooperCompatUtils { - private static final Method METHOD_quitSafely = CompatUtils.getMethod( - Looper.class, "quitSafely"); - - public static void quitSafely(final Looper looper) { - if (null != METHOD_quitSafely) { - CompatUtils.invoke(looper, null /* default return value */, METHOD_quitSafely); - } else { - looper.quit(); - } - } -} diff --git a/java/src/com/android/inputmethod/compat/NotificationCompatUtils.java b/java/src/com/android/inputmethod/compat/NotificationCompatUtils.java deleted file mode 100644 index 70ab972c5..000000000 --- a/java/src/com/android/inputmethod/compat/NotificationCompatUtils.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.compat; - -import android.app.Notification; -import android.os.Build; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -public class NotificationCompatUtils { - // Note that TextInfo.getCharSequence() is supposed to be available in API level 21 and later. - private static final Method METHOD_setColor = - CompatUtils.getMethod(Notification.Builder.class, "setColor", int.class); - private static final Method METHOD_setVisibility = - CompatUtils.getMethod(Notification.Builder.class, "setVisibility", int.class); - private static final Method METHOD_setCategory = - CompatUtils.getMethod(Notification.Builder.class, "setCategory", String.class); - private static final Method METHOD_setPriority = - CompatUtils.getMethod(Notification.Builder.class, "setPriority", int.class); - private static final Method METHOD_build = - CompatUtils.getMethod(Notification.Builder.class, "build"); - private static final Field FIELD_VISIBILITY_SECRET = - CompatUtils.getField(Notification.class, "VISIBILITY_SECRET"); - private static final int VISIBILITY_SECRET = null == FIELD_VISIBILITY_SECRET ? 0 - : (Integer) CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */, - FIELD_VISIBILITY_SECRET); - private static final Field FIELD_CATEGORY_RECOMMENDATION = - CompatUtils.getField(Notification.class, "CATEGORY_RECOMMENDATION"); - private static final String CATEGORY_RECOMMENDATION = null == FIELD_CATEGORY_RECOMMENDATION ? "" - : (String) CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */, - FIELD_CATEGORY_RECOMMENDATION); - private static final Field FIELD_PRIORITY_LOW = - CompatUtils.getField(Notification.class, "PRIORITY_LOW"); - private static final int PRIORITY_LOW = null == FIELD_PRIORITY_LOW ? 0 - : (Integer) CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */, - FIELD_PRIORITY_LOW); - - private NotificationCompatUtils() { - // This class is non-instantiable. - } - - // Sets the accent color - public static void setColor(final Notification.Builder builder, final int color) { - CompatUtils.invoke(builder, null, METHOD_setColor, color); - } - - public static void setVisibilityToSecret(final Notification.Builder builder) { - CompatUtils.invoke(builder, null, METHOD_setVisibility, VISIBILITY_SECRET); - } - - public static void setCategoryToRecommendation(final Notification.Builder builder) { - CompatUtils.invoke(builder, null, METHOD_setCategory, CATEGORY_RECOMMENDATION); - } - - public static void setPriorityToLow(final Notification.Builder builder) { - CompatUtils.invoke(builder, null, METHOD_setPriority, PRIORITY_LOW); - } - - @SuppressWarnings("deprecation") - public static Notification build(final Notification.Builder builder) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - // #build was added in API level 16, JELLY_BEAN - return (Notification) CompatUtils.invoke(builder, null, METHOD_build); - } - // #getNotification was deprecated in API level 16, JELLY_BEAN - return builder.getNotification(); - } -} diff --git a/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java b/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java deleted file mode 100644 index cfaf7ec77..000000000 --- a/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.compat; - -import java.lang.reflect.Field; - -public final class SettingsSecureCompatUtils { - // Note that Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD has been introduced - // in API level 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1). - private static final Field FIELD_ACCESSIBILITY_SPEAK_PASSWORD = CompatUtils.getField( - android.provider.Settings.Secure.class, "ACCESSIBILITY_SPEAK_PASSWORD"); - - private SettingsSecureCompatUtils() { - // This class is non-instantiable. - } - - /** - * Whether to speak passwords while in accessibility mode. - */ - public static final String ACCESSIBILITY_SPEAK_PASSWORD = (String) CompatUtils.getFieldValue( - null /* receiver */, null /* defaultValue */, FIELD_ACCESSIBILITY_SPEAK_PASSWORD); -} diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java deleted file mode 100644 index 3f621913c..000000000 --- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.compat; - -import android.content.Context; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.SuggestionSpan; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.LocaleUtils; -import com.android.inputmethod.latin.define.DebugFlags; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Locale; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public final class SuggestionSpanUtils { - // Note that SuggestionSpan.FLAG_AUTO_CORRECTION has been introduced - // in API level 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1). - private static final Field FIELD_FLAG_AUTO_CORRECTION = CompatUtils.getField( - SuggestionSpan.class, "FLAG_AUTO_CORRECTION"); - private static final Integer OBJ_FLAG_AUTO_CORRECTION = (Integer) CompatUtils.getFieldValue( - null /* receiver */, null /* defaultValue */, FIELD_FLAG_AUTO_CORRECTION); - - static { - if (DebugFlags.DEBUG_ENABLED) { - if (OBJ_FLAG_AUTO_CORRECTION == null) { - throw new RuntimeException("Field is accidentially null."); - } - } - } - - private SuggestionSpanUtils() { - // This utility class is not publicly instantiable. - } - - @UsedForTesting - public static CharSequence getTextWithAutoCorrectionIndicatorUnderline( - final Context context, final String text, @Nonnull final Locale locale) { - if (TextUtils.isEmpty(text) || OBJ_FLAG_AUTO_CORRECTION == null) { - return text; - } - final Spannable spannable = new SpannableString(text); - final SuggestionSpan suggestionSpan = new SuggestionSpan(context, locale, - new String[] {} /* suggestions */, OBJ_FLAG_AUTO_CORRECTION, null); - spannable.setSpan(suggestionSpan, 0, text.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); - return spannable; - } - - @UsedForTesting - public static CharSequence getTextWithSuggestionSpan(final Context context, - final String pickedWord, final SuggestedWords suggestedWords, final Locale locale) { - if (TextUtils.isEmpty(pickedWord) || suggestedWords.isEmpty() - || suggestedWords.isPrediction() || suggestedWords.isPunctuationSuggestions()) { - return pickedWord; - } - - final ArrayList<String> suggestionsList = new ArrayList<>(); - for (int i = 0; i < suggestedWords.size(); ++i) { - if (suggestionsList.size() >= SuggestionSpan.SUGGESTIONS_MAX_SIZE) { - break; - } - final SuggestedWordInfo info = suggestedWords.getInfo(i); - if (info.isKindOf(SuggestedWordInfo.KIND_PREDICTION)) { - continue; - } - final String word = suggestedWords.getWord(i); - if (!TextUtils.equals(pickedWord, word)) { - suggestionsList.add(word.toString()); - } - } - final SuggestionSpan suggestionSpan = new SuggestionSpan(context, locale, - suggestionsList.toArray(new String[suggestionsList.size()]), 0 /* flags */, null); - final Spannable spannable = new SpannableString(pickedWord); - spannable.setSpan(suggestionSpan, 0, pickedWord.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - return spannable; - } - - /** - * Returns first {@link Locale} found in the given array of {@link SuggestionSpan}. - * @param suggestionSpans the array of {@link SuggestionSpan} to be examined. - * @return the first {@link Locale} found in {@code suggestionSpans}. {@code null} when not - * found. - */ - @UsedForTesting - @Nullable - public static Locale findFirstLocaleFromSuggestionSpans( - final SuggestionSpan[] suggestionSpans) { - for (final SuggestionSpan suggestionSpan : suggestionSpans) { - final String localeString = suggestionSpan.getLocale(); - if (TextUtils.isEmpty(localeString)) { - continue; - } - return LocaleUtils.constructLocaleFromString(localeString); - } - return null; - } -} diff --git a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java deleted file mode 100644 index d8d2be04c..000000000 --- a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.compat; - -import android.view.textservice.SuggestionsInfo; - -import java.lang.reflect.Field; - -public final class SuggestionsInfoCompatUtils { - // Note that SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS has been introduced - // in API level 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1). - private static final Field FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = - CompatUtils.getField(SuggestionsInfo.class, "RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS"); - private static final Integer OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = - (Integer) CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */, - FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS); - private static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = - OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS != null - ? OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS : 0; - - private SuggestionsInfoCompatUtils() { - // This utility class is not publicly instantiable. - } - - /** - * Returns the flag value of the attributes of the suggestions that can be obtained by - * {@link SuggestionsInfo#getSuggestionsAttributes()}: this tells that the text service thinks - * the result suggestions include highly recommended ones. - */ - public static int getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS() { - return RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS; - } -} diff --git a/java/src/com/android/inputmethod/compat/TextInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/TextInfoCompatUtils.java deleted file mode 100644 index 09f39a756..000000000 --- a/java/src/com/android/inputmethod/compat/TextInfoCompatUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.compat; - -import android.view.textservice.TextInfo; - -import com.android.inputmethod.annotations.UsedForTesting; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -@UsedForTesting -public final class TextInfoCompatUtils { - // Note that TextInfo.getCharSequence() is supposed to be available in API level 21 and later. - private static final Method TEXT_INFO_GET_CHAR_SEQUENCE = - CompatUtils.getMethod(TextInfo.class, "getCharSequence"); - private static final Constructor<?> TEXT_INFO_CONSTRUCTOR_FOR_CHAR_SEQUENCE = - CompatUtils.getConstructor(TextInfo.class, CharSequence.class, int.class, int.class, - int.class, int.class); - - @UsedForTesting - public static boolean isCharSequenceSupported() { - return TEXT_INFO_GET_CHAR_SEQUENCE != null && - TEXT_INFO_CONSTRUCTOR_FOR_CHAR_SEQUENCE != null; - } - - @UsedForTesting - public static TextInfo newInstance(CharSequence charSequence, int start, int end, int cookie, - int sequenceNumber) { - if (TEXT_INFO_CONSTRUCTOR_FOR_CHAR_SEQUENCE != null) { - return (TextInfo) CompatUtils.newInstance(TEXT_INFO_CONSTRUCTOR_FOR_CHAR_SEQUENCE, - charSequence, start, end, cookie, sequenceNumber); - } - return new TextInfo(charSequence.subSequence(start, end).toString(), cookie, - sequenceNumber); - } - - /** - * Returns the result of {@link TextInfo#getCharSequence()} when available. Otherwise returns - * the result of {@link TextInfo#getText()} as fall back. - * @param textInfo the instance for which {@link TextInfo#getCharSequence()} or - * {@link TextInfo#getText()} is called. - * @return the result of {@link TextInfo#getCharSequence()} when available. Otherwise returns - * the result of {@link TextInfo#getText()} as fall back. If {@code textInfo} is {@code null}, - * returns {@code null}. - */ - @UsedForTesting - public static CharSequence getCharSequenceOrString(final TextInfo textInfo) { - final CharSequence defaultValue = (textInfo == null ? null : textInfo.getText()); - return (CharSequence) CompatUtils.invoke(textInfo, defaultValue, - TEXT_INFO_GET_CHAR_SEQUENCE); - } -} diff --git a/java/src/com/android/inputmethod/compat/TextViewCompatUtils.java b/java/src/com/android/inputmethod/compat/TextViewCompatUtils.java deleted file mode 100644 index f8e1902c0..000000000 --- a/java/src/com/android/inputmethod/compat/TextViewCompatUtils.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.compat; - -import android.graphics.drawable.Drawable; -import android.widget.TextView; - -import java.lang.reflect.Method; - -public final class TextViewCompatUtils { - // Note that TextView.setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable,Drawable, - // Drawable,Drawable) has been introduced in API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1). - private static final Method METHOD_setCompoundDrawablesRelativeWithIntrinsicBounds = - CompatUtils.getMethod(TextView.class, "setCompoundDrawablesRelativeWithIntrinsicBounds", - Drawable.class, Drawable.class, Drawable.class, Drawable.class); - - private TextViewCompatUtils() { - // This utility class is not publicly instantiable. - } - - public static void setCompoundDrawablesRelativeWithIntrinsicBounds(final TextView textView, - final Drawable start, final Drawable top, final Drawable end, final Drawable bottom) { - if (METHOD_setCompoundDrawablesRelativeWithIntrinsicBounds == null) { - textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom); - return; - } - CompatUtils.invoke(textView, null, METHOD_setCompoundDrawablesRelativeWithIntrinsicBounds, - start, top, end, bottom); - } -} diff --git a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java deleted file mode 100644 index 3633d667d..000000000 --- a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.compat; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -import android.provider.UserDictionary; - -import java.util.Locale; - -public final class UserDictionaryCompatUtils { - @SuppressWarnings("deprecation") - public static void addWord(final Context context, final String word, - final int freq, final String shortcut, final Locale locale) { - if (BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - addWordWithShortcut(context, word, freq, shortcut, locale); - return; - } - // Fall back to the pre-JellyBean method. - final Locale currentLocale = context.getResources().getConfiguration().locale; - final int localeType = currentLocale.equals(locale) - ? UserDictionary.Words.LOCALE_TYPE_CURRENT : UserDictionary.Words.LOCALE_TYPE_ALL; - UserDictionary.Words.addWord(context, word, freq, localeType); - } - - // {@link UserDictionary.Words#addWord(Context,String,int,String,Locale)} was introduced - // in API level 16 (Build.VERSION_CODES.JELLY_BEAN). - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private static void addWordWithShortcut(final Context context, final String word, - final int freq, final String shortcut, final Locale locale) { - UserDictionary.Words.addWord(context, word, freq, shortcut, locale); - } -} - diff --git a/java/src/com/android/inputmethod/compat/UserManagerCompatUtils.java b/java/src/com/android/inputmethod/compat/UserManagerCompatUtils.java deleted file mode 100644 index a0ca2c9f2..000000000 --- a/java/src/com/android/inputmethod/compat/UserManagerCompatUtils.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.inputmethod.compat; - -import android.content.Context; -import android.os.Build; -import android.os.UserManager; -import androidx.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.reflect.Method; - -import static java.lang.annotation.RetentionPolicy.SOURCE; - -/** - * A temporary solution until {@code UserManagerCompat.isUserUnlocked()} in the support-v4 library - * becomes publicly available. - */ -public final class UserManagerCompatUtils { - private static final Method METHOD_isUserUnlocked; - - static { - // We do not try to search the method in Android M and prior. - if (BuildCompatUtils.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.M) { - METHOD_isUserUnlocked = null; - } else { - METHOD_isUserUnlocked = CompatUtils.getMethod(UserManager.class, "isUserUnlocked"); - } - } - - private UserManagerCompatUtils() { - // This utility class is not publicly instantiable. - } - - public static final int LOCK_STATE_UNKNOWN = 0; - public static final int LOCK_STATE_UNLOCKED = 1; - public static final int LOCK_STATE_LOCKED = 2; - - @Retention(SOURCE) - @IntDef({LOCK_STATE_UNKNOWN, LOCK_STATE_UNLOCKED, LOCK_STATE_LOCKED}) - public @interface LockState {} - - /** - * Check if the calling user is running in an "unlocked" state. A user is unlocked only after - * they've entered their credentials (such as a lock pattern or PIN), and credential-encrypted - * private app data storage is available. - * @param context context from which {@link UserManager} should be obtained. - * @return One of {@link LockState}. - */ - @LockState - public static int getUserLockState(final Context context) { - if (METHOD_isUserUnlocked == null) { - return LOCK_STATE_UNKNOWN; - } - final UserManager userManager = context.getSystemService(UserManager.class); - if (userManager == null) { - return LOCK_STATE_UNKNOWN; - } - final Boolean result = - (Boolean) CompatUtils.invoke(userManager, null, METHOD_isUserUnlocked); - if (result == null) { - return LOCK_STATE_UNKNOWN; - } - return result ? LOCK_STATE_UNLOCKED : LOCK_STATE_LOCKED; - } -} diff --git a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java deleted file mode 100644 index a584625e2..000000000 --- a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.compat; - -import android.view.View; - -import java.lang.reflect.Method; - -// TODO: Use {@link androidx.core.view.ViewCompat} instead of this utility class. -// Currently {@link #getPaddingEnd(View)} and {@link #setPaddingRelative(View,int,int,int,int)} -// are missing from android-support-v4 static library in KitKat SDK. -public final class ViewCompatUtils { - // Note that View.getPaddingEnd(), View.setPaddingRelative(int,int,int,int) have been - // introduced in API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1). - private static final Method METHOD_getPaddingEnd = CompatUtils.getMethod( - View.class, "getPaddingEnd"); - private static final Method METHOD_setPaddingRelative = CompatUtils.getMethod( - View.class, "setPaddingRelative", - int.class, int.class, int.class, int.class); - // Note that View.setTextAlignment(int) has been introduced in API level 17. - private static final Method METHOD_setTextAlignment = CompatUtils.getMethod( - View.class, "setTextAlignment", int.class); - - private ViewCompatUtils() { - // This utility class is not publicly instantiable. - } - - public static int getPaddingEnd(final View view) { - if (METHOD_getPaddingEnd == null) { - return view.getPaddingRight(); - } - return (Integer)CompatUtils.invoke(view, 0, METHOD_getPaddingEnd); - } - - public static void setPaddingRelative(final View view, final int start, final int top, - final int end, final int bottom) { - if (METHOD_setPaddingRelative == null) { - view.setPadding(start, top, end, bottom); - return; - } - CompatUtils.invoke(view, null, METHOD_setPaddingRelative, start, top, end, bottom); - } - - // These TEXT_ALIGNMENT_* constants have been introduced in API 17. - public static final int TEXT_ALIGNMENT_INHERIT = 0; - public static final int TEXT_ALIGNMENT_GRAVITY = 1; - public static final int TEXT_ALIGNMENT_TEXT_START = 2; - public static final int TEXT_ALIGNMENT_TEXT_END = 3; - public static final int TEXT_ALIGNMENT_CENTER = 4; - public static final int TEXT_ALIGNMENT_VIEW_START = 5; - public static final int TEXT_ALIGNMENT_VIEW_END = 6; - - public static void setTextAlignment(final View view, final int textAlignment) { - CompatUtils.invoke(view, null, METHOD_setTextAlignment, textAlignment); - } -} diff --git a/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtils.java deleted file mode 100644 index 0c8e5b77d..000000000 --- a/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.compat; - -import android.inputmethodservice.InputMethodService; -import android.os.Build; -import android.view.View; - -public class ViewOutlineProviderCompatUtils { - private ViewOutlineProviderCompatUtils() { - // This utility class is not publicly instantiable. - } - - public interface InsetsUpdater { - public void setInsets(final InputMethodService.Insets insets); - } - - private static final InsetsUpdater EMPTY_INSETS_UPDATER = new InsetsUpdater() { - @Override - public void setInsets(final InputMethodService.Insets insets) {} - }; - - public static InsetsUpdater setInsetsOutlineProvider(final View view) { - if (BuildCompatUtils.EFFECTIVE_SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return EMPTY_INSETS_UPDATER; - } - return ViewOutlineProviderCompatUtilsLXX.setInsetsOutlineProvider(view); - } -} diff --git a/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java b/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java deleted file mode 100644 index 5bbb5ce99..000000000 --- a/java/src/com/android/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.compat; - -import android.annotation.TargetApi; -import android.graphics.Outline; -import android.inputmethodservice.InputMethodService; -import android.os.Build; -import android.view.View; -import android.view.ViewOutlineProvider; - -import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater; - -@TargetApi(Build.VERSION_CODES.LOLLIPOP) -class ViewOutlineProviderCompatUtilsLXX { - private ViewOutlineProviderCompatUtilsLXX() { - // This utility class is not publicly instantiable. - } - - static InsetsUpdater setInsetsOutlineProvider(final View view) { - final InsetsOutlineProvider provider = new InsetsOutlineProvider(view); - view.setOutlineProvider(provider); - return provider; - } - - private static class InsetsOutlineProvider extends ViewOutlineProvider - implements InsetsUpdater { - private final View mView; - private static final int NO_DATA = -1; - private int mLastVisibleTopInsets = NO_DATA; - - public InsetsOutlineProvider(final View view) { - mView = view; - view.setOutlineProvider(this); - } - - @Override - public void setInsets(final InputMethodService.Insets insets) { - final int visibleTopInsets = insets.visibleTopInsets; - if (mLastVisibleTopInsets != visibleTopInsets) { - mLastVisibleTopInsets = visibleTopInsets; - mView.invalidateOutline(); - } - } - - @Override - public void getOutline(final View view, final Outline outline) { - if (mLastVisibleTopInsets == NO_DATA) { - // Call default implementation. - ViewOutlineProvider.BACKGROUND.getOutline(view, outline); - return; - } - // TODO: Revisit this when floating/resize keyboard is supported. - outline.setRect( - view.getLeft(), mLastVisibleTopInsets, view.getRight(), view.getBottom()); - } - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java deleted file mode 100644 index 1b526d453..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java +++ /dev/null @@ -1,625 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.app.DownloadManager.Request; -import android.content.ContentValues; -import android.content.Context; -import android.content.res.Resources; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.latin.BinaryDictionaryFileDumper; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.LocaleUtils; -import com.android.inputmethod.latin.utils.ApplicationUtils; -import com.android.inputmethod.latin.utils.DebugLogUtils; - -import java.util.LinkedList; -import java.util.Queue; - -/** - * Object representing an upgrade from one state to another. - * - * This implementation basically encapsulates a list of Runnable objects. In the future - * it may manage dependencies between them. Concretely, it does not use Runnable because the - * actions need an argument. - */ -/* - -The state of a word list follows the following scheme. - - | ^ - MakeAvailable | - | .------------Forget--------' - V | - STATUS_AVAILABLE <-------------------------. - | | -StartDownloadAction FinishDeleteAction - | | - V | -STATUS_DOWNLOADING EnableAction-- STATUS_DELETING - | | ^ -InstallAfterDownloadAction | | - | .---------------' StartDeleteAction - | | | - V V | - STATUS_INSTALLED <--EnableAction-- STATUS_DISABLED - --DisableAction--> - - It may also be possible that DisableAction or StartDeleteAction or - DownloadAction run when the file is still downloading. This cancels - the download and returns to STATUS_AVAILABLE. - Also, an UpdateDataAction may apply in any state. It does not affect - the state in any way (nor type, local filename, id or version) but - may update other attributes like description or remote filename. - - Forget is an DB maintenance action that removes the entry if it is not installed or disabled. - This happens when the word list information disappeared from the server, or when a new version - is available and we should forget about the old one. -*/ -public final class ActionBatch { - /** - * A piece of update. - * - * Action is basically like a Runnable that takes an argument. - */ - public interface Action { - /** - * Execute this action NOW. - * @param context the context to get system services, resources, databases - */ - void execute(final Context context); - } - - /** - * An action that starts downloading an available word list. - */ - public static final class StartDownloadAction implements Action { - static final String TAG = "DictionaryProvider:" + StartDownloadAction.class.getSimpleName(); - - private final String mClientId; - // The data to download. May not be null. - final WordListMetadata mWordList; - public StartDownloadAction(final String clientId, final WordListMetadata wordList) { - DebugLogUtils.l("New download action for client ", clientId, " : ", wordList); - mClientId = clientId; - mWordList = wordList; - } - - @Override - public void execute(final Context context) { - if (null == mWordList) { // This should never happen - Log.e(TAG, "UpdateAction with a null parameter!"); - return; - } - DebugLogUtils.l("Downloading word list"); - final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); - final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, - mWordList.mId, mWordList.mVersion); - final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); - final DownloadManagerWrapper manager = new DownloadManagerWrapper(context); - if (MetadataDbHelper.STATUS_DOWNLOADING == status) { - // The word list is still downloading. Cancel the download and revert the - // word list status to "available". - manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN)); - MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion); - } else if (MetadataDbHelper.STATUS_AVAILABLE != status - && MetadataDbHelper.STATUS_RETRYING != status) { - // Should never happen - Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' : " + status - + " for an upgrade action. Fall back to download."); - } - // Download it. - DebugLogUtils.l("Upgrade word list, downloading", mWordList.mRemoteFilename); - - // This is an upgraded word list: we should download it. - // Adding a disambiguator to circumvent a bug in older versions of DownloadManager. - // DownloadManager also stupidly cuts the extension to replace with its own that it - // gets from the content-type. We need to circumvent this. - final String disambiguator = "#" + System.currentTimeMillis() - + ApplicationUtils.getVersionName(context) + ".dict"; - final Uri uri = Uri.parse(mWordList.mRemoteFilename + disambiguator); - final Request request = new Request(uri); - - final Resources res = context.getResources(); - request.setAllowedNetworkTypes(Request.NETWORK_WIFI | Request.NETWORK_MOBILE); - request.setTitle(mWordList.mDescription); - request.setNotificationVisibility(Request.VISIBILITY_HIDDEN); - request.setVisibleInDownloadsUi( - res.getBoolean(R.bool.dict_downloads_visible_in_download_UI)); - - final long downloadId = UpdateHandler.registerDownloadRequest(manager, request, db, - mWordList.mId, mWordList.mVersion); - Log.i(TAG, String.format("Starting the dictionary download with version:" - + " %d and Url: %s", mWordList.mVersion, uri)); - DebugLogUtils.l("Starting download of", uri, "with id", downloadId); - PrivateLog.log("Starting download of " + uri + ", id : " + downloadId); - } - } - - /** - * An action that updates the database to reflect the status of a newly installed word list. - */ - public static final class InstallAfterDownloadAction implements Action { - static final String TAG = "DictionaryProvider:" - + InstallAfterDownloadAction.class.getSimpleName(); - private final String mClientId; - // The state to upgrade from. May not be null. - final ContentValues mWordListValues; - - public InstallAfterDownloadAction(final String clientId, - final ContentValues wordListValues) { - DebugLogUtils.l("New InstallAfterDownloadAction for client ", clientId, " : ", - wordListValues); - mClientId = clientId; - mWordListValues = wordListValues; - } - - @Override - public void execute(final Context context) { - if (null == mWordListValues) { - Log.e(TAG, "InstallAfterDownloadAction with a null parameter!"); - return; - } - final int status = mWordListValues.getAsInteger(MetadataDbHelper.STATUS_COLUMN); - if (MetadataDbHelper.STATUS_DOWNLOADING != status) { - final String id = mWordListValues.getAsString(MetadataDbHelper.WORDLISTID_COLUMN); - Log.e(TAG, "Unexpected state of the word list '" + id + "' : " + status - + " for an InstallAfterDownload action. Bailing out."); - return; - } - - DebugLogUtils.l("Setting word list as installed"); - final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); - MetadataDbHelper.markEntryAsFinishedDownloadingAndInstalled(db, mWordListValues); - - // Install the downloaded file by un-compressing and moving it to the staging - // directory. Ideally, we should do this before updating the DB, but the - // installDictToStagingFromContentProvider() relies on the db being updated. - final String localeString = mWordListValues.getAsString(MetadataDbHelper.LOCALE_COLUMN); - BinaryDictionaryFileDumper.installDictToStagingFromContentProvider( - LocaleUtils.constructLocaleFromString(localeString), context, false); - } - } - - /** - * An action that enables an existing word list. - */ - public static final class EnableAction implements Action { - static final String TAG = "DictionaryProvider:" + EnableAction.class.getSimpleName(); - private final String mClientId; - // The state to upgrade from. May not be null. - final WordListMetadata mWordList; - - public EnableAction(final String clientId, final WordListMetadata wordList) { - DebugLogUtils.l("New EnableAction for client ", clientId, " : ", wordList); - mClientId = clientId; - mWordList = wordList; - } - - @Override - public void execute(final Context context) { - if (null == mWordList) { - Log.e(TAG, "EnableAction with a null parameter!"); - return; - } - DebugLogUtils.l("Enabling word list"); - final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); - final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, - mWordList.mId, mWordList.mVersion); - final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); - if (MetadataDbHelper.STATUS_DISABLED != status - && MetadataDbHelper.STATUS_DELETING != status) { - Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + " : " + status - + " for an enable action. Cancelling"); - return; - } - MetadataDbHelper.markEntryAsEnabled(db, mWordList.mId, mWordList.mVersion); - } - } - - /** - * An action that disables a word list. - */ - public static final class DisableAction implements Action { - static final String TAG = "DictionaryProvider:" + DisableAction.class.getSimpleName(); - private final String mClientId; - // The word list to disable. May not be null. - final WordListMetadata mWordList; - public DisableAction(final String clientId, final WordListMetadata wordlist) { - DebugLogUtils.l("New Disable action for client ", clientId, " : ", wordlist); - mClientId = clientId; - mWordList = wordlist; - } - - @Override - public void execute(final Context context) { - if (null == mWordList) { // This should never happen - Log.e(TAG, "DisableAction with a null word list!"); - return; - } - DebugLogUtils.l("Disabling word list : " + mWordList); - final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); - final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, - mWordList.mId, mWordList.mVersion); - final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); - if (MetadataDbHelper.STATUS_INSTALLED == status) { - // Disabling an installed word list - MetadataDbHelper.markEntryAsDisabled(db, mWordList.mId, mWordList.mVersion); - } else { - if (MetadataDbHelper.STATUS_DOWNLOADING != status) { - Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' : " - + status + " for a disable action. Fall back to marking as available."); - } - // The word list is still downloading. Cancel the download and revert the - // word list status to "available". - final DownloadManagerWrapper manager = new DownloadManagerWrapper(context); - manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN)); - MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion); - } - } - } - - /** - * An action that makes a word list available. - */ - public static final class MakeAvailableAction implements Action { - static final String TAG = "DictionaryProvider:" + MakeAvailableAction.class.getSimpleName(); - private final String mClientId; - // The word list to make available. May not be null. - final WordListMetadata mWordList; - public MakeAvailableAction(final String clientId, final WordListMetadata wordlist) { - DebugLogUtils.l("New MakeAvailable action", clientId, " : ", wordlist); - mClientId = clientId; - mWordList = wordlist; - } - - @Override - public void execute(final Context context) { - if (null == mWordList) { // This should never happen - Log.e(TAG, "MakeAvailableAction with a null word list!"); - return; - } - final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); - if (null != MetadataDbHelper.getContentValuesByWordListId(db, - mWordList.mId, mWordList.mVersion)) { - Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' " - + " for a makeavailable action. Marking as available anyway."); - } - DebugLogUtils.l("Making word list available : " + mWordList); - // If mLocalFilename is null, then it's a remote file that hasn't been downloaded - // yet, so we set the local filename to the empty string. - final ContentValues values = MetadataDbHelper.makeContentValues(0, - MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_AVAILABLE, - mWordList.mId, mWordList.mLocale, mWordList.mDescription, - null == mWordList.mLocalFilename ? "" : mWordList.mLocalFilename, - mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum, - mWordList.mChecksum, mWordList.mRetryCount, mWordList.mFileSize, - mWordList.mVersion, mWordList.mFormatVersion); - PrivateLog.log("Insert 'available' record for " + mWordList.mDescription - + " and locale " + mWordList.mLocale); - db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values); - } - } - - /** - * An action that marks a word list as pre-installed. - * - * This is almost the same as MakeAvailableAction, as it only inserts a line with parameters - * received from outside. - * Unlike MakeAvailableAction, the parameters are not received from a downloaded metadata file - * but from the client directly; it marks a word list as being "installed" and not "available". - * It also explicitly sets the filename to the empty string, so that we don't try to open - * it on our side. - */ - public static final class MarkPreInstalledAction implements Action { - static final String TAG = "DictionaryProvider:" - + MarkPreInstalledAction.class.getSimpleName(); - private final String mClientId; - // The word list to mark pre-installed. May not be null. - final WordListMetadata mWordList; - public MarkPreInstalledAction(final String clientId, final WordListMetadata wordlist) { - DebugLogUtils.l("New MarkPreInstalled action", clientId, " : ", wordlist); - mClientId = clientId; - mWordList = wordlist; - } - - @Override - public void execute(final Context context) { - if (null == mWordList) { // This should never happen - Log.e(TAG, "MarkPreInstalledAction with a null word list!"); - return; - } - final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); - if (null != MetadataDbHelper.getContentValuesByWordListId(db, - mWordList.mId, mWordList.mVersion)) { - Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' " - + " for a markpreinstalled action. Marking as preinstalled anyway."); - } - DebugLogUtils.l("Marking word list preinstalled : " + mWordList); - // This word list is pre-installed : we don't have its file. We should reset - // the local file name to the empty string so that we don't try to open it - // accidentally. The remote filename may be set by the application if it so wishes. - final ContentValues values = MetadataDbHelper.makeContentValues(0, - MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_INSTALLED, - mWordList.mId, mWordList.mLocale, mWordList.mDescription, - TextUtils.isEmpty(mWordList.mLocalFilename) ? "" : mWordList.mLocalFilename, - mWordList.mRemoteFilename, mWordList.mLastUpdate, - mWordList.mRawChecksum, mWordList.mChecksum, mWordList.mRetryCount, - mWordList.mFileSize, mWordList.mVersion, mWordList.mFormatVersion); - PrivateLog.log("Insert 'preinstalled' record for " + mWordList.mDescription - + " and locale " + mWordList.mLocale); - db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values); - } - } - - /** - * An action that updates information about a word list - description, locale etc - */ - public static final class UpdateDataAction implements Action { - static final String TAG = "DictionaryProvider:" + UpdateDataAction.class.getSimpleName(); - private final String mClientId; - final WordListMetadata mWordList; - public UpdateDataAction(final String clientId, final WordListMetadata wordlist) { - DebugLogUtils.l("New UpdateData action for client ", clientId, " : ", wordlist); - mClientId = clientId; - mWordList = wordlist; - } - - @Override - public void execute(final Context context) { - if (null == mWordList) { // This should never happen - Log.e(TAG, "UpdateDataAction with a null word list!"); - return; - } - final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); - ContentValues oldValues = MetadataDbHelper.getContentValuesByWordListId(db, - mWordList.mId, mWordList.mVersion); - if (null == oldValues) { - Log.e(TAG, "Trying to update data about a non-existing word list. Bailing out."); - return; - } - DebugLogUtils.l("Updating data about a word list : " + mWordList); - final ContentValues values = MetadataDbHelper.makeContentValues( - oldValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN), - oldValues.getAsInteger(MetadataDbHelper.TYPE_COLUMN), - oldValues.getAsInteger(MetadataDbHelper.STATUS_COLUMN), - mWordList.mId, mWordList.mLocale, mWordList.mDescription, - oldValues.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN), - mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum, - mWordList.mChecksum, mWordList.mRetryCount, mWordList.mFileSize, - mWordList.mVersion, mWordList.mFormatVersion); - PrivateLog.log("Updating record for " + mWordList.mDescription - + " and locale " + mWordList.mLocale); - db.update(MetadataDbHelper.METADATA_TABLE_NAME, values, - MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND " - + MetadataDbHelper.VERSION_COLUMN + " = ?", - new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) }); - } - } - - /** - * An action that deletes the metadata about a word list if possible. - * - * This is triggered when a specific word list disappeared from the server, or when a fresher - * word list is available and the old one was not installed. - * If the word list has not been installed, it's possible to delete its associated metadata. - * Otherwise, the settings are retained so that the user can still administrate it. - */ - public static final class ForgetAction implements Action { - static final String TAG = "DictionaryProvider:" + ForgetAction.class.getSimpleName(); - private final String mClientId; - // The word list to remove. May not be null. - final WordListMetadata mWordList; - final boolean mHasNewerVersion; - public ForgetAction(final String clientId, final WordListMetadata wordlist, - final boolean hasNewerVersion) { - DebugLogUtils.l("New TryRemove action for client ", clientId, " : ", wordlist); - mClientId = clientId; - mWordList = wordlist; - mHasNewerVersion = hasNewerVersion; - } - - @Override - public void execute(final Context context) { - if (null == mWordList) { // This should never happen - Log.e(TAG, "TryRemoveAction with a null word list!"); - return; - } - DebugLogUtils.l("Trying to remove word list : " + mWordList); - final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); - final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, - mWordList.mId, mWordList.mVersion); - if (null == values) { - Log.e(TAG, "Trying to update the metadata of a non-existing wordlist. Cancelling."); - return; - } - final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); - if (mHasNewerVersion && MetadataDbHelper.STATUS_AVAILABLE != status) { - // If we have a newer version of this word list, we should be here ONLY if it was - // not installed - else we should be upgrading it. - Log.e(TAG, "Unexpected status for forgetting a word list info : " + status - + ", removing URL to prevent re-download"); - } - if (MetadataDbHelper.STATUS_INSTALLED == status - || MetadataDbHelper.STATUS_DISABLED == status - || MetadataDbHelper.STATUS_DELETING == status) { - // If it is installed or disabled, we need to mark it as deleted so that LatinIME - // will remove it next time it enquires for dictionaries. - // If it is deleting and we don't have a new version, then we have to wait until - // LatinIME actually has deleted it before we can remove its metadata. - // In both cases, remove the URI from the database since it is not supposed to - // be accessible any more. - values.put(MetadataDbHelper.REMOTE_FILENAME_COLUMN, ""); - values.put(MetadataDbHelper.STATUS_COLUMN, MetadataDbHelper.STATUS_DELETING); - db.update(MetadataDbHelper.METADATA_TABLE_NAME, values, - MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND " - + MetadataDbHelper.VERSION_COLUMN + " = ?", - new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) }); - } else { - // If it's AVAILABLE or DOWNLOADING or even UNKNOWN, delete the entry. - db.delete(MetadataDbHelper.METADATA_TABLE_NAME, - MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND " - + MetadataDbHelper.VERSION_COLUMN + " = ?", - new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) }); - } - } - } - - /** - * An action that sets the word list for deletion as soon as possible. - * - * This is triggered when the user requests deletion of a word list. This will mark it as - * deleted in the database, and fire an intent for Android Keyboard to take notice and - * reload its dictionaries right away if it is up. If it is not up now, then it will - * delete the actual file the next time it gets up. - * A file marked as deleted causes the content provider to supply a zero-sized file to - * Android Keyboard, which will overwrite any existing file and provide no words for this - * word list. This is not exactly a "deletion", since there is an actual file which takes up - * a few bytes on the disk, but this allows to override a default dictionary with an empty - * dictionary. This way, there is no need for the user to make a distinction between - * dictionaries installed by default and add-on dictionaries. - */ - public static final class StartDeleteAction implements Action { - static final String TAG = "DictionaryProvider:" + StartDeleteAction.class.getSimpleName(); - private final String mClientId; - // The word list to delete. May not be null. - final WordListMetadata mWordList; - public StartDeleteAction(final String clientId, final WordListMetadata wordlist) { - DebugLogUtils.l("New StartDelete action for client ", clientId, " : ", wordlist); - mClientId = clientId; - mWordList = wordlist; - } - - @Override - public void execute(final Context context) { - if (null == mWordList) { // This should never happen - Log.e(TAG, "StartDeleteAction with a null word list!"); - return; - } - DebugLogUtils.l("Trying to delete word list : " + mWordList); - final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); - final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, - mWordList.mId, mWordList.mVersion); - if (null == values) { - Log.e(TAG, "Trying to set a non-existing wordlist for removal. Cancelling."); - return; - } - final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); - if (MetadataDbHelper.STATUS_DISABLED != status) { - Log.e(TAG, "Unexpected status for deleting a word list info : " + status); - } - MetadataDbHelper.markEntryAsDeleting(db, mWordList.mId, mWordList.mVersion); - } - } - - /** - * An action that validates a word list as deleted. - * - * This will restore the word list as available if it still is, or remove the entry if - * it is not any more. - */ - public static final class FinishDeleteAction implements Action { - static final String TAG = "DictionaryProvider:" + FinishDeleteAction.class.getSimpleName(); - private final String mClientId; - // The word list to delete. May not be null. - final WordListMetadata mWordList; - public FinishDeleteAction(final String clientId, final WordListMetadata wordlist) { - DebugLogUtils.l("New FinishDelete action for client", clientId, " : ", wordlist); - mClientId = clientId; - mWordList = wordlist; - } - - @Override - public void execute(final Context context) { - if (null == mWordList) { // This should never happen - Log.e(TAG, "FinishDeleteAction with a null word list!"); - return; - } - DebugLogUtils.l("Trying to delete word list : " + mWordList); - final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); - final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, - mWordList.mId, mWordList.mVersion); - if (null == values) { - Log.e(TAG, "Trying to set a non-existing wordlist for removal. Cancelling."); - return; - } - final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); - if (MetadataDbHelper.STATUS_DELETING != status) { - Log.e(TAG, "Unexpected status for finish-deleting a word list info : " + status); - } - final String remoteFilename = - values.getAsString(MetadataDbHelper.REMOTE_FILENAME_COLUMN); - // If there isn't a remote filename any more, then we don't know where to get the file - // from any more, so we remove the entry entirely. As a matter of fact, if the file was - // marked DELETING but disappeared from the metadata on the server, it ended up - // this way. - if (TextUtils.isEmpty(remoteFilename)) { - db.delete(MetadataDbHelper.METADATA_TABLE_NAME, - MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND " - + MetadataDbHelper.VERSION_COLUMN + " = ?", - new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) }); - } else { - MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion); - } - } - } - - // An action batch consists of an ordered queue of Actions that can execute. - private final Queue<Action> mActions; - - public ActionBatch() { - mActions = new LinkedList<>(); - } - - public void add(final Action a) { - mActions.add(a); - } - - /** - * Append all the actions of another action batch. - * @param that the upgrade to merge into this one. - */ - public void append(final ActionBatch that) { - for (final Action a : that.mActions) { - add(a); - } - } - - /** - * Execute this batch. - * - * @param context the context for getting resources, databases, system services. - * @param reporter a Reporter to send errors to. - */ - public void execute(final Context context, final ProblemReporter reporter) { - DebugLogUtils.l("Executing a batch of actions"); - Queue<Action> remainingActions = mActions; - while (!remainingActions.isEmpty()) { - final Action a = remainingActions.poll(); - try { - a.execute(context); - } catch (Exception e) { - if (null != reporter) - reporter.report(e); - } - } - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/AssetFileAddress.java b/java/src/com/android/inputmethod/dictionarypack/AssetFileAddress.java deleted file mode 100644 index bebb59fc0..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/AssetFileAddress.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import java.io.File; - -/** - * Immutable class to hold the address of an asset. - * As opposed to a normal file, an asset is usually represented as a contiguous byte array in - * the package file. Open it correctly thus requires the name of the package it is in, but - * also the offset in the file and the length of this data. This class encapsulates these three. - */ -final class AssetFileAddress { - public final String mFilename; - public final long mOffset; - public final long mLength; - - public AssetFileAddress(final String filename, final long offset, final long length) { - mFilename = filename; - mOffset = offset; - mLength = length; - } - - /** - * Makes an AssetFileAddress. This may return null. - * - * @param filename the filename. - * @return the address, or null if the file does not exist or the parameters are not valid. - */ - public static AssetFileAddress makeFromFileName(final String filename) { - if (null == filename) return null; - final File f = new File(filename); - if (!f.isFile()) return null; - return new AssetFileAddress(filename, 0l, f.length()); - } - - /** - * Makes an AssetFileAddress. This may return null. - * - * @param filename the filename. - * @param offset the offset. - * @param length the length. - * @return the address, or null if the file does not exist or the parameters are not valid. - */ - public static AssetFileAddress makeFromFileNameAndOffset(final String filename, - final long offset, final long length) { - if (null == filename) return null; - final File f = new File(filename); - if (!f.isFile()) return null; - return new AssetFileAddress(filename, offset, length); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/BadFormatException.java b/java/src/com/android/inputmethod/dictionarypack/BadFormatException.java deleted file mode 100644 index d3090ddb0..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/BadFormatException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -/** - * Exception thrown when the metadata for the dictionary does not comply to a known format. - */ -public final class BadFormatException extends Exception { - public BadFormatException() { - super(); - } - - public BadFormatException(final String message) { - super(message); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java deleted file mode 100644 index 0fa72c3fd..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Copyright (C) 2013 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 com.android.inputmethod.dictionarypack; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewPropertyAnimator; -import android.widget.Button; -import android.widget.FrameLayout; - -import com.android.inputmethod.latin.R; - -/** - * A view that handles buttons inside it according to a status. - */ -public class ButtonSwitcher extends FrameLayout { - public static final int NOT_INITIALIZED = -1; - public static final int STATUS_NO_BUTTON = 0; - public static final int STATUS_INSTALL = 1; - public static final int STATUS_CANCEL = 2; - public static final int STATUS_DELETE = 3; - // One of the above - private int mStatus = NOT_INITIALIZED; - private int mAnimateToStatus = NOT_INITIALIZED; - - // Animation directions - public static final int ANIMATION_IN = 1; - public static final int ANIMATION_OUT = 2; - - private Button mInstallButton; - private Button mCancelButton; - private Button mDeleteButton; - private DictionaryListInterfaceState mInterfaceState; - private OnClickListener mOnClickListener; - - public ButtonSwitcher(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ButtonSwitcher(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public void reset(final DictionaryListInterfaceState interfaceState) { - mStatus = NOT_INITIALIZED; - mAnimateToStatus = NOT_INITIALIZED; - mInterfaceState = interfaceState; - } - - @Override - protected void onLayout(final boolean changed, final int left, final int top, final int right, - final int bottom) { - super.onLayout(changed, left, top, right, bottom); - mInstallButton = (Button)findViewById(R.id.dict_install_button); - mCancelButton = (Button)findViewById(R.id.dict_cancel_button); - mDeleteButton = (Button)findViewById(R.id.dict_delete_button); - setInternalOnClickListener(mOnClickListener); - setButtonPositionWithoutAnimation(mStatus); - if (mAnimateToStatus != NOT_INITIALIZED) { - // We have been asked to animate before we were ready, so we took a note of it. - // We are now ready: launch the animation. - animateButtonPosition(mStatus, mAnimateToStatus); - mStatus = mAnimateToStatus; - mAnimateToStatus = NOT_INITIALIZED; - } - } - - private Button getButton(final int status) { - switch(status) { - case STATUS_INSTALL: - return mInstallButton; - case STATUS_CANCEL: - return mCancelButton; - case STATUS_DELETE: - return mDeleteButton; - default: - return null; - } - } - - public void setStatusAndUpdateVisuals(final int status) { - if (mStatus == NOT_INITIALIZED) { - setButtonPositionWithoutAnimation(status); - mStatus = status; - } else { - if (null == mInstallButton) { - // We may come here before we have been layout. In this case we don't know our - // size yet so we can't start animations so we need to remember what animation to - // start once layout has gone through. - mAnimateToStatus = status; - } else { - animateButtonPosition(mStatus, status); - mStatus = status; - } - } - } - - private void setButtonPositionWithoutAnimation(final int status) { - // This may be called by setStatus() before the layout has come yet. - if (null == mInstallButton) return; - final int width = getWidth(); - // Set to out of the screen if that's not the currently displayed status - mInstallButton.setTranslationX(STATUS_INSTALL == status ? 0 : width); - mCancelButton.setTranslationX(STATUS_CANCEL == status ? 0 : width); - mDeleteButton.setTranslationX(STATUS_DELETE == status ? 0 : width); - } - - // The helper method for {@link AnimatorListenerAdapter}. - void animateButtonIfStatusIsEqual(final View newButton, final int newStatus) { - if (newStatus != mStatus) return; - animateButton(newButton, ANIMATION_IN); - } - - private void animateButtonPosition(final int oldStatus, final int newStatus) { - final View oldButton = getButton(oldStatus); - final View newButton = getButton(newStatus); - if (null != oldButton && null != newButton) { - // Transition between two buttons : animate out, then in - animateButton(oldButton, ANIMATION_OUT).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(final Animator animation) { - animateButtonIfStatusIsEqual(newButton, newStatus); - } - }); - } else if (null != oldButton) { - animateButton(oldButton, ANIMATION_OUT); - } else if (null != newButton) { - animateButton(newButton, ANIMATION_IN); - } - } - - public void setInternalOnClickListener(final OnClickListener listener) { - mOnClickListener = listener; - if (null != mInstallButton) { - // Already laid out : do it now - mInstallButton.setOnClickListener(mOnClickListener); - mCancelButton.setOnClickListener(mOnClickListener); - mDeleteButton.setOnClickListener(mOnClickListener); - } - } - - private ViewPropertyAnimator animateButton(final View button, final int direction) { - final float outerX = getWidth(); - final float innerX = button.getX() - button.getTranslationX(); - mInterfaceState.removeFromCache((View)getParent()); - if (ANIMATION_IN == direction) { - button.setClickable(true); - return button.animate().translationX(0); - } - button.setClickable(false); - return button.animate().translationX(outerX - innerX); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java b/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java deleted file mode 100644 index 3d0e29ed0..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.content.Context; -import android.content.SharedPreferences; - -public final class CommonPreferences { - private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs"; - - public static SharedPreferences getCommonPreferences(final Context context) { - return context.getSharedPreferences(COMMON_PREFERENCES_NAME, 0); - } - - public static void enable(final SharedPreferences pref, final String id) { - final SharedPreferences.Editor editor = pref.edit(); - editor.putBoolean(id, true); - editor.apply(); - } - - public static void disable(final SharedPreferences pref, final String id) { - final SharedPreferences.Editor editor = pref.edit(); - editor.putBoolean(id, false); - editor.apply(); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/CompletedDownloadInfo.java b/java/src/com/android/inputmethod/dictionarypack/CompletedDownloadInfo.java deleted file mode 100644 index ff198756e..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/CompletedDownloadInfo.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.dictionarypack; - -import android.app.DownloadManager; - -/** - * Struct class to encapsulate the result of a completed download. - */ -public class CompletedDownloadInfo { - final String mUri; - final long mDownloadId; - final int mStatus; - public CompletedDownloadInfo(final String uri, final long downloadId, final int status) { - mUri = uri; - mDownloadId = downloadId; - mStatus = status; - } - public boolean wasSuccessful() { - return DownloadManager.STATUS_SUCCESSFUL == mStatus; - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java deleted file mode 100644 index 759852025..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java +++ /dev/null @@ -1,173 +0,0 @@ -/** - * Copyright (C) 2013 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 com.android.inputmethod.dictionarypack; - -import android.app.DownloadManager; -import android.app.DownloadManager.Query; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.os.Handler; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.widget.ProgressBar; - -public class DictionaryDownloadProgressBar extends ProgressBar { - private static final String TAG = DictionaryDownloadProgressBar.class.getSimpleName(); - private static final int NOT_A_DOWNLOADMANAGER_PENDING_ID = 0; - - private String mClientId; - private String mWordlistId; - private boolean mIsCurrentlyAttachedToWindow = false; - private Thread mReporterThread = null; - - public DictionaryDownloadProgressBar(final Context context) { - super(context); - } - - public DictionaryDownloadProgressBar(final Context context, final AttributeSet attrs) { - super(context, attrs); - } - - public void setIds(final String clientId, final String wordlistId) { - mClientId = clientId; - mWordlistId = wordlistId; - } - - static private int getDownloadManagerPendingIdFromWordlistId(final Context context, - final String clientId, final String wordlistId) { - final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId); - final ContentValues wordlistValues = - MetadataDbHelper.getContentValuesOfLatestAvailableWordlistById(db, wordlistId); - if (null == wordlistValues) { - // We don't know anything about a word list with this id. Bug? This should never - // happen, but still return to prevent a crash. - Log.e(TAG, "Unexpected word list ID: " + wordlistId); - return NOT_A_DOWNLOADMANAGER_PENDING_ID; - } - return wordlistValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN); - } - - /* - * This method will stop any running updater thread for this progress bar and create and run - * a new one only if the progress bar is visible. - * Hence, as a result of calling this method, the progress bar will have an updater thread - * running if and only if the progress bar is visible. - */ - private void updateReporterThreadRunningStatusAccordingToVisibility() { - if (null != mReporterThread) mReporterThread.interrupt(); - if (mIsCurrentlyAttachedToWindow && View.VISIBLE == getVisibility()) { - final int downloadManagerPendingId = - getDownloadManagerPendingIdFromWordlistId(getContext(), mClientId, mWordlistId); - if (NOT_A_DOWNLOADMANAGER_PENDING_ID == downloadManagerPendingId) { - // Can't get the ID. This is never supposed to happen, but still clear the updater - // thread and return to avoid a crash. - mReporterThread = null; - return; - } - final UpdaterThread updaterThread = - new UpdaterThread(getContext(), downloadManagerPendingId); - updaterThread.start(); - mReporterThread = updaterThread; - } else { - // We're not going to restart the thread anyway, so we may as well garbage collect it. - mReporterThread = null; - } - } - - @Override - protected void onAttachedToWindow() { - mIsCurrentlyAttachedToWindow = true; - updateReporterThreadRunningStatusAccordingToVisibility(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mIsCurrentlyAttachedToWindow = false; - updateReporterThreadRunningStatusAccordingToVisibility(); - } - - private class UpdaterThread extends Thread { - private final static int REPORT_PERIOD = 150; // how often to report progress, in ms - final DownloadManagerWrapper mDownloadManagerWrapper; - final int mId; - public UpdaterThread(final Context context, final int id) { - super(); - mDownloadManagerWrapper = new DownloadManagerWrapper(context); - mId = id; - } - @Override - public void run() { - try { - final UpdateHelper updateHelper = new UpdateHelper(); - final Query query = new Query().setFilterById(mId); - setIndeterminate(true); - while (!isInterrupted()) { - final Cursor cursor = mDownloadManagerWrapper.query(query); - if (null == cursor) { - // Can't contact DownloadManager: this should never happen. - return; - } - try { - if (cursor.moveToNext()) { - final int columnBytesDownloadedSoFar = cursor.getColumnIndex( - DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR); - final int bytesDownloadedSoFar = - cursor.getInt(columnBytesDownloadedSoFar); - updateHelper.setProgressFromAnotherThread(bytesDownloadedSoFar); - } else { - // Download has finished and DownloadManager has already been asked to - // clean up the db entry. - updateHelper.setProgressFromAnotherThread(getMax()); - return; - } - } finally { - cursor.close(); - } - Thread.sleep(REPORT_PERIOD); - } - } catch (InterruptedException e) { - // Do nothing and terminate normally. - } - } - - class UpdateHelper implements Runnable { - private int mProgress; - @Override - public void run() { - setIndeterminate(false); - setProgress(mProgress); - } - public void setProgressFromAnotherThread(final int progress) { - if (mProgress != progress) { - mProgress = progress; - // For some unknown reason, setProgress just does not work from a separate - // thread, although the code in ProgressBar looks like it should. Thus, we - // resort to a runnable posted to the handler of the view. - final Handler handler = getHandler(); - // It's possible to come here before this view has been laid out. If so, - // just ignore the call - it will be updated again later. - if (null == handler) return; - handler.post(this); - } - } - } - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java deleted file mode 100644 index 836340a75..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (C) 2013 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 com.android.inputmethod.dictionarypack; - -import android.view.View; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * Helper class to maintain the interface state of word list preferences. - * - * This is necessary because the views are created on-demand by calling code. There are many - * situations where views are renewed with little relation with user interaction. For example, - * when scrolling, the view is reused so it doesn't keep its state, which means we need to keep - * it separately. Also whenever the underlying dictionary list undergoes a change (for example, - * update the metadata, or finish downloading) the whole list has to be thrown out and recreated - * in case some dictionaries appeared, disappeared, changed states etc. - */ -public class DictionaryListInterfaceState { - static class State { - public boolean mOpen = false; - public int mStatus = MetadataDbHelper.STATUS_UNKNOWN; - } - - private HashMap<String, State> mWordlistToState = new HashMap<>(); - private ArrayList<View> mViewCache = new ArrayList<>(); - - public boolean isOpen(final String wordlistId) { - final State state = mWordlistToState.get(wordlistId); - if (null == state) return false; - return state.mOpen; - } - - public int getStatus(final String wordlistId) { - final State state = mWordlistToState.get(wordlistId); - if (null == state) return MetadataDbHelper.STATUS_UNKNOWN; - return state.mStatus; - } - - public void setOpen(final String wordlistId, final int status) { - final State newState; - final State state = mWordlistToState.get(wordlistId); - newState = null == state ? new State() : state; - newState.mOpen = true; - newState.mStatus = status; - mWordlistToState.put(wordlistId, newState); - } - - public void closeAll() { - for (final State state : mWordlistToState.values()) { - state.mOpen = false; - } - } - - public View findFirstOrphanedView() { - for (final View v : mViewCache) { - if (null == v.getParent()) return v; - } - return null; - } - - public View addToCacheAndReturnView(final View view) { - mViewCache.add(view); - return view; - } - - public void removeFromCache(final View view) { - mViewCache.remove(view); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java deleted file mode 100644 index 13caea403..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.dictionarypack; - -/** - * A class to group constants for dictionary pack usage. - * - * This class only defines constants. It should not make any references to outside code as far as - * possible, as it's used to separate cleanly the keyboard code from the dictionary pack code; this - * is needed in particular to cleanly compile regression tests. - */ -public class DictionaryPackConstants { - /** - * The root domain for the dictionary pack, upon which authorities and actions will append - * their own distinctive strings. - */ - private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack.aosp"; - - /** - * Authority for the ContentProvider protocol. - */ - // TODO: find some way to factorize this string with the one in the resources - public static final String AUTHORITY = DICTIONARY_DOMAIN; - - /** - * The action of the intent for publishing that new dictionary data is available. - */ - // TODO: make this different across different packages. A suggested course of action is - // to use the package name inside this string. - // NOTE: The appended string should be uppercase like all other actions, but it's not for - // historical reasons. - public static final String NEW_DICTIONARY_INTENT_ACTION = DICTIONARY_DOMAIN + ".newdict"; - - /** - * The action of the intent sent by the dictionary pack to ask for a client to make - * itself known. This is used when the settings activity is brought up for a client the - * dictionary pack does not know about. - */ - public static final String UNKNOWN_DICTIONARY_PROVIDER_CLIENT = DICTIONARY_DOMAIN - + ".UNKNOWN_CLIENT"; - - // In the above intents, the name of the string extra that contains the name of the client - // we want information about. - public static final String DICTIONARY_PROVIDER_CLIENT_EXTRA = "client"; - - /** - * The action of the intent to tell the dictionary provider to update now. - */ - public static final String UPDATE_NOW_INTENT_ACTION = DICTIONARY_DOMAIN - + ".UPDATE_NOW"; - - /** - * The intent action to inform the dictionary provider to initialize the db - * and update now. - */ - public static final String INIT_AND_UPDATE_NOW_INTENT_ACTION = DICTIONARY_DOMAIN - + ".INIT_AND_UPDATE_NOW"; -} diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java deleted file mode 100644 index 308b123e1..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java +++ /dev/null @@ -1,541 +0,0 @@ -/** - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.UriMatcher; -import android.content.res.AssetFileDescriptor; -import android.database.AbstractCursor; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.LocaleUtils; -import com.android.inputmethod.latin.utils.DebugLogUtils; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; - -/** - * Provider for dictionaries. - * - * This class is a ContentProvider exposing all available dictionary data as managed by - * the dictionary pack. - */ -public final class DictionaryProvider extends ContentProvider { - private static final String TAG = DictionaryProvider.class.getSimpleName(); - public static final boolean DEBUG = false; - - public static final Uri CONTENT_URI = - Uri.parse(ContentResolver.SCHEME_CONTENT + "://" + DictionaryPackConstants.AUTHORITY); - private static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt"; - private static final String QUERY_PARAMETER_TRUE = "true"; - private static final String QUERY_PARAMETER_DELETE_RESULT = "result"; - private static final String QUERY_PARAMETER_FAILURE = "failure"; - public static final String QUERY_PARAMETER_PROTOCOL_VERSION = "protocol"; - private static final int NO_MATCH = 0; - private static final int DICTIONARY_V1_WHOLE_LIST = 1; - private static final int DICTIONARY_V1_DICT_INFO = 2; - private static final int DICTIONARY_V2_METADATA = 3; - private static final int DICTIONARY_V2_WHOLE_LIST = 4; - private static final int DICTIONARY_V2_DICT_INFO = 5; - private static final int DICTIONARY_V2_DATAFILE = 6; - private static final UriMatcher sUriMatcherV1 = new UriMatcher(NO_MATCH); - private static final UriMatcher sUriMatcherV2 = new UriMatcher(NO_MATCH); - static - { - sUriMatcherV1.addURI(DictionaryPackConstants.AUTHORITY, "list", DICTIONARY_V1_WHOLE_LIST); - sUriMatcherV1.addURI(DictionaryPackConstants.AUTHORITY, "*", DICTIONARY_V1_DICT_INFO); - sUriMatcherV2.addURI(DictionaryPackConstants.AUTHORITY, "*/metadata", - DICTIONARY_V2_METADATA); - sUriMatcherV2.addURI(DictionaryPackConstants.AUTHORITY, "*/list", DICTIONARY_V2_WHOLE_LIST); - sUriMatcherV2.addURI(DictionaryPackConstants.AUTHORITY, "*/dict/*", - DICTIONARY_V2_DICT_INFO); - sUriMatcherV2.addURI(DictionaryPackConstants.AUTHORITY, "*/datafile/*", - DICTIONARY_V2_DATAFILE); - } - - // MIME types for dictionary and dictionary list, as required by ContentProvider contract. - public static final String DICT_LIST_MIME_TYPE = - "vnd.android.cursor.item/vnd.google.dictionarylist"; - public static final String DICT_DATAFILE_MIME_TYPE = - "vnd.android.cursor.item/vnd.google.dictionary"; - - public static final String ID_CATEGORY_SEPARATOR = ":"; - - private static final class WordListInfo { - public final String mId; - public final String mLocale; - public final String mRawChecksum; - public final int mMatchLevel; - public WordListInfo(final String id, final String locale, final String rawChecksum, - final int matchLevel) { - mId = id; - mLocale = locale; - mRawChecksum = rawChecksum; - mMatchLevel = matchLevel; - } - } - - /** - * A cursor for returning a list of file ids from a List of strings. - * - * This simulates only the necessary methods. It has no error handling to speak of, - * and does not support everything a database does, only a few select necessary methods. - */ - private static final class ResourcePathCursor extends AbstractCursor { - - // Column names for the cursor returned by this content provider. - static private final String[] columnNames = { MetadataDbHelper.WORDLISTID_COLUMN, - MetadataDbHelper.LOCALE_COLUMN, MetadataDbHelper.RAW_CHECKSUM_COLUMN }; - - // The list of word lists served by this provider that match the client request. - final WordListInfo[] mWordLists; - // Note : the cursor also uses mPos, which is defined in AbstractCursor. - - public ResourcePathCursor(final Collection<WordListInfo> wordLists) { - // Allocating a 0-size WordListInfo here allows the toArray() method - // to ensure we have a strongly-typed array. It's thrown out. That's - // what the documentation of #toArray says to do in order to get a - // new strongly typed array of the correct size. - mWordLists = wordLists.toArray(new WordListInfo[0]); - mPos = 0; - } - - @Override - public String[] getColumnNames() { - return columnNames; - } - - @Override - public int getCount() { - return mWordLists.length; - } - - @Override public double getDouble(int column) { return 0; } - @Override public float getFloat(int column) { return 0; } - @Override public int getInt(int column) { return 0; } - @Override public short getShort(int column) { return 0; } - @Override public long getLong(int column) { return 0; } - - @Override public String getString(final int column) { - switch (column) { - case 0: return mWordLists[mPos].mId; - case 1: return mWordLists[mPos].mLocale; - case 2: return mWordLists[mPos].mRawChecksum; - default : return null; - } - } - - @Override - public boolean isNull(final int column) { - if (mPos >= mWordLists.length) return true; - return column != 0; - } - } - - @Override - public boolean onCreate() { - return true; - } - - private static int matchUri(final Uri uri) { - int protocolVersion = 1; - final String protocolVersionArg = uri.getQueryParameter(QUERY_PARAMETER_PROTOCOL_VERSION); - if ("2".equals(protocolVersionArg)) protocolVersion = 2; - switch (protocolVersion) { - case 1: return sUriMatcherV1.match(uri); - case 2: return sUriMatcherV2.match(uri); - default: return NO_MATCH; - } - } - - private static String getClientId(final Uri uri) { - int protocolVersion = 1; - final String protocolVersionArg = uri.getQueryParameter(QUERY_PARAMETER_PROTOCOL_VERSION); - if ("2".equals(protocolVersionArg)) protocolVersion = 2; - switch (protocolVersion) { - case 1: return null; // In protocol 1, the client ID is always null. - case 2: return uri.getPathSegments().get(0); - default: return null; - } - } - - /** - * Returns the MIME type of the content associated with an Uri - * - * @see android.content.ContentProvider#getType(android.net.Uri) - * - * @param uri the URI of the content the type of which should be returned. - * @return the MIME type, or null if the URL is not recognized. - */ - @Override - public String getType(final Uri uri) { - PrivateLog.log("Asked for type of : " + uri); - final int match = matchUri(uri); - switch (match) { - case NO_MATCH: return null; - case DICTIONARY_V1_WHOLE_LIST: - case DICTIONARY_V1_DICT_INFO: - case DICTIONARY_V2_WHOLE_LIST: - case DICTIONARY_V2_DICT_INFO: return DICT_LIST_MIME_TYPE; - case DICTIONARY_V2_DATAFILE: return DICT_DATAFILE_MIME_TYPE; - default: return null; - } - } - - /** - * Query the provider for dictionary files. - * - * This version dispatches the query according to the protocol version found in the - * ?protocol= query parameter. If absent or not well-formed, it defaults to 1. - * @see android.content.ContentProvider#query(Uri, String[], String, String[], String) - * - * @param uri a content uri (see sUriMatcherV{1,2} at the top of this file for format) - * @param projection ignored. All columns are always returned. - * @param selection ignored. - * @param selectionArgs ignored. - * @param sortOrder ignored. The results are always returned in no particular order. - * @return a cursor matching the uri, or null if the URI was not recognized. - */ - @Override - public Cursor query(final Uri uri, final String[] projection, final String selection, - final String[] selectionArgs, final String sortOrder) { - DebugLogUtils.l("Uri =", uri); - PrivateLog.log("Query : " + uri); - final String clientId = getClientId(uri); - final int match = matchUri(uri); - switch (match) { - case DICTIONARY_V1_WHOLE_LIST: - case DICTIONARY_V2_WHOLE_LIST: - final Cursor c = MetadataDbHelper.queryDictionaries(getContext(), clientId); - DebugLogUtils.l("List of dictionaries with count", c.getCount()); - PrivateLog.log("Returned a list of " + c.getCount() + " items"); - return c; - case DICTIONARY_V2_DICT_INFO: - // In protocol version 2, we return null if the client is unknown. Otherwise - // we behave exactly like for protocol 1. - if (!MetadataDbHelper.isClientKnown(getContext(), clientId)) return null; - // Fall through - case DICTIONARY_V1_DICT_INFO: - final String locale = uri.getLastPathSegment(); - final Collection<WordListInfo> dictFiles = - getDictionaryWordListsForLocale(clientId, locale); - // TODO: pass clientId to the following function - DictionaryService.updateNowIfNotUpdatedInAVeryLongTime(getContext()); - if (null != dictFiles && dictFiles.size() > 0) { - PrivateLog.log("Returned " + dictFiles.size() + " files"); - return new ResourcePathCursor(dictFiles); - } - PrivateLog.log("No dictionary files for this URL"); - return new ResourcePathCursor(Collections.<WordListInfo>emptyList()); - // V2_METADATA and V2_DATAFILE are not supported for query() - default: - return null; - } - } - - /** - * Helper method to get the wordlist metadata associated with a wordlist ID. - * - * @param clientId the ID of the client - * @param wordlistId the ID of the wordlist for which to get the metadata. - * @return the metadata for this wordlist ID, or null if none could be found. - */ - private ContentValues getWordlistMetadataForWordlistId(final String clientId, - final String wordlistId) { - final Context context = getContext(); - if (TextUtils.isEmpty(wordlistId)) return null; - final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId); - return MetadataDbHelper.getInstalledOrDeletingWordListContentValuesByWordListId( - db, wordlistId); - } - - /** - * Opens an asset file for an URI. - * - * Called by {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} or - * {@link android.content.ContentResolver#openInputStream(Uri)} from a client requesting a - * dictionary. - * @see android.content.ContentProvider#openAssetFile(Uri, String) - * - * @param uri the URI the file is for. - * @param mode the mode to read the file. MUST be "r" for readonly. - * @return the descriptor, or null if the file is not found or if mode is not equals to "r". - */ - @Override - public AssetFileDescriptor openAssetFile(final Uri uri, final String mode) { - if (null == mode || !"r".equals(mode)) return null; - - final int match = matchUri(uri); - if (DICTIONARY_V1_DICT_INFO != match && DICTIONARY_V2_DATAFILE != match) { - // Unsupported URI for openAssetFile - Log.w(TAG, "Unsupported URI for openAssetFile : " + uri); - return null; - } - final String wordlistId = uri.getLastPathSegment(); - final String clientId = getClientId(uri); - final ContentValues wordList = getWordlistMetadataForWordlistId(clientId, wordlistId); - - if (null == wordList) return null; - - try { - final int status = wordList.getAsInteger(MetadataDbHelper.STATUS_COLUMN); - if (MetadataDbHelper.STATUS_DELETING == status) { - // This will return an empty file (R.raw.empty points at an empty dictionary) - // This is how we "delete" the files. It allows Android Keyboard to fake deleting - // a default dictionary - which is actually in its assets and can't be really - // deleted. - final AssetFileDescriptor afd = getContext().getResources().openRawResourceFd( - R.raw.empty); - return afd; - } - final String localFilename = - wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN); - final File f = getContext().getFileStreamPath(localFilename); - final ParcelFileDescriptor pfd = - ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY); - return new AssetFileDescriptor(pfd, 0, pfd.getStatSize()); - } catch (FileNotFoundException e) { - // No file : fall through and return null - } - return null; - } - - /** - * Reads the metadata and returns the collection of dictionaries for a given locale. - * - * Word list IDs are expected to be in the form category:manual_id. This method - * will select only one word list for each category: the one with the most specific - * locale matching the locale specified in the URI. The manual id serves only to - * distinguish a word list from another for the purpose of updating, and is arbitrary - * but may not contain a colon. - * - * @param clientId the ID of the client requesting the list - * @param locale the locale for which we want the list, as a String - * @return a collection of ids. It is guaranteed to be non-null, but may be empty. - */ - private Collection<WordListInfo> getDictionaryWordListsForLocale(final String clientId, - final String locale) { - final Context context = getContext(); - final Cursor results = - MetadataDbHelper.queryInstalledOrDeletingOrAvailableDictionaryMetadata(context, - clientId); - if (null == results) { - return Collections.<WordListInfo>emptyList(); - } - try { - final HashMap<String, WordListInfo> dicts = new HashMap<>(); - final int idIndex = results.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN); - final int localeIndex = results.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN); - final int localFileNameIndex = - results.getColumnIndex(MetadataDbHelper.LOCAL_FILENAME_COLUMN); - final int rawChecksumIndex = - results.getColumnIndex(MetadataDbHelper.RAW_CHECKSUM_COLUMN); - final int statusIndex = results.getColumnIndex(MetadataDbHelper.STATUS_COLUMN); - if (results.moveToFirst()) { - do { - final String wordListId = results.getString(idIndex); - if (TextUtils.isEmpty(wordListId)) continue; - final String[] wordListIdArray = - TextUtils.split(wordListId, ID_CATEGORY_SEPARATOR); - final String wordListCategory; - if (2 == wordListIdArray.length) { - // This is at the category:manual_id format. - wordListCategory = wordListIdArray[0]; - // We don't need to read wordListIdArray[1] here, because it's irrelevant to - // word list selection - it's just a name we use to identify which data file - // is a newer version of which word list. We do however return the full id - // string for each selected word list, so in this sense we are 'using' it. - } else { - // This does not contain a colon, like the old format does. Old-format IDs - // always point to main dictionaries, so we force the main category upon it. - wordListCategory = UpdateHandler.MAIN_DICTIONARY_CATEGORY; - } - final String wordListLocale = results.getString(localeIndex); - final String wordListLocalFilename = results.getString(localFileNameIndex); - final String wordListRawChecksum = results.getString(rawChecksumIndex); - final int wordListStatus = results.getInt(statusIndex); - // Test the requested locale against this wordlist locale. The requested locale - // has to either match exactly or be more specific than the dictionary - a - // dictionary for "en" would match both a request for "en" or for "en_US", but a - // dictionary for "en_GB" would not match a request for "en_US". Thus if all - // three of "en" "en_US" and "en_GB" dictionaries are installed, a request for - // "en_US" would match "en" and "en_US", and a request for "en" only would only - // match the generic "en" dictionary. For more details, see the documentation - // for LocaleUtils#getMatchLevel. - final int matchLevel = LocaleUtils.getMatchLevel(wordListLocale, locale); - if (!LocaleUtils.isMatch(matchLevel)) { - // The locale of this wordlist does not match the required locale. - // Skip this wordlist and go to the next. - continue; - } - if (MetadataDbHelper.STATUS_INSTALLED == wordListStatus) { - // If the file does not exist, it has been deleted and the IME should - // already have it. Do not return it. However, this only applies if the - // word list is INSTALLED, for if it is DELETING we should return it always - // so that Android Keyboard can perform the actual deletion. - final File f = getContext().getFileStreamPath(wordListLocalFilename); - if (!f.isFile()) { - continue; - } - } else if (MetadataDbHelper.STATUS_AVAILABLE == wordListStatus) { - // The locale is the id for the main dictionary. - UpdateHandler.installIfNeverRequested(context, clientId, wordListId); - continue; - } - final WordListInfo currentBestMatch = dicts.get(wordListCategory); - if (null == currentBestMatch - || currentBestMatch.mMatchLevel < matchLevel) { - dicts.put(wordListCategory, new WordListInfo(wordListId, wordListLocale, - wordListRawChecksum, matchLevel)); - } - } while (results.moveToNext()); - } - return Collections.unmodifiableCollection(dicts.values()); - } finally { - results.close(); - } - } - - /** - * Deletes the file pointed by Uri, as returned by openAssetFile. - * - * @param uri the URI the file is for. - * @param selection ignored - * @param selectionArgs ignored - * @return the number of files deleted (0 or 1 in the current implementation) - * @see android.content.ContentProvider#delete(Uri, String, String[]) - */ - @Override - public int delete(final Uri uri, final String selection, final String[] selectionArgs) - throws UnsupportedOperationException { - final int match = matchUri(uri); - if (DICTIONARY_V1_DICT_INFO == match || DICTIONARY_V2_DATAFILE == match) { - return deleteDataFile(uri); - } - if (DICTIONARY_V2_METADATA == match) { - if (MetadataDbHelper.deleteClient(getContext(), getClientId(uri))) { - return 1; - } - return 0; - } - // Unsupported URI for delete - return 0; - } - - private int deleteDataFile(final Uri uri) { - final String wordlistId = uri.getLastPathSegment(); - final String clientId = getClientId(uri); - final ContentValues wordList = getWordlistMetadataForWordlistId(clientId, wordlistId); - if (null == wordList) { - return 0; - } - final int status = wordList.getAsInteger(MetadataDbHelper.STATUS_COLUMN); - final int version = wordList.getAsInteger(MetadataDbHelper.VERSION_COLUMN); - if (MetadataDbHelper.STATUS_DELETING == status) { - UpdateHandler.markAsDeleted(getContext(), clientId, wordlistId, version, status); - return 1; - } - if (MetadataDbHelper.STATUS_INSTALLED == status) { - final String result = uri.getQueryParameter(QUERY_PARAMETER_DELETE_RESULT); - if (QUERY_PARAMETER_FAILURE.equals(result)) { - if (DEBUG) { - Log.d(TAG, - "Dictionary is broken, attempting to retry download & installation."); - } - UpdateHandler.markAsBrokenOrRetrying(getContext(), clientId, wordlistId, version); - } - final String localFilename = - wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN); - final File f = getContext().getFileStreamPath(localFilename); - // f.delete() returns true if the file was successfully deleted, false otherwise - return f.delete() ? 1 : 0; - } - Log.e(TAG, "Attempt to delete a file whose status is " + status); - return 0; - } - - /** - * Insert data into the provider. May be either a metadata source URL or some dictionary info. - * - * @param uri the designated content URI. See sUriMatcherV{1,2} for available URIs. - * @param values the values to insert for this content uri - * @return the URI for the newly inserted item. May be null if arguments don't allow for insert - */ - @Override - public Uri insert(final Uri uri, final ContentValues values) - throws UnsupportedOperationException { - if (null == uri || null == values) return null; // Should never happen but let's be safe - PrivateLog.log("Insert, uri = " + uri.toString()); - final String clientId = getClientId(uri); - switch (matchUri(uri)) { - case DICTIONARY_V2_METADATA: - // The values should contain a valid client ID and a valid URI for the metadata. - // The client ID may not be null, nor may it be empty because the empty client ID - // is reserved for internal use. - // The metadata URI may not be null, but it may be empty if the client does not - // want the dictionary pack to update the metadata automatically. - MetadataDbHelper.updateClientInfo(getContext(), clientId, values); - break; - case DICTIONARY_V2_DICT_INFO: - try { - final WordListMetadata newDictionaryMetadata = - WordListMetadata.createFromContentValues( - MetadataDbHelper.completeWithDefaultValues(values)); - new ActionBatch.MarkPreInstalledAction(clientId, newDictionaryMetadata) - .execute(getContext()); - } catch (final BadFormatException e) { - Log.w(TAG, "Not enough information to insert this dictionary " + values, e); - } - // We just received new information about the list of dictionary for this client. - // For all intents and purposes, this is new metadata, so we should publish it - // so that any listeners (like the Settings interface for example) can update - // themselves. - UpdateHandler.publishUpdateMetadataCompleted(getContext(), true); - break; - case DICTIONARY_V1_WHOLE_LIST: - case DICTIONARY_V1_DICT_INFO: - PrivateLog.log("Attempt to insert : " + uri); - throw new UnsupportedOperationException( - "Insertion in the dictionary is not supported in this version"); - } - return uri; - } - - /** - * Updating data is not supported, and will throw an exception. - * @see android.content.ContentProvider#update(Uri, ContentValues, String, String[]) - * @see android.content.ContentProvider#insert(Uri, ContentValues) - */ - @Override - public int update(final Uri uri, final ContentValues values, final String selection, - final String[] selectionArgs) throws UnsupportedOperationException { - PrivateLog.log("Attempt to update : " + uri); - throw new UnsupportedOperationException("Updating dictionary words is not supported"); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java deleted file mode 100644 index 5ab55bc44..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java +++ /dev/null @@ -1,280 +0,0 @@ -/** - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; -import android.util.Log; -import android.widget.Toast; - -import com.android.inputmethod.latin.BinaryDictionaryFileDumper; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.LocaleUtils; - -import java.util.Locale; -import java.util.Random; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; - -/** - * Service that handles background tasks for the dictionary provider. - * - * This service provides the context for the long-running operations done by the - * dictionary provider. Those include: - * - Checking for the last update date and scheduling the next update. This runs every - * day around midnight, upon reception of the DATE_CHANGED_INTENT_ACTION broadcast. - * Every four days, it schedules an update of the metadata with the alarm manager. - * - Issuing the order to update the metadata. This runs every four days, between 0 and - * 6, upon reception of the UPDATE_NOW_INTENT_ACTION broadcast sent by the alarm manager - * as a result of the above action. - * - Handling a download that just ended. These come in two flavors: - * - Metadata is finished downloading. We should check whether there are new dictionaries - * available, and download those that we need that have new versions. - * - A dictionary file finished downloading. We should put the file ready for a client IME - * to access, and mark the current state as such. - */ -public final class DictionaryService extends Service { - private static final String TAG = DictionaryService.class.getSimpleName(); - - /** - * The package name, to use in the intent actions. - */ - private static final String PACKAGE_NAME = "com.android.inputmethod.latin"; - - /** - * The action of the date changing, used to schedule a periodic freshness check - */ - private static final String DATE_CHANGED_INTENT_ACTION = - Intent.ACTION_DATE_CHANGED; - - /** - * The action of displaying a toast to warn the user an automatic download is starting. - */ - /* package */ static final String SHOW_DOWNLOAD_TOAST_INTENT_ACTION = - PACKAGE_NAME + ".SHOW_DOWNLOAD_TOAST_INTENT_ACTION"; - - /** - * A locale argument, as a String. - */ - /* package */ static final String LOCALE_INTENT_ARGUMENT = "locale"; - - /** - * How often, in milliseconds, we want to update the metadata. This is a - * floor value; actually, it may happen several hours later, or even more. - */ - private static final long UPDATE_FREQUENCY_MILLIS = TimeUnit.DAYS.toMillis(4); - - /** - * We are waked around midnight, local time. We want to wake between midnight and 6 am, - * roughly. So use a random time between 0 and this delay. - */ - private static final int MAX_ALARM_DELAY_MILLIS = (int)TimeUnit.HOURS.toMillis(6); - - /** - * How long we consider a "very long time". If no update took place in this time, - * the content provider will trigger an update in the background. - */ - private static final long VERY_LONG_TIME_MILLIS = TimeUnit.DAYS.toMillis(14); - - /** - * After starting a download, how long we wait before considering it may be stuck. After this - * period is elapsed, if the keyboard tries to download again, then we cancel and re-register - * the request; if it's within this time, we just leave it be. - * It's important to note that we do not re-submit the request merely because the time is up. - * This is only to decide whether to cancel the old one and re-requesting when the keyboard - * fires a new request for the same data. - */ - public static final long NO_CANCEL_DOWNLOAD_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(30); - - /** - * An executor that serializes tasks given to it. - */ - private ThreadPoolExecutor mExecutor; - private static final int WORKER_THREAD_TIMEOUT_SECONDS = 15; - - @Override - public void onCreate() { - // By default, a thread pool executor does not timeout its core threads, so it will - // never kill them when there isn't any work to do any more. That would mean the service - // can never die! By creating it this way and calling allowCoreThreadTimeOut, we allow - // the single thread to time out after WORKER_THREAD_TIMEOUT_SECONDS = 15 seconds, allowing - // the process to be reclaimed by the system any time after that if it's not doing - // anything else. - // Executors#newSingleThreadExecutor creates a ThreadPoolExecutor but it returns the - // superclass ExecutorService which does not have the #allowCoreThreadTimeOut method, - // so we can't use that. - mExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */, - WORKER_THREAD_TIMEOUT_SECONDS /* keepAliveTime */, - TimeUnit.SECONDS /* unit for keepAliveTime */, - new LinkedBlockingQueue<Runnable>() /* workQueue */); - mExecutor.allowCoreThreadTimeOut(true); - } - - @Override - public void onDestroy() { - } - - @Override - public IBinder onBind(Intent intent) { - // This service cannot be bound - return null; - } - - /** - * Executes an explicit command. - * - * This is the entry point for arbitrary commands that are executed upon reception of certain - * events that should be executed on the context of this service. The supported commands are: - * - Check last update time and possibly schedule an update of the data for later. - * This is triggered every day, upon reception of the DATE_CHANGED_INTENT_ACTION broadcast. - * - Update data NOW. - * This is normally received upon trigger of the scheduled update. - * - Handle a finished download. - * This executes the actions that must be taken after a file (metadata or dictionary data - * has been downloaded (or failed to download). - * The commands that can be spun an another thread will be executed serially, in order, on - * a worker thread that is created on demand and terminates after a short while if there isn't - * any work left to do. - */ - @Override - public synchronized int onStartCommand(final Intent intent, final int flags, - final int startId) { - final DictionaryService self = this; - if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) { - final String localeString = intent.getStringExtra(LOCALE_INTENT_ARGUMENT); - if (localeString == null) { - Log.e(TAG, "Received " + intent.getAction() + " without locale; skipped"); - } else { - // This is a UI action, it can't be run in another thread - showStartDownloadingToast( - this, LocaleUtils.constructLocaleFromString(localeString)); - } - } else { - // If it's a command that does not require UI, arrange for the work to be done on a - // separate thread, so that we can return right away. The executor will spawn a thread - // if necessary, or reuse a thread that has become idle as appropriate. - // DATE_CHANGED or UPDATE_NOW are examples of commands that can be done on another - // thread. - mExecutor.submit(new Runnable() { - @Override - public void run() { - dispatchBroadcast(self, intent); - // Since calls to onStartCommand are serialized, the submissions to the executor - // are serialized. That means we are guaranteed to call the stopSelfResult() - // in the same order that we got them, so we don't need to take care of the - // order. - stopSelfResult(startId); - } - }); - } - return Service.START_REDELIVER_INTENT; - } - - static void dispatchBroadcast(final Context context, final Intent intent) { - final String action = intent.getAction(); - if (DATE_CHANGED_INTENT_ACTION.equals(action)) { - // This happens when the date of the device changes. This normally happens - // at midnight local time, but it may happen if the user changes the date - // by hand or something similar happens. - checkTimeAndMaybeSetupUpdateAlarm(context); - } else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(action)) { - // Intent to trigger an update now. - UpdateHandler.tryUpdate(context); - } else if (DictionaryPackConstants.INIT_AND_UPDATE_NOW_INTENT_ACTION.equals(action)) { - // Initialize the client Db. - final String mClientId = context.getString(R.string.dictionary_pack_client_id); - BinaryDictionaryFileDumper.initializeClientRecordHelper(context, mClientId); - - // Updates the metadata and the download the dictionaries. - UpdateHandler.tryUpdate(context); - } else { - UpdateHandler.downloadFinished(context, intent); - } - } - - /** - * Setups an alarm to check for updates if an update is due. - */ - private static void checkTimeAndMaybeSetupUpdateAlarm(final Context context) { - // Of all clients, if the one that hasn't been updated for the longest - // is still more recent than UPDATE_FREQUENCY_MILLIS, do nothing. - if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY_MILLIS)) return; - - PrivateLog.log("Date changed - registering alarm"); - AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); - - // Best effort to wake between midnight and MAX_ALARM_DELAY_MILLIS in the morning. - // It doesn't matter too much if this is very inexact. - final long now = System.currentTimeMillis(); - final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY_MILLIS); - final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION); - // Set the package name to ensure the PendingIntent is only delivered to trusted components - updateIntent.setPackage(context.getPackageName()); - int pendingIntentFlags = PendingIntent.FLAG_CANCEL_CURRENT; - if (android.os.Build.VERSION.SDK_INT >= 23) { - pendingIntentFlags |= PendingIntent.FLAG_IMMUTABLE; - } - final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, - updateIntent, pendingIntentFlags); - - // We set the alarm in the type that doesn't forcefully wake the device - // from sleep, but fires the next time the device actually wakes for any - // other reason. - if (null != alarmManager) alarmManager.set(AlarmManager.RTC, alarmTime, pendingIntent); - } - - /** - * Utility method to decide whether the last update is older than a certain time. - * - * @return true if at least `time' milliseconds have elapsed since last update, false otherwise. - */ - private static boolean isLastUpdateAtLeastThisOld(final Context context, final long time) { - final long now = System.currentTimeMillis(); - final long lastUpdate = MetadataDbHelper.getOldestUpdateTime(context); - PrivateLog.log("Last update was " + lastUpdate); - return lastUpdate + time < now; - } - - /** - * Refreshes data if it hasn't been refreshed in a very long time. - * - * This will check the last update time, and if it's been more than VERY_LONG_TIME_MILLIS, - * update metadata now - and possibly take subsequent update actions. - */ - public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) { - if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME_MILLIS)) return; - UpdateHandler.tryUpdate(context); - } - - /** - * Shows a toast informing the user that an automatic dictionary download is starting. - */ - private static void showStartDownloadingToast(final Context context, - @Nonnull final Locale locale) { - final String toastText = String.format( - context.getString(R.string.toast_downloading_suggestions), - locale.getDisplayName()); - Toast.makeText(context, toastText, Toast.LENGTH_LONG).show(); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java deleted file mode 100644 index 284032beb..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import com.android.inputmethod.latin.utils.FragmentUtils; - -import android.annotation.TargetApi; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceActivity; - -/** - * Preference screen. - */ -public final class DictionarySettingsActivity extends PreferenceActivity { - private static final String DEFAULT_FRAGMENT = DictionarySettingsFragment.class.getName(); - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public Intent getIntent() { - final Intent modIntent = new Intent(super.getIntent()); - modIntent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT); - modIntent.putExtra(EXTRA_NO_HEADERS, true); - // Important note : the original intent should contain a String extra with the key - // DictionarySettingsFragment.DICT_SETTINGS_FRAGMENT_CLIENT_ID_ARGUMENT so that the - // fragment can know who the client is. - return modIntent; - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - @Override - public boolean isValidFragment(String fragmentName) { - return FragmentUtils.isValidFragment(fragmentName); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java deleted file mode 100644 index 35b46a978..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java +++ /dev/null @@ -1,438 +0,0 @@ -/** - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import com.android.inputmethod.latin.common.LocaleUtils; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.database.Cursor; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.preference.PreferenceGroup; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; - -import com.android.inputmethod.latin.R; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Locale; -import java.util.TreeMap; - -/** - * Preference screen. - */ -public final class DictionarySettingsFragment extends PreferenceFragment - implements UpdateHandler.UpdateEventListener { - private static final String TAG = DictionarySettingsFragment.class.getSimpleName(); - - static final private String DICT_LIST_ID = "list"; - static final public String DICT_SETTINGS_FRAGMENT_CLIENT_ID_ARGUMENT = "clientId"; - - static final private int MENU_UPDATE_NOW = Menu.FIRST; - - private View mLoadingView; - private String mClientId; - private ConnectivityManager mConnectivityManager; - private MenuItem mUpdateNowMenu; - private boolean mChangedSettings; - private DictionaryListInterfaceState mDictionaryListInterfaceState = - new DictionaryListInterfaceState(); - // never null - private TreeMap<String, WordListPreference> mCurrentPreferenceMap = new TreeMap<>(); - - private final BroadcastReceiver mConnectivityChangedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - refreshNetworkState(); - } - }; - - /** - * Empty constructor for fragment generation. - */ - public DictionarySettingsFragment() { - } - - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - final View v = inflater.inflate(R.layout.loading_page, container, true); - mLoadingView = v.findViewById(R.id.loading_container); - return super.onCreateView(inflater, container, savedInstanceState); - } - - @Override - public void onActivityCreated(final Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - final Activity activity = getActivity(); - mClientId = activity.getIntent().getStringExtra(DICT_SETTINGS_FRAGMENT_CLIENT_ID_ARGUMENT); - mConnectivityManager = - (ConnectivityManager)activity.getSystemService(Context.CONNECTIVITY_SERVICE); - addPreferencesFromResource(R.xml.dictionary_settings); - refreshInterface(); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - new AsyncTask<Void, Void, String>() { - @Override - protected String doInBackground(Void... params) { - return MetadataDbHelper.getMetadataUriAsString(getActivity(), mClientId); - } - - @Override - protected void onPostExecute(String metadataUri) { - // We only add the "Refresh" button if we have a non-empty URL to refresh from. If - // the URL is empty, of course we can't refresh so it makes no sense to display - // this. - if (!TextUtils.isEmpty(metadataUri)) { - if (mUpdateNowMenu == null) { - mUpdateNowMenu = menu.add(Menu.NONE, MENU_UPDATE_NOW, 0, - R.string.check_for_updates_now); - mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - } - refreshNetworkState(); - } - } - }.execute(); - } - - @Override - public void onResume() { - super.onResume(); - mChangedSettings = false; - UpdateHandler.registerUpdateEventListener(this); - final Activity activity = getActivity(); - final IntentFilter filter = new IntentFilter(); - filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - getActivity().registerReceiver(mConnectivityChangedReceiver, filter); - refreshNetworkState(); - - new Thread("onResume") { - @Override - public void run() { - if (!MetadataDbHelper.isClientKnown(activity, mClientId)) { - Log.i(TAG, "Unknown dictionary pack client: " + mClientId - + ". Requesting info."); - final Intent unknownClientBroadcast = - new Intent(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT); - unknownClientBroadcast.putExtra( - DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA, mClientId); - activity.sendBroadcast(unknownClientBroadcast); - } - } - }.start(); - } - - @Override - public void onPause() { - super.onPause(); - final Activity activity = getActivity(); - UpdateHandler.unregisterUpdateEventListener(this); - activity.unregisterReceiver(mConnectivityChangedReceiver); - if (mChangedSettings) { - final Intent newDictBroadcast = - new Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION); - activity.sendBroadcast(newDictBroadcast); - mChangedSettings = false; - } - } - - @Override - public void downloadedMetadata(final boolean succeeded) { - stopLoadingAnimation(); - if (!succeeded) return; // If the download failed nothing changed, so no need to refresh - new Thread("refreshInterface") { - @Override - public void run() { - refreshInterface(); - } - }.start(); - } - - @Override - public void wordListDownloadFinished(final String wordListId, final boolean succeeded) { - final WordListPreference pref = findWordListPreference(wordListId); - if (null == pref) return; - // TODO: Report to the user if !succeeded - final Activity activity = getActivity(); - if (null == activity) return; - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - // We have to re-read the db in case the description has changed, and to - // find out what state it ended up if the download wasn't successful - // TODO: don't redo everything, only re-read and set this word list status - refreshInterface(); - } - }); - } - - private WordListPreference findWordListPreference(final String id) { - final PreferenceGroup prefScreen = getPreferenceScreen(); - if (null == prefScreen) { - Log.e(TAG, "Could not find the preference group"); - return null; - } - for (int i = prefScreen.getPreferenceCount() - 1; i >= 0; --i) { - final Preference pref = prefScreen.getPreference(i); - if (pref instanceof WordListPreference) { - final WordListPreference wlPref = (WordListPreference)pref; - if (id.equals(wlPref.mWordlistId)) { - return wlPref; - } - } - } - Log.e(TAG, "Could not find the preference for a word list id " + id); - return null; - } - - @Override - public void updateCycleCompleted() {} - - void refreshNetworkState() { - NetworkInfo info = mConnectivityManager.getActiveNetworkInfo(); - boolean isConnected = null == info ? false : info.isConnected(); - if (null != mUpdateNowMenu) mUpdateNowMenu.setEnabled(isConnected); - } - - void refreshInterface() { - final Activity activity = getActivity(); - if (null == activity) return; - final PreferenceGroup prefScreen = getPreferenceScreen(); - final Collection<? extends Preference> prefList = - createInstalledDictSettingsCollection(mClientId); - - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - // TODO: display this somewhere - // if (0 != lastUpdate) mUpdateNowPreference.setSummary(updateNowSummary); - refreshNetworkState(); - - removeAnyDictSettings(prefScreen); - int i = 0; - for (Preference preference : prefList) { - preference.setOrder(i++); - prefScreen.addPreference(preference); - } - } - }); - } - - private static Preference createErrorMessage(final Activity activity, final int messageResource) { - final Preference message = new Preference(activity); - message.setTitle(messageResource); - message.setEnabled(false); - return message; - } - - static void removeAnyDictSettings(final PreferenceGroup prefGroup) { - for (int i = prefGroup.getPreferenceCount() - 1; i >= 0; --i) { - prefGroup.removePreference(prefGroup.getPreference(i)); - } - } - - /** - * Creates a WordListPreference list to be added to the screen. - * - * This method only creates the preferences but does not add them. - * Thus, it can be called on another thread. - * - * @param clientId the id of the client for which we want to display the dictionary list - * @return A collection of preferences ready to add to the interface. - */ - private Collection<? extends Preference> createInstalledDictSettingsCollection( - final String clientId) { - // This will directly contact the DictionaryProvider and request the list exactly like - // any regular client would do. - // Considering the respective value of the respective constants used here for each path, - // segment, the url generated by this is of the form (assuming "clientId" as a clientId) - // content://com.android.inputmethod.latin.dictionarypack/clientId/list?procotol=2 - final Uri contentUri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) - .authority(getString(R.string.authority)) - .appendPath(clientId) - .appendPath(DICT_LIST_ID) - // Need to use version 2 to get this client's list - .appendQueryParameter(DictionaryProvider.QUERY_PARAMETER_PROTOCOL_VERSION, "2") - .build(); - final Activity activity = getActivity(); - final Cursor cursor = (null == activity) ? null - : activity.getContentResolver().query(contentUri, null, null, null, null); - - if (null == cursor) { - final ArrayList<Preference> result = new ArrayList<>(); - result.add(createErrorMessage(activity, R.string.cannot_connect_to_dict_service)); - return result; - } - try { - if (!cursor.moveToFirst()) { - final ArrayList<Preference> result = new ArrayList<>(); - result.add(createErrorMessage(activity, R.string.no_dictionaries_available)); - return result; - } - final String systemLocaleString = Locale.getDefault().toString(); - final TreeMap<String, WordListPreference> prefMap = new TreeMap<>(); - final int idIndex = cursor.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN); - final int versionIndex = cursor.getColumnIndex(MetadataDbHelper.VERSION_COLUMN); - final int localeIndex = cursor.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN); - final int descriptionIndex = cursor.getColumnIndex(MetadataDbHelper.DESCRIPTION_COLUMN); - final int statusIndex = cursor.getColumnIndex(MetadataDbHelper.STATUS_COLUMN); - final int filesizeIndex = cursor.getColumnIndex(MetadataDbHelper.FILESIZE_COLUMN); - do { - final String wordlistId = cursor.getString(idIndex); - final int version = cursor.getInt(versionIndex); - final String localeString = cursor.getString(localeIndex); - final Locale locale = new Locale(localeString); - final String description = cursor.getString(descriptionIndex); - final int status = cursor.getInt(statusIndex); - final int matchLevel = LocaleUtils.getMatchLevel(systemLocaleString, localeString); - final String matchLevelString = LocaleUtils.getMatchLevelSortedString(matchLevel); - final int filesize = cursor.getInt(filesizeIndex); - // The key is sorted in lexicographic order, according to the match level, then - // the description. - final String key = matchLevelString + "." + description + "." + wordlistId; - final WordListPreference existingPref = prefMap.get(key); - if (null == existingPref || existingPref.hasPriorityOver(status)) { - final WordListPreference oldPreference = mCurrentPreferenceMap.get(key); - final WordListPreference pref; - if (null != oldPreference - && oldPreference.mVersion == version - && oldPreference.hasStatus(status) - && oldPreference.mLocale.equals(locale)) { - // If the old preference has all the new attributes, reuse it. Ideally, - // we should reuse the old pref even if its status is different and call - // setStatus here, but setStatus calls Preference#setSummary() which - // needs to be done on the UI thread and we're not on the UI thread - // here. We could do all this work on the UI thread, but in this case - // it's probably lighter to stay on a background thread and throw this - // old preference out. - pref = oldPreference; - } else { - // Otherwise, discard it and create a new one instead. - // TODO: when the status is different from the old one, we need to - // animate the old one out before animating the new one in. - pref = new WordListPreference(activity, mDictionaryListInterfaceState, - mClientId, wordlistId, version, locale, description, status, - filesize); - } - prefMap.put(key, pref); - } - } while (cursor.moveToNext()); - mCurrentPreferenceMap = prefMap; - return prefMap.values(); - } finally { - cursor.close(); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case MENU_UPDATE_NOW: - if (View.GONE == mLoadingView.getVisibility()) { - startRefresh(); - } else { - cancelRefresh(); - } - return true; - } - return false; - } - - private void startRefresh() { - startLoadingAnimation(); - mChangedSettings = true; - UpdateHandler.registerUpdateEventListener(this); - final Activity activity = getActivity(); - new Thread("updateByHand") { - @Override - public void run() { - // We call tryUpdate(), which returns whether we could successfully start an update. - // If we couldn't, we'll never receive the end callback, so we stop the loading - // animation and return to the previous screen. - if (!UpdateHandler.tryUpdate(activity)) { - stopLoadingAnimation(); - } - } - }.start(); - } - - private void cancelRefresh() { - UpdateHandler.unregisterUpdateEventListener(this); - final Context context = getActivity(); - new Thread("cancelByHand") { - @Override - public void run() { - UpdateHandler.cancelUpdate(context, mClientId); - stopLoadingAnimation(); - } - }.start(); - } - - private void startLoadingAnimation() { - mLoadingView.setVisibility(View.VISIBLE); - getView().setVisibility(View.GONE); - // We come here when the menu element is pressed so presumably it can't be null. But - // better safe than sorry. - if (null != mUpdateNowMenu) mUpdateNowMenu.setTitle(R.string.cancel); - } - - void stopLoadingAnimation() { - final View preferenceView = getView(); - final Activity activity = getActivity(); - if (null == activity) return; - final View loadingView = mLoadingView; - final MenuItem updateNowMenu = mUpdateNowMenu; - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - loadingView.setVisibility(View.GONE); - preferenceView.setVisibility(View.VISIBLE); - loadingView.startAnimation(AnimationUtils.loadAnimation( - activity, android.R.anim.fade_out)); - preferenceView.startAnimation(AnimationUtils.loadAnimation( - activity, android.R.anim.fade_in)); - // The menu is created by the framework asynchronously after the activity, - // which means it's possible to have the activity running but the menu not - // created yet - hence the necessity for a null check here. - if (null != updateNowMenu) { - updateNowMenu.setTitle(R.string.check_for_updates_now); - } - } - }); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java b/java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java deleted file mode 100644 index 6247a15e2..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.dictionarypack; - -/** - * A simple container of download ID and download start date. - */ -public class DownloadIdAndStartDate { - public final long mId; - public final long mStartDate; - public DownloadIdAndStartDate(final long id, final long startDate) { - mId = id; - mStartDate = startDate; - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java b/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java deleted file mode 100644 index e2e9a7e44..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.dictionarypack; - -import android.app.DownloadManager; -import android.app.DownloadManager.Query; -import android.app.DownloadManager.Request; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -import java.io.FileNotFoundException; -import java.util.Arrays; - -import javax.annotation.Nullable; - -/** - * A class to help with calling DownloadManager methods. - * - * Mostly, the problem here is that most methods from DownloadManager may throw SQL exceptions if - * they can't open the database on disk. We want to avoid crashing in these cases but can't do - * much more, so this class insulates the callers from these. SQLiteException also inherit from - * RuntimeException so they are unchecked :( - * While we're at it, we also insulate callers from the cases where DownloadManager is disabled, - * and getSystemService returns null. - */ -public class DownloadManagerWrapper { - private final static String TAG = DownloadManagerWrapper.class.getSimpleName(); - private final DownloadManager mDownloadManager; - - public DownloadManagerWrapper(final Context context) { - this((DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE)); - } - - private DownloadManagerWrapper(final DownloadManager downloadManager) { - mDownloadManager = downloadManager; - } - - public void remove(final long... ids) { - try { - if (null != mDownloadManager) { - mDownloadManager.remove(ids); - } - } catch (IllegalArgumentException e) { - // This is expected to happen on boot when the device is encrypted. - } catch (SQLiteException e) { - // We couldn't remove the file from DownloadManager. Apparently, the database can't - // be opened. It may be a problem with file system corruption. In any case, there is - // not much we can do apart from avoiding crashing. - Log.e(TAG, "Can't remove files with ID " + Arrays.toString(ids) + - " from download manager", e); - } - } - - public ParcelFileDescriptor openDownloadedFile(final long fileId) throws FileNotFoundException { - try { - if (null != mDownloadManager) { - return mDownloadManager.openDownloadedFile(fileId); - } - } catch (IllegalArgumentException e) { - // This is expected to happen on boot when the device is encrypted. - } catch (SQLiteException e) { - Log.e(TAG, "Can't open downloaded file with ID " + fileId, e); - } - // We come here if mDownloadManager is null or if an exception was thrown. - throw new FileNotFoundException(); - } - - @Nullable - public Cursor query(final Query query) { - try { - if (null != mDownloadManager) { - return mDownloadManager.query(query); - } - } catch (IllegalArgumentException e) { - // This is expected to happen on boot when the device is encrypted. - } catch (SQLiteException e) { - Log.e(TAG, "Can't query the download manager", e); - } - // We come here if mDownloadManager is null or if an exception was thrown. - return null; - } - - public long enqueue(final Request request) { - try { - if (null != mDownloadManager) { - return mDownloadManager.enqueue(request); - } - } catch (IllegalArgumentException e) { - // This is expected to happen on boot when the device is encrypted. - } catch (SQLiteException e) { - Log.e(TAG, "Can't enqueue a request with the download manager", e); - } - return 0; - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java b/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java deleted file mode 100644 index 908d931a0..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.dictionarypack; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.text.Html; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import com.android.inputmethod.annotations.ExternallyReferenced; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.LocaleUtils; - -import javax.annotation.Nullable; - -/** - * This implements the dialog for asking the user whether it's okay to download dictionaries over - * a metered connection or not (e.g. their mobile data plan). - */ -public final class DownloadOverMeteredDialog extends Activity { - final public static String CLIENT_ID_KEY = "client_id"; - final public static String WORDLIST_TO_DOWNLOAD_KEY = "wordlist_to_download"; - final public static String SIZE_KEY = "size"; - final public static String LOCALE_KEY = "locale"; - private String mClientId; - private String mWordListToDownload; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Intent intent = getIntent(); - mClientId = intent.getStringExtra(CLIENT_ID_KEY); - mWordListToDownload = intent.getStringExtra(WORDLIST_TO_DOWNLOAD_KEY); - final String localeString = intent.getStringExtra(LOCALE_KEY); - final long size = intent.getIntExtra(SIZE_KEY, 0); - setContentView(R.layout.download_over_metered); - setTexts(localeString, size); - } - - private void setTexts(@Nullable final String localeString, final long size) { - final String promptFormat = getString(R.string.should_download_over_metered_prompt); - final String allowButtonFormat = getString(R.string.download_over_metered); - final String language = (null == localeString) ? "" - : LocaleUtils.constructLocaleFromString(localeString).getDisplayLanguage(); - final TextView prompt = (TextView)findViewById(R.id.download_over_metered_prompt); - prompt.setText(Html.fromHtml(String.format(promptFormat, language))); - final Button allowButton = (Button)findViewById(R.id.allow_button); - allowButton.setText(String.format(allowButtonFormat, ((float)size)/(1024*1024))); - } - - // This method is externally referenced from layout/download_over_metered.xml using onClick - // attribute of Button. - @ExternallyReferenced - @SuppressWarnings("unused") - public void onClickDeny(final View v) { - UpdateHandler.setDownloadOverMeteredSetting(this, false); - finish(); - } - - // This method is externally referenced from layout/download_over_metered.xml using onClick - // attribute of Button. - @ExternallyReferenced - @SuppressWarnings("unused") - public void onClickAllow(final View v) { - UpdateHandler.setDownloadOverMeteredSetting(this, true); - UpdateHandler.installIfNeverRequested(this, mClientId, mWordListToDownload); - finish(); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadRecord.java b/java/src/com/android/inputmethod/dictionarypack/DownloadRecord.java deleted file mode 100644 index c26299027..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/DownloadRecord.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.dictionarypack; - -import android.content.ContentValues; - -/** - * Struct class to encapsulate a client ID with content values about a download. - */ -public class DownloadRecord { - public final String mClientId; - // Only word lists have attributes, and the ContentValues should contain the same - // keys as they do for all MetadataDbHelper functions. Since only word lists have - // attributes, a null pointer here means this record represents metadata. - public final ContentValues mAttributes; - public DownloadRecord(final String clientId, final ContentValues attributes) { - mClientId = clientId; - mAttributes = attributes; - } - public boolean isMetadata() { - return null == mAttributes; - } -}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java deleted file mode 100644 index 859f1b35b..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public final class EventHandler extends BroadcastReceiver { - /** - * Receives a intent broadcast. - * - * We receive every day a broadcast indicating that date changed. - * Then we wait a random amount of time before actually registering - * the download, to avoid concentrating too many accesses around - * midnight in more populated timezones. - * We receive all broadcasts here, so this can be either the DATE_CHANGED broadcast, the - * UPDATE_NOW private broadcast that we receive when the time-randomizing alarm triggers - * for regular update or from applications that want to test the dictionary pack, or a - * broadcast from DownloadManager telling that a download has finished. - * See inside of AndroidManifest.xml to see which events are caught. - * Also @see {@link BroadcastReceiver#onReceive(Context, Intent)} - * - * @param context the context of the application. - * @param intent the intent that was broadcast. - */ - @Override - public void onReceive(final Context context, final Intent intent) { - intent.setClass(context, DictionaryService.class); - context.startService(intent); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/LogProblemReporter.java b/java/src/com/android/inputmethod/dictionarypack/LogProblemReporter.java deleted file mode 100644 index c9e128d70..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/LogProblemReporter.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.util.Log; - -/** - * A very simple problem reporter. - */ -final class LogProblemReporter implements ProblemReporter { - private final String TAG; - - public LogProblemReporter(final String tag) { - TAG = tag; - } - - @Override - public void report(final Exception e) { - Log.e(TAG, "Reporting problem", e); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/MD5Calculator.java b/java/src/com/android/inputmethod/dictionarypack/MD5Calculator.java deleted file mode 100644 index ccd054c84..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/MD5Calculator.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (C) 2012 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 com.android.inputmethod.dictionarypack; - -import java.io.InputStream; -import java.io.IOException; -import java.security.MessageDigest; - -public final class MD5Calculator { - private MD5Calculator() {} // This helper class is not instantiable - - public static String checksum(final InputStream in) throws IOException { - // This code from the Android documentation for MessageDigest. Nearly verbatim. - MessageDigest digester; - try { - digester = MessageDigest.getInstance("MD5"); - } catch (java.security.NoSuchAlgorithmException e) { - return null; // Platform does not support MD5 : can't check, so return null - } - final byte[] bytes = new byte[8192]; - int byteCount; - while ((byteCount = in.read(bytes)) > 0) { - digester.update(bytes, 0, byteCount); - } - final byte[] digest = digester.digest(); - final StringBuilder s = new StringBuilder(); - for (int i = 0; i < digest.length; ++i) { - s.append(String.format("%1$02x", digest[i])); - } - return s.toString(); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java deleted file mode 100644 index 94dd7a1c9..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java +++ /dev/null @@ -1,1155 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteOpenHelper; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.DebugLogUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.TreeMap; - -import javax.annotation.Nullable; - -/** - * Various helper functions for the state database - */ -public class MetadataDbHelper extends SQLiteOpenHelper { - private static final String TAG = MetadataDbHelper.class.getSimpleName(); - - // This was the initial release version of the database. It should never be - // changed going forward. - private static final int METADATA_DATABASE_INITIAL_VERSION = 3; - // This is the first released version of the database that implements CLIENTID. It is - // used to identify the versions for upgrades. This should never change going forward. - private static final int METADATA_DATABASE_VERSION_WITH_CLIENTID = 6; - // The current database version. - // This MUST be increased every time the dictionary pack metadata URL changes. - private static final int CURRENT_METADATA_DATABASE_VERSION = 16; - - private final static long NOT_A_DOWNLOAD_ID = -1; - - // The number of retries allowed when attempting to download a broken dictionary. - public static final int DICTIONARY_RETRY_THRESHOLD = 2; - - public static final String METADATA_TABLE_NAME = "pendingUpdates"; - static final String CLIENT_TABLE_NAME = "clients"; - public static final String PENDINGID_COLUMN = "pendingid"; // Download Manager ID - public static final String TYPE_COLUMN = "type"; - public static final String STATUS_COLUMN = "status"; - public static final String LOCALE_COLUMN = "locale"; - public static final String WORDLISTID_COLUMN = "id"; - public static final String DESCRIPTION_COLUMN = "description"; - public static final String LOCAL_FILENAME_COLUMN = "filename"; - public static final String REMOTE_FILENAME_COLUMN = "url"; - public static final String DATE_COLUMN = "date"; - public static final String CHECKSUM_COLUMN = "checksum"; - public static final String FILESIZE_COLUMN = "filesize"; - public static final String VERSION_COLUMN = "version"; - public static final String FORMATVERSION_COLUMN = "formatversion"; - public static final String FLAGS_COLUMN = "flags"; - public static final String RAW_CHECKSUM_COLUMN = "rawChecksum"; - public static final String RETRY_COUNT_COLUMN = "remainingRetries"; - public static final int COLUMN_COUNT = 15; - - private static final String CLIENT_CLIENT_ID_COLUMN = "clientid"; - private static final String CLIENT_METADATA_URI_COLUMN = "uri"; - private static final String CLIENT_METADATA_ADDITIONAL_ID_COLUMN = "additionalid"; - private static final String CLIENT_LAST_UPDATE_DATE_COLUMN = "lastupdate"; - private static final String CLIENT_PENDINGID_COLUMN = "pendingid"; // Download Manager ID - - public static final String METADATA_DATABASE_NAME_STEM = "pendingUpdates"; - public static final String METADATA_UPDATE_DESCRIPTION = "metadata"; - - public static final String DICTIONARIES_ASSETS_PATH = "dictionaries"; - - // Statuses, for storing in the STATUS_COLUMN - // IMPORTANT: The following are used as index arrays in ../WordListPreference - // Do not change their values without updating the matched code. - // Unknown status: this should never happen. - public static final int STATUS_UNKNOWN = 0; - // Available: this word list is available, but it is not downloaded (not downloading), because - // it is set not to be used. - public static final int STATUS_AVAILABLE = 1; - // Downloading: this word list is being downloaded. - public static final int STATUS_DOWNLOADING = 2; - // Installed: this word list is installed and usable. - public static final int STATUS_INSTALLED = 3; - // Disabled: this word list is installed, but has been disabled by the user. - public static final int STATUS_DISABLED = 4; - // Deleting: the user marked this word list to be deleted, but it has not been yet because - // Latin IME is not up yet. - public static final int STATUS_DELETING = 5; - // Retry: dictionary got corrupted, so an attempt must be done to download & install it again. - public static final int STATUS_RETRYING = 6; - - // Types, for storing in the TYPE_COLUMN - // This is metadata about what is available. - public static final int TYPE_METADATA = 1; - // This is a bulk file. It should replace older files. - public static final int TYPE_BULK = 2; - // This is an incremental update, expected to be small, and meaningless on its own. - public static final int TYPE_UPDATE = 3; - - private static final String METADATA_TABLE_CREATE = - "CREATE TABLE " + METADATA_TABLE_NAME + " (" - + PENDINGID_COLUMN + " INTEGER, " - + TYPE_COLUMN + " INTEGER, " - + STATUS_COLUMN + " INTEGER, " - + WORDLISTID_COLUMN + " TEXT, " - + LOCALE_COLUMN + " TEXT, " - + DESCRIPTION_COLUMN + " TEXT, " - + LOCAL_FILENAME_COLUMN + " TEXT, " - + REMOTE_FILENAME_COLUMN + " TEXT, " - + DATE_COLUMN + " INTEGER, " - + CHECKSUM_COLUMN + " TEXT, " - + FILESIZE_COLUMN + " INTEGER, " - + VERSION_COLUMN + " INTEGER," - + FORMATVERSION_COLUMN + " INTEGER, " - + FLAGS_COLUMN + " INTEGER, " - + RAW_CHECKSUM_COLUMN + " TEXT," - + RETRY_COUNT_COLUMN + " INTEGER, " - + "PRIMARY KEY (" + WORDLISTID_COLUMN + "," + VERSION_COLUMN + "));"; - private static final String METADATA_CREATE_CLIENT_TABLE = - "CREATE TABLE IF NOT EXISTS " + CLIENT_TABLE_NAME + " (" - + CLIENT_CLIENT_ID_COLUMN + " TEXT, " - + CLIENT_METADATA_URI_COLUMN + " TEXT, " - + CLIENT_METADATA_ADDITIONAL_ID_COLUMN + " TEXT, " - + CLIENT_LAST_UPDATE_DATE_COLUMN + " INTEGER NOT NULL DEFAULT 0, " - + CLIENT_PENDINGID_COLUMN + " INTEGER, " - + FLAGS_COLUMN + " INTEGER, " - + "PRIMARY KEY (" + CLIENT_CLIENT_ID_COLUMN + "));"; - - // List of all metadata table columns. - static final String[] METADATA_TABLE_COLUMNS = { PENDINGID_COLUMN, TYPE_COLUMN, - STATUS_COLUMN, WORDLISTID_COLUMN, LOCALE_COLUMN, DESCRIPTION_COLUMN, - LOCAL_FILENAME_COLUMN, REMOTE_FILENAME_COLUMN, DATE_COLUMN, CHECKSUM_COLUMN, - FILESIZE_COLUMN, VERSION_COLUMN, FORMATVERSION_COLUMN, FLAGS_COLUMN, - RAW_CHECKSUM_COLUMN, RETRY_COUNT_COLUMN }; - // List of all client table columns. - static final String[] CLIENT_TABLE_COLUMNS = { CLIENT_CLIENT_ID_COLUMN, - CLIENT_METADATA_URI_COLUMN, CLIENT_PENDINGID_COLUMN, FLAGS_COLUMN }; - // List of public columns returned to clients. Everything that is not in this list is - // private and implementation-dependent. - static final String[] DICTIONARIES_LIST_PUBLIC_COLUMNS = { STATUS_COLUMN, WORDLISTID_COLUMN, - LOCALE_COLUMN, DESCRIPTION_COLUMN, DATE_COLUMN, FILESIZE_COLUMN, VERSION_COLUMN }; - - // This class exhibits a singleton-like behavior by client ID, so it is getInstance'd - // and has a private c'tor. - private static TreeMap<String, MetadataDbHelper> sInstanceMap = null; - public static synchronized MetadataDbHelper getInstance(final Context context, - final String clientIdOrNull) { - // As a backward compatibility feature, null can be passed here to retrieve the "default" - // database. Before multi-client support, the dictionary packed used only one database - // and would not be able to handle several dictionary sets. Passing null here retrieves - // this legacy database. New clients should make sure to always pass a client ID so as - // to avoid conflicts. - final String clientId = null != clientIdOrNull ? clientIdOrNull : ""; - if (null == sInstanceMap) sInstanceMap = new TreeMap<>(); - MetadataDbHelper helper = sInstanceMap.get(clientId); - if (null == helper) { - helper = new MetadataDbHelper(context, clientId); - sInstanceMap.put(clientId, helper); - } - return helper; - } - private MetadataDbHelper(final Context context, final String clientId) { - super(context, - METADATA_DATABASE_NAME_STEM + (TextUtils.isEmpty(clientId) ? "" : "." + clientId), - null, CURRENT_METADATA_DATABASE_VERSION); - mContext = context; - mClientId = clientId; - } - - private final Context mContext; - private final String mClientId; - - /** - * Get the database itself. This always returns the same object for any client ID. If the - * client ID is null, a default database is returned for backward compatibility. Don't - * pass null for new calls. - * - * @param context the context to create the database from. This is ignored after the first call. - * @param clientId the client id to retrieve the database of. null for default (deprecated) - * @return the database. - */ - public static SQLiteDatabase getDb(final Context context, final String clientId) { - return getInstance(context, clientId).getWritableDatabase(); - } - - private void createClientTable(final SQLiteDatabase db) { - // The clients table only exists in the primary db, the one that has an empty client id - if (!TextUtils.isEmpty(mClientId)) return; - db.execSQL(METADATA_CREATE_CLIENT_TABLE); - final String defaultMetadataUri = mContext.getString(R.string.default_metadata_uri); - if (!TextUtils.isEmpty(defaultMetadataUri)) { - final ContentValues defaultMetadataValues = new ContentValues(); - defaultMetadataValues.put(CLIENT_CLIENT_ID_COLUMN, ""); - defaultMetadataValues.put(CLIENT_METADATA_URI_COLUMN, defaultMetadataUri); - defaultMetadataValues.put(CLIENT_PENDINGID_COLUMN, UpdateHandler.NOT_AN_ID); - db.insert(CLIENT_TABLE_NAME, null, defaultMetadataValues); - } - } - - /** - * Create the table and populate it with the resources found inside the apk. - * - * @see SQLiteOpenHelper#onCreate(SQLiteDatabase) - * - * @param db the database to create and populate. - */ - @Override - public void onCreate(final SQLiteDatabase db) { - db.execSQL(METADATA_TABLE_CREATE); - createClientTable(db); - } - - private static void addRawChecksumColumnUnlessPresent(final SQLiteDatabase db) { - try { - db.execSQL("SELECT " + RAW_CHECKSUM_COLUMN + " FROM " - + METADATA_TABLE_NAME + " LIMIT 0;"); - } catch (SQLiteException e) { - Log.i(TAG, "No " + RAW_CHECKSUM_COLUMN + " column : creating it"); - db.execSQL("ALTER TABLE " + METADATA_TABLE_NAME + " ADD COLUMN " - + RAW_CHECKSUM_COLUMN + " TEXT;"); - } - } - - private static void addRetryCountColumnUnlessPresent(final SQLiteDatabase db) { - try { - db.execSQL("SELECT " + RETRY_COUNT_COLUMN + " FROM " - + METADATA_TABLE_NAME + " LIMIT 0;"); - } catch (SQLiteException e) { - Log.i(TAG, "No " + RETRY_COUNT_COLUMN + " column : creating it"); - db.execSQL("ALTER TABLE " + METADATA_TABLE_NAME + " ADD COLUMN " - + RETRY_COUNT_COLUMN + " INTEGER DEFAULT " + DICTIONARY_RETRY_THRESHOLD + ";"); - } - } - - /** - * Upgrade the database. Upgrade from version 3 is supported. - * Version 3 has a DB named METADATA_DATABASE_NAME_STEM containing a table METADATA_TABLE_NAME. - * Version 6 and above has a DB named METADATA_DATABASE_NAME_STEM containing a - * table CLIENT_TABLE_NAME, and for each client a table called METADATA_TABLE_STEM + "." + the - * name of the client and contains a table METADATA_TABLE_NAME. - * For schemas, see the above create statements. The schemas have never changed so far. - * - * This method is called by the framework. See {@link SQLiteOpenHelper#onUpgrade} - * @param db The database we are upgrading - * @param oldVersion The old database version (the one on the disk) - * @param newVersion The new database version as supplied to the constructor of SQLiteOpenHelper - */ - @Override - public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { - if (METADATA_DATABASE_INITIAL_VERSION == oldVersion - && METADATA_DATABASE_VERSION_WITH_CLIENTID <= newVersion - && CURRENT_METADATA_DATABASE_VERSION >= newVersion) { - // Upgrade from version METADATA_DATABASE_INITIAL_VERSION to version - // METADATA_DATABASE_VERSION_WITH_CLIENT_ID - // Only the default database should contain the client table, so we test for mClientId. - if (TextUtils.isEmpty(mClientId)) { - // Anyway in version 3 only the default table existed so the emptiness - // test should always be true, but better check to be sure. - createClientTable(db); - } - } else if (METADATA_DATABASE_VERSION_WITH_CLIENTID < newVersion - && CURRENT_METADATA_DATABASE_VERSION >= newVersion) { - // Here we drop the client table, so that all clients send us their information again. - // The client table contains the URL to hit to update the available dictionaries list, - // but the info about the dictionaries themselves is stored in the table called - // METADATA_TABLE_NAME and we want to keep it, so we only drop the client table. - db.execSQL("DROP TABLE IF EXISTS " + CLIENT_TABLE_NAME); - // Only the default database should contain the client table, so we test for mClientId. - if (TextUtils.isEmpty(mClientId)) { - createClientTable(db); - } - } else { - // If we're not in the above case, either we are upgrading from an earlier versionCode - // and we should wipe the database, or we are handling a version we never heard about - // (can only be a bug) so it's safer to wipe the database. - db.execSQL("DROP TABLE IF EXISTS " + METADATA_TABLE_NAME); - db.execSQL("DROP TABLE IF EXISTS " + CLIENT_TABLE_NAME); - onCreate(db); - } - // A rawChecksum column that did not exist in the previous versions was added that - // corresponds to the md5 checksum of the file after decompression/decryption. This is to - // strengthen the system against corrupted dictionary files. - // The most secure way to upgrade a database is to just test for the column presence, and - // add it if it's not there. - addRawChecksumColumnUnlessPresent(db); - - // A retry count column that did not exist in the previous versions was added that - // corresponds to the number of download & installation attempts that have been made - // in order to strengthen the system recovery from corrupted dictionary files. - // The most secure way to upgrade a database is to just test for the column presence, and - // add it if it's not there. - addRetryCountColumnUnlessPresent(db); - } - - /** - * Downgrade the database. This drops and recreates the table in all cases. - */ - @Override - public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { - // No matter what the numerical values of oldVersion and newVersion are, we know this - // is a downgrade (newVersion < oldVersion). There is no way to know what the future - // databases will look like, but we know it's extremely likely that it's okay to just - // drop the tables and start from scratch. Hence, we ignore the versions and just wipe - // everything we want to use. - if (oldVersion <= newVersion) { - Log.e(TAG, "onDowngrade database but new version is higher? " + oldVersion + " <= " - + newVersion); - } - db.execSQL("DROP TABLE IF EXISTS " + METADATA_TABLE_NAME); - db.execSQL("DROP TABLE IF EXISTS " + CLIENT_TABLE_NAME); - onCreate(db); - } - - /** - * Given a client ID, returns whether this client exists. - * - * @param context a context to open the database - * @param clientId the client ID to check - * @return true if the client is known, false otherwise - */ - public static boolean isClientKnown(final Context context, final String clientId) { - // If the client is known, they'll have a non-null metadata URI. An empty string is - // allowed as a metadata URI, if the client doesn't want any updates to happen. - return null != getMetadataUriAsString(context, clientId); - } - - private static final MetadataUriGetter sMetadataUriGetter = new MetadataUriGetter(); - - /** - * Returns the metadata URI as a string. - * - * If the client is not known, this will return null. If it is known, it will return - * the URI as a string. Note that the empty string is a valid value. - * - * @param context a context instance to open the database on - * @param clientId the ID of the client we want the metadata URI of - * @return the string representation of the URI - */ - public static String getMetadataUriAsString(final Context context, final String clientId) { - SQLiteDatabase defaultDb = MetadataDbHelper.getDb(context, null); - final Cursor cursor = defaultDb.query(MetadataDbHelper.CLIENT_TABLE_NAME, - new String[] { MetadataDbHelper.CLIENT_METADATA_URI_COLUMN }, - MetadataDbHelper.CLIENT_CLIENT_ID_COLUMN + " = ?", new String[] { clientId }, - null, null, null, null); - try { - if (!cursor.moveToFirst()) return null; - return sMetadataUriGetter.getUri(context, cursor.getString(0)); - } finally { - cursor.close(); - } - } - - /** - * Update the last metadata update time for all clients using a particular URI. - * - * This method searches for all clients using a particular URI and updates the last - * update time for this client. - * The current time is used as the latest update time. This saved date will be what - * is returned henceforth by {@link #getLastUpdateDateForClient(Context, String)}, - * until this method is called again. - * - * @param context a context instance to open the database on - * @param uri the metadata URI we just downloaded - */ - public static void saveLastUpdateTimeOfUri(final Context context, final String uri) { - PrivateLog.log("Save last update time of URI : " + uri + " " + System.currentTimeMillis()); - final ContentValues values = new ContentValues(); - values.put(CLIENT_LAST_UPDATE_DATE_COLUMN, System.currentTimeMillis()); - final SQLiteDatabase defaultDb = getDb(context, null); - final Cursor cursor = MetadataDbHelper.queryClientIds(context); - if (null == cursor) return; - try { - if (!cursor.moveToFirst()) return; - do { - final String clientId = cursor.getString(0); - final String metadataUri = - MetadataDbHelper.getMetadataUriAsString(context, clientId); - if (metadataUri.equals(uri)) { - defaultDb.update(CLIENT_TABLE_NAME, values, - CLIENT_CLIENT_ID_COLUMN + " = ?", new String[] { clientId }); - } - } while (cursor.moveToNext()); - } finally { - cursor.close(); - } - } - - /** - * Retrieves the last date at which we updated the metadata for this client. - * - * The returned date is in milliseconds from the EPOCH; this is the same unit as - * returned by {@link System#currentTimeMillis()}. - * - * @param context a context instance to open the database on - * @param clientId the client ID to get the latest update date of - * @return the last date at which this client was updated, as a long. - */ - public static long getLastUpdateDateForClient(final Context context, final String clientId) { - SQLiteDatabase defaultDb = getDb(context, null); - final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME, - new String[] { CLIENT_LAST_UPDATE_DATE_COLUMN }, - CLIENT_CLIENT_ID_COLUMN + " = ?", - new String[] { null == clientId ? "" : clientId }, - null, null, null, null); - try { - if (!cursor.moveToFirst()) return 0; - return cursor.getLong(0); // Only one column, return it - } finally { - cursor.close(); - } - } - - /** - * Get the metadata download ID for a metadata URI. - * - * This will retrieve the download ID for the metadata file that has the passed URI. - * If this URI is not being downloaded right now, it will return NOT_AN_ID. - * - * @param context a context instance to open the database on - * @param uri the URI to retrieve the metadata download ID of - * @return the download id and start date, or null if the URL is not known - */ - public static DownloadIdAndStartDate getMetadataDownloadIdAndStartDateForURI( - final Context context, final String uri) { - SQLiteDatabase defaultDb = getDb(context, null); - final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME, - new String[] { CLIENT_PENDINGID_COLUMN, CLIENT_LAST_UPDATE_DATE_COLUMN }, - CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri }, - null, null, null, null); - try { - if (!cursor.moveToFirst()) return null; - return new DownloadIdAndStartDate(cursor.getInt(0), cursor.getLong(1)); - } finally { - cursor.close(); - } - } - - public static long getOldestUpdateTime(final Context context) { - SQLiteDatabase defaultDb = getDb(context, null); - final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME, - new String[] { CLIENT_LAST_UPDATE_DATE_COLUMN }, - null, null, null, null, null); - try { - if (!cursor.moveToFirst()) return 0; - final int columnIndex = 0; // Only one column queried - // Initialize the earliestTime to the largest possible value. - long earliestTime = Long.MAX_VALUE; // Almost 300 million years in the future - do { - final long thisTime = cursor.getLong(columnIndex); - earliestTime = Math.min(thisTime, earliestTime); - } while (cursor.moveToNext()); - return earliestTime; - } finally { - cursor.close(); - } - } - - /** - * Helper method to make content values to write into the database. - * @return content values with all the arguments put with the right column names. - */ - public static ContentValues makeContentValues(final int pendingId, final int type, - final int status, final String wordlistId, final String locale, - final String description, final String filename, final String url, final long date, - final String rawChecksum, final String checksum, final int retryCount, - final long filesize, final int version, final int formatVersion) { - final ContentValues result = new ContentValues(COLUMN_COUNT); - result.put(PENDINGID_COLUMN, pendingId); - result.put(TYPE_COLUMN, type); - result.put(WORDLISTID_COLUMN, wordlistId); - result.put(STATUS_COLUMN, status); - result.put(LOCALE_COLUMN, locale); - result.put(DESCRIPTION_COLUMN, description); - result.put(LOCAL_FILENAME_COLUMN, filename); - result.put(REMOTE_FILENAME_COLUMN, url); - result.put(DATE_COLUMN, date); - result.put(RAW_CHECKSUM_COLUMN, rawChecksum); - result.put(RETRY_COUNT_COLUMN, retryCount); - result.put(CHECKSUM_COLUMN, checksum); - result.put(FILESIZE_COLUMN, filesize); - result.put(VERSION_COLUMN, version); - result.put(FORMATVERSION_COLUMN, formatVersion); - result.put(FLAGS_COLUMN, 0); - return result; - } - - /** - * Helper method to fill in an incomplete ContentValues with default values. - * A wordlist ID and a locale are required, otherwise BadFormatException is thrown. - * @return the same object that was passed in, completed with default values. - */ - public static ContentValues completeWithDefaultValues(final ContentValues result) - throws BadFormatException { - if (null == result.get(WORDLISTID_COLUMN) || null == result.get(LOCALE_COLUMN)) { - throw new BadFormatException(); - } - // 0 for the pending id, because there is none - if (null == result.get(PENDINGID_COLUMN)) result.put(PENDINGID_COLUMN, 0); - // This is a binary blob of a dictionary - if (null == result.get(TYPE_COLUMN)) result.put(TYPE_COLUMN, TYPE_BULK); - // This word list is unknown, but it's present, else we wouldn't be here, so INSTALLED - if (null == result.get(STATUS_COLUMN)) result.put(STATUS_COLUMN, STATUS_INSTALLED); - // No description unless specified, because we can't guess it - if (null == result.get(DESCRIPTION_COLUMN)) result.put(DESCRIPTION_COLUMN, ""); - // File name - this is an asset, so it works as an already deleted file. - // hence, we need to supply a non-existent file name. Anything will - // do as long as it returns false when tested with File#exist(), and - // the empty string does not, so it's set to "_". - if (null == result.get(LOCAL_FILENAME_COLUMN)) result.put(LOCAL_FILENAME_COLUMN, "_"); - // No remote file name : this can't be downloaded. Unless specified. - if (null == result.get(REMOTE_FILENAME_COLUMN)) result.put(REMOTE_FILENAME_COLUMN, ""); - // 0 for the update date : 1970/1/1. Unless specified. - if (null == result.get(DATE_COLUMN)) result.put(DATE_COLUMN, 0); - // Raw checksum unknown unless specified - if (null == result.get(RAW_CHECKSUM_COLUMN)) result.put(RAW_CHECKSUM_COLUMN, ""); - // Retry column 0 unless specified - if (null == result.get(RETRY_COUNT_COLUMN)) result.put(RETRY_COUNT_COLUMN, - DICTIONARY_RETRY_THRESHOLD); - // Checksum unknown unless specified - if (null == result.get(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, ""); - // No filesize unless specified - if (null == result.get(FILESIZE_COLUMN)) result.put(FILESIZE_COLUMN, 0); - // Smallest possible version unless specified - if (null == result.get(VERSION_COLUMN)) result.put(VERSION_COLUMN, 1); - // Assume current format unless specified - if (null == result.get(FORMATVERSION_COLUMN)) - result.put(FORMATVERSION_COLUMN, UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION); - // No flags unless specified - if (null == result.get(FLAGS_COLUMN)) result.put(FLAGS_COLUMN, 0); - return result; - } - - /** - * Reads a column in a Cursor as a String and stores it in a ContentValues object. - * @param result the ContentValues object to store the result in. - * @param cursor the Cursor to read the column from. - * @param columnId the column ID to read. - */ - private static void putStringResult(ContentValues result, Cursor cursor, String columnId) { - result.put(columnId, cursor.getString(cursor.getColumnIndex(columnId))); - } - - /** - * Reads a column in a Cursor as an int and stores it in a ContentValues object. - * @param result the ContentValues object to store the result in. - * @param cursor the Cursor to read the column from. - * @param columnId the column ID to read. - */ - private static void putIntResult(ContentValues result, Cursor cursor, String columnId) { - result.put(columnId, cursor.getInt(cursor.getColumnIndex(columnId))); - } - - private static ContentValues getFirstLineAsContentValues(final Cursor cursor) { - final ContentValues result; - if (cursor.moveToFirst()) { - result = new ContentValues(COLUMN_COUNT); - putIntResult(result, cursor, PENDINGID_COLUMN); - putIntResult(result, cursor, TYPE_COLUMN); - putIntResult(result, cursor, STATUS_COLUMN); - putStringResult(result, cursor, WORDLISTID_COLUMN); - putStringResult(result, cursor, LOCALE_COLUMN); - putStringResult(result, cursor, DESCRIPTION_COLUMN); - putStringResult(result, cursor, LOCAL_FILENAME_COLUMN); - putStringResult(result, cursor, REMOTE_FILENAME_COLUMN); - putIntResult(result, cursor, DATE_COLUMN); - putStringResult(result, cursor, RAW_CHECKSUM_COLUMN); - putStringResult(result, cursor, CHECKSUM_COLUMN); - putIntResult(result, cursor, RETRY_COUNT_COLUMN); - putIntResult(result, cursor, FILESIZE_COLUMN); - putIntResult(result, cursor, VERSION_COLUMN); - putIntResult(result, cursor, FORMATVERSION_COLUMN); - putIntResult(result, cursor, FLAGS_COLUMN); - if (cursor.moveToNext()) { - // TODO: print the second level of the stack to the log so that we know - // in which code path the error happened - Log.e(TAG, "Several SQL results when we expected only one!"); - } - } else { - result = null; - } - return result; - } - - /** - * Gets the info about as specific download, indexed by its DownloadManager ID. - * @param db the database to get the information from. - * @param id the DownloadManager id. - * @return metadata about this download. This returns all columns in the database. - */ - public static ContentValues getContentValuesByPendingId(final SQLiteDatabase db, - final long id) { - final Cursor cursor = db.query(METADATA_TABLE_NAME, - METADATA_TABLE_COLUMNS, - PENDINGID_COLUMN + "= ?", - new String[] { Long.toString(id) }, - null, null, null); - if (null == cursor) { - return null; - } - try { - // There should never be more than one result. If because of some bug there are, - // returning only one result is the right thing to do, because we couldn't handle - // several anyway and we should still handle one. - return getFirstLineAsContentValues(cursor); - } finally { - cursor.close(); - } - } - - /** - * Gets the info about an installed OR deleting word list with a specified id. - * - * Basically, this is the word list that we want to return to Android Keyboard when - * it asks for a specific id. - * - * @param db the database to get the information from. - * @param id the word list ID. - * @return the metadata about this word list. - */ - public static ContentValues getInstalledOrDeletingWordListContentValuesByWordListId( - final SQLiteDatabase db, final String id) { - final Cursor cursor = db.query(METADATA_TABLE_NAME, - METADATA_TABLE_COLUMNS, - WORDLISTID_COLUMN + "=? AND (" + STATUS_COLUMN + "=? OR " + STATUS_COLUMN + "=?)", - new String[] { id, Integer.toString(STATUS_INSTALLED), - Integer.toString(STATUS_DELETING) }, - null, null, null); - if (null == cursor) { - return null; - } - try { - // There should only be one result, but if there are several, we can't tell which - // is the best, so we just return the first one. - return getFirstLineAsContentValues(cursor); - } finally { - cursor.close(); - } - } - - /** - * Given a specific download ID, return records for all pending downloads across all clients. - * - * If several clients use the same metadata URL, we know to only download it once, and - * dispatch the update process across all relevant clients when the download ends. This means - * several clients may share a single download ID if they share a metadata URI. - * The dispatching is done in - * {@link UpdateHandler#downloadFinished(Context, android.content.Intent)}, which - * finds out about the list of relevant clients by calling this method. - * - * @param context a context instance to open the databases - * @param downloadId the download ID to query about - * @return the list of records. Never null, but may be empty. - */ - public static ArrayList<DownloadRecord> getDownloadRecordsForDownloadId(final Context context, - final long downloadId) { - final SQLiteDatabase defaultDb = getDb(context, ""); - final ArrayList<DownloadRecord> results = new ArrayList<>(); - final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME, CLIENT_TABLE_COLUMNS, - null, null, null, null, null); - try { - if (!cursor.moveToFirst()) return results; - final int clientIdIndex = cursor.getColumnIndex(CLIENT_CLIENT_ID_COLUMN); - final int pendingIdColumn = cursor.getColumnIndex(CLIENT_PENDINGID_COLUMN); - do { - final long pendingId = cursor.getInt(pendingIdColumn); - final String clientId = cursor.getString(clientIdIndex); - if (pendingId == downloadId) { - results.add(new DownloadRecord(clientId, null)); - } - final ContentValues valuesForThisClient = - getContentValuesByPendingId(getDb(context, clientId), downloadId); - if (null != valuesForThisClient) { - results.add(new DownloadRecord(clientId, valuesForThisClient)); - } - } while (cursor.moveToNext()); - } finally { - cursor.close(); - } - return results; - } - - /** - * Gets the info about a specific word list. - * - * @param db the database to get the information from. - * @param id the word list ID. - * @param version the word list version. - * @return the metadata about this word list. - */ - @Nullable - public static ContentValues getContentValuesByWordListId(final SQLiteDatabase db, - final String id, final int version) { - final Cursor cursor = db.query(METADATA_TABLE_NAME, - METADATA_TABLE_COLUMNS, - WORDLISTID_COLUMN + "= ? AND " + VERSION_COLUMN + "= ? AND " - + FORMATVERSION_COLUMN + "<= ?", - new String[] - { id, - Integer.toString(version), - Integer.toString(UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION) - }, - null /* groupBy */, - null /* having */, - FORMATVERSION_COLUMN + " DESC"/* orderBy */); - if (null == cursor) { - return null; - } - try { - // This is a lookup by primary key, so there can't be more than one result. - return getFirstLineAsContentValues(cursor); - } finally { - cursor.close(); - } - } - - /** - * Gets the info about the latest word list with an id. - * - * @param db the database to get the information from. - * @param id the word list ID. - * @return the metadata about the word list with this id and the latest version number. - */ - public static ContentValues getContentValuesOfLatestAvailableWordlistById( - final SQLiteDatabase db, final String id) { - final Cursor cursor = db.query(METADATA_TABLE_NAME, - METADATA_TABLE_COLUMNS, - WORDLISTID_COLUMN + "= ?", - new String[] { id }, null, null, VERSION_COLUMN + " DESC", "1"); - if (null == cursor) { - return null; - } - try { - // Return the first result from the list of results. - return getFirstLineAsContentValues(cursor); - } finally { - cursor.close(); - } - } - - /** - * Gets the current metadata about INSTALLED, AVAILABLE or DELETING dictionaries. - * - * This odd method is tailored to the needs of - * DictionaryProvider#getDictionaryWordListsForContentUri, which needs the word list if - * it is: - * - INSTALLED: this should be returned to LatinIME if the file is still inside the dictionary - * pack, so that it can be copied. If the file is not there, it's been copied already and should - * not be returned, so getDictionaryWordListsForContentUri takes care of this. - * - DELETING: this should be returned to LatinIME so that it can actually delete the file. - * - AVAILABLE: this should not be returned, but should be checked for auto-installation. - * - * @param context the context for getting the database. - * @param clientId the client id for retrieving the database. null for default (deprecated) - * @return a cursor with metadata about usable dictionaries. - */ - public static Cursor queryInstalledOrDeletingOrAvailableDictionaryMetadata( - final Context context, final String clientId) { - // If clientId is null, we get the defaut DB (see #getInstance() for more about this) - final Cursor results = getDb(context, clientId).query(METADATA_TABLE_NAME, - METADATA_TABLE_COLUMNS, - STATUS_COLUMN + " = ? OR " + STATUS_COLUMN + " = ? OR " + STATUS_COLUMN + " = ?", - new String[] { Integer.toString(STATUS_INSTALLED), - Integer.toString(STATUS_DELETING), - Integer.toString(STATUS_AVAILABLE) }, - null, null, LOCALE_COLUMN); - return results; - } - - /** - * Gets the current metadata about all dictionaries. - * - * This will retrieve the metadata about all dictionaries, including - * older files, or files not yet downloaded. - * - * @param context the context for getting the database. - * @param clientId the client id for retrieving the database. null for default (deprecated) - * @return a cursor with metadata about usable dictionaries. - */ - public static Cursor queryCurrentMetadata(final Context context, final String clientId) { - // If clientId is null, we get the defaut DB (see #getInstance() for more about this) - final Cursor results = getDb(context, clientId).query(METADATA_TABLE_NAME, - METADATA_TABLE_COLUMNS, null, null, null, null, LOCALE_COLUMN); - return results; - } - - /** - * Gets the list of all dictionaries known to the dictionary provider, with only public columns. - * - * This will retrieve information about all known dictionaries, and their status. As such, - * it will also return information about dictionaries on the server that have not been - * downloaded yet, but may be requested. - * This only returns public columns. It does not populate internal columns in the returned - * cursor. - * The value returned by this method is intended to be good to be returned directly for a - * request of the list of dictionaries by a client. - * - * @param context the context to read the database from. - * @param clientId the client id for retrieving the database. null for default (deprecated) - * @return a cursor that lists all available dictionaries and their metadata. - */ - public static Cursor queryDictionaries(final Context context, final String clientId) { - // If clientId is null, we get the defaut DB (see #getInstance() for more about this) - final Cursor results = getDb(context, clientId).query(METADATA_TABLE_NAME, - DICTIONARIES_LIST_PUBLIC_COLUMNS, - // Filter out empty locales so as not to return auxiliary data, like a - // data line for downloading metadata: - MetadataDbHelper.LOCALE_COLUMN + " != ?", new String[] {""}, - // TODO: Reinstate the following code for bulk, then implement partial updates - /* MetadataDbHelper.TYPE_COLUMN + " = ?", - new String[] { Integer.toString(MetadataDbHelper.TYPE_BULK) }, */ - null, null, LOCALE_COLUMN); - return results; - } - - /** - * Deletes all data associated with a client. - * - * @param context the context for opening the database - * @param clientId the ID of the client to delete. - * @return true if the client was successfully deleted, false otherwise. - */ - public static boolean deleteClient(final Context context, final String clientId) { - // Remove all metadata associated with this client - final SQLiteDatabase db = getDb(context, clientId); - db.execSQL("DROP TABLE IF EXISTS " + METADATA_TABLE_NAME); - db.execSQL(METADATA_TABLE_CREATE); - // Remove this client's entry in the clients table - final SQLiteDatabase defaultDb = getDb(context, ""); - if (0 == defaultDb.delete(CLIENT_TABLE_NAME, - CLIENT_CLIENT_ID_COLUMN + " = ?", new String[] { clientId })) { - return false; - } - return true; - } - - /** - * Updates information relative to a specific client. - * - * Updatable information includes the metadata URI and the additional ID column. It may be - * expanded in the future. - * The passed values must include a client ID in the key CLIENT_CLIENT_ID_COLUMN, and it must - * be equal to the string passed as an argument for clientId. It may not be empty. - * The passed values must also include a non-null metadata URI in the - * CLIENT_METADATA_URI_COLUMN column, as well as a non-null additional ID in the - * CLIENT_METADATA_ADDITIONAL_ID_COLUMN. Both these strings may be empty. - * If any of the above is not complied with, this function returns without updating data. - * - * @param context the context, to open the database - * @param clientId the ID of the client to update - * @param values the values to update. Must conform to the protocol (see above) - */ - public static void updateClientInfo(final Context context, final String clientId, - final ContentValues values) { - // Validity check the content values - final String valuesClientId = values.getAsString(CLIENT_CLIENT_ID_COLUMN); - final String valuesMetadataUri = values.getAsString(CLIENT_METADATA_URI_COLUMN); - final String valuesMetadataAdditionalId = - values.getAsString(CLIENT_METADATA_ADDITIONAL_ID_COLUMN); - // Empty string is a valid client ID, but external apps may not configure it, so disallow - // both null and empty string. - // Empty string is a valid metadata URI if the client does not want updates, so allow - // empty string but disallow null. - // Empty string is a valid additional ID so allow empty string but disallow null. - if (TextUtils.isEmpty(valuesClientId) || null == valuesMetadataUri - || null == valuesMetadataAdditionalId) { - // We need all these columns to be filled in - DebugLogUtils.l("Missing parameter for updateClientInfo"); - return; - } - if (!clientId.equals(valuesClientId)) { - // Mismatch! The client violates the protocol. - DebugLogUtils.l("Received an updateClientInfo request for ", clientId, - " but the values " + "contain a different ID : ", valuesClientId); - return; - } - // Default value for a pending ID is NOT_AN_ID - values.put(CLIENT_PENDINGID_COLUMN, UpdateHandler.NOT_AN_ID); - final SQLiteDatabase defaultDb = getDb(context, ""); - if (-1 == defaultDb.insert(CLIENT_TABLE_NAME, null, values)) { - defaultDb.update(CLIENT_TABLE_NAME, values, - CLIENT_CLIENT_ID_COLUMN + " = ?", new String[] { clientId }); - } - } - - /** - * Retrieves the list of existing client IDs. - * @param context the context to open the database - * @return a cursor containing only one column, and one client ID per line. - */ - public static Cursor queryClientIds(final Context context) { - return getDb(context, null).query(CLIENT_TABLE_NAME, - new String[] { CLIENT_CLIENT_ID_COLUMN }, null, null, null, null, null); - } - - /** - * Register a download ID for a specific metadata URI. - * - * This method should be called when a download for a metadata URI is starting. It will - * search for all clients using this metadata URI and will register for each of them - * the download ID into the database for later retrieval by - * {@link #getDownloadRecordsForDownloadId(Context, long)}. - * - * @param context a context for opening databases - * @param uri the metadata URI - * @param downloadId the download ID - */ - public static void registerMetadataDownloadId(final Context context, final String uri, - final long downloadId) { - final ContentValues values = new ContentValues(); - values.put(CLIENT_PENDINGID_COLUMN, downloadId); - values.put(CLIENT_LAST_UPDATE_DATE_COLUMN, System.currentTimeMillis()); - final SQLiteDatabase defaultDb = getDb(context, ""); - final Cursor cursor = MetadataDbHelper.queryClientIds(context); - if (null == cursor) return; - try { - if (!cursor.moveToFirst()) return; - do { - final String clientId = cursor.getString(0); - final String metadataUri = - MetadataDbHelper.getMetadataUriAsString(context, clientId); - if (metadataUri.equals(uri)) { - defaultDb.update(CLIENT_TABLE_NAME, values, - CLIENT_CLIENT_ID_COLUMN + " = ?", new String[] { clientId }); - } - } while (cursor.moveToNext()); - } finally { - cursor.close(); - } - } - - /** - * Marks a downloading entry as having successfully downloaded and being installed. - * - * The metadata database contains information about ongoing processes, typically ongoing - * downloads. This marks such an entry as having finished and having installed successfully, - * so it becomes INSTALLED. - * - * @param db the metadata database. - * @param r content values about the entry to mark as processed. - */ - public static void markEntryAsFinishedDownloadingAndInstalled(final SQLiteDatabase db, - final ContentValues r) { - switch (r.getAsInteger(TYPE_COLUMN)) { - case TYPE_BULK: - DebugLogUtils.l("Ended processing a wordlist"); - // Updating a bulk word list is a three-step operation: - // - Add the new entry to the table - // - Remove the old entry from the table - // - Erase the old file - // We start by gathering the names of the files we should delete. - final List<String> filenames = new LinkedList<>(); - final Cursor c = db.query(METADATA_TABLE_NAME, - new String[] { LOCAL_FILENAME_COLUMN }, - LOCALE_COLUMN + " = ? AND " + - WORDLISTID_COLUMN + " = ? AND " + STATUS_COLUMN + " = ?", - new String[] { r.getAsString(LOCALE_COLUMN), - r.getAsString(WORDLISTID_COLUMN), - Integer.toString(STATUS_INSTALLED) }, - null, null, null); - try { - if (c.moveToFirst()) { - // There should never be more than one file, but if there are, it's a bug - // and we should remove them all. I think it might happen if the power of - // the phone is suddenly cut during an update. - final int filenameIndex = c.getColumnIndex(LOCAL_FILENAME_COLUMN); - do { - DebugLogUtils.l("Setting for removal", c.getString(filenameIndex)); - filenames.add(c.getString(filenameIndex)); - } while (c.moveToNext()); - } - } finally { - c.close(); - } - r.put(STATUS_COLUMN, STATUS_INSTALLED); - db.beginTransactionNonExclusive(); - // Delete all old entries. There should never be any stalled entries, but if - // there are, this deletes them. - db.delete(METADATA_TABLE_NAME, - WORDLISTID_COLUMN + " = ?", - new String[] { r.getAsString(WORDLISTID_COLUMN) }); - db.insert(METADATA_TABLE_NAME, null, r); - db.setTransactionSuccessful(); - db.endTransaction(); - for (String filename : filenames) { - try { - final File f = new File(filename); - f.delete(); - } catch (SecurityException e) { - // No permissions to delete. Um. Can't do anything. - } // I don't think anything else can be thrown - } - break; - default: - // Unknown type: do nothing. - break; - } - } - - /** - * Removes a downloading entry from the database. - * - * This is invoked when a download fails. Either we tried to download, but - * we received a permanent failure and we should remove it, or we got manually - * cancelled and we should leave it at that. - * - * @param db the metadata database. - * @param id the DownloadManager id of the file. - */ - public static void deleteDownloadingEntry(final SQLiteDatabase db, final long id) { - db.delete(METADATA_TABLE_NAME, PENDINGID_COLUMN + " = ? AND " + STATUS_COLUMN + " = ?", - new String[] { Long.toString(id), Integer.toString(STATUS_DOWNLOADING) }); - } - - /** - * Forcefully removes an entry from the database. - * - * This is invoked when a file is broken. The file has been downloaded, but Android - * Keyboard is telling us it could not open it. - * - * @param db the metadata database. - * @param id the id of the word list. - * @param version the version of the word list. - */ - public static void deleteEntry(final SQLiteDatabase db, final String id, final int version) { - db.delete(METADATA_TABLE_NAME, WORDLISTID_COLUMN + " = ? AND " + VERSION_COLUMN + " = ?", - new String[] { id, Integer.toString(version) }); - } - - /** - * Internal method that sets the current status of an entry of the database. - * - * @param db the metadata database. - * @param id the id of the word list. - * @param version the version of the word list. - * @param status the status to set the word list to. - * @param downloadId an optional download id to write, or NOT_A_DOWNLOAD_ID - */ - private static void markEntryAs(final SQLiteDatabase db, final String id, - final int version, final int status, final long downloadId) { - final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, id, version); - values.put(STATUS_COLUMN, status); - if (NOT_A_DOWNLOAD_ID != downloadId) { - values.put(MetadataDbHelper.PENDINGID_COLUMN, downloadId); - } - db.update(METADATA_TABLE_NAME, values, - WORDLISTID_COLUMN + " = ? AND " + VERSION_COLUMN + " = ?", - new String[] { id, Integer.toString(version) }); - } - - /** - * Writes the status column for the wordlist with this id as enabled. Typically this - * means the word list is currently disabled and we want to set its status to INSTALLED. - * - * @param db the metadata database. - * @param id the id of the word list. - * @param version the version of the word list. - */ - public static void markEntryAsEnabled(final SQLiteDatabase db, final String id, - final int version) { - markEntryAs(db, id, version, STATUS_INSTALLED, NOT_A_DOWNLOAD_ID); - } - - /** - * Writes the status column for the wordlist with this id as disabled. Typically this - * means the word list is currently installed and we want to set its status to DISABLED. - * - * @param db the metadata database. - * @param id the id of the word list. - * @param version the version of the word list. - */ - public static void markEntryAsDisabled(final SQLiteDatabase db, final String id, - final int version) { - markEntryAs(db, id, version, STATUS_DISABLED, NOT_A_DOWNLOAD_ID); - } - - /** - * Writes the status column for the wordlist with this id as available. This happens for - * example when a word list has been deleted but can be downloaded again. - * - * @param db the metadata database. - * @param id the id of the word list. - * @param version the version of the word list. - */ - public static void markEntryAsAvailable(final SQLiteDatabase db, final String id, - final int version) { - markEntryAs(db, id, version, STATUS_AVAILABLE, NOT_A_DOWNLOAD_ID); - } - - /** - * Writes the designated word list as downloadable, alongside with its download id. - * - * @param db the metadata database. - * @param id the id of the word list. - * @param version the version of the word list. - * @param downloadId the download id. - */ - public static void markEntryAsDownloading(final SQLiteDatabase db, final String id, - final int version, final long downloadId) { - markEntryAs(db, id, version, STATUS_DOWNLOADING, downloadId); - } - - /** - * Writes the designated word list as deleting. - * - * @param db the metadata database. - * @param id the id of the word list. - * @param version the version of the word list. - */ - public static void markEntryAsDeleting(final SQLiteDatabase db, final String id, - final int version) { - markEntryAs(db, id, version, STATUS_DELETING, NOT_A_DOWNLOAD_ID); - } - - /** - * Checks retry counts and marks the word list as retrying if retry is possible. - * - * @param db the metadata database. - * @param id the id of the word list. - * @param version the version of the word list. - * @return {@code true} if the retry is possible. - */ - public static boolean maybeMarkEntryAsRetrying(final SQLiteDatabase db, final String id, - final int version) { - final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, id, version); - int retryCount = values.getAsInteger(MetadataDbHelper.RETRY_COUNT_COLUMN); - if (retryCount > 1) { - values.put(STATUS_COLUMN, STATUS_RETRYING); - values.put(RETRY_COUNT_COLUMN, retryCount - 1); - db.update(METADATA_TABLE_NAME, values, - WORDLISTID_COLUMN + " = ? AND " + VERSION_COLUMN + " = ?", - new String[] { id, Integer.toString(version) }); - return true; - } - return false; - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java deleted file mode 100644 index e5d632fbe..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.util.Log; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.Collections; -import java.util.ArrayList; -import java.util.List; - -/** - * Helper class to easy up manipulation of dictionary pack metadata. - */ -public class MetadataHandler { - - public static final String TAG = MetadataHandler.class.getSimpleName(); - - // The canonical file name for metadata. This is not the name of a real file on the - // device, but a symbolic name used in the database and in metadata handling. It is never - // tested against, only used for human-readability as the file name for the metadata. - public static final String METADATA_FILENAME = "metadata.json"; - - /** - * Reads the data from the cursor and store it in metadata objects. - * @param results the cursor to read data from. - * @return the constructed list of wordlist metadata. - */ - private static List<WordListMetadata> makeMetadataObject(final Cursor results) { - final ArrayList<WordListMetadata> buildingMetadata = new ArrayList<>(); - if (null != results && results.moveToFirst()) { - final int localeColumn = results.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN); - final int typeColumn = results.getColumnIndex(MetadataDbHelper.TYPE_COLUMN); - final int descriptionColumn = - results.getColumnIndex(MetadataDbHelper.DESCRIPTION_COLUMN); - final int idIndex = results.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN); - final int updateIndex = results.getColumnIndex(MetadataDbHelper.DATE_COLUMN); - final int fileSizeIndex = results.getColumnIndex(MetadataDbHelper.FILESIZE_COLUMN); - final int rawChecksumIndex = - results.getColumnIndex(MetadataDbHelper.RAW_CHECKSUM_COLUMN); - final int checksumIndex = results.getColumnIndex(MetadataDbHelper.CHECKSUM_COLUMN); - final int retryCountIndex = results.getColumnIndex(MetadataDbHelper.RETRY_COUNT_COLUMN); - final int localFilenameIndex = - results.getColumnIndex(MetadataDbHelper.LOCAL_FILENAME_COLUMN); - final int remoteFilenameIndex = - results.getColumnIndex(MetadataDbHelper.REMOTE_FILENAME_COLUMN); - final int versionIndex = results.getColumnIndex(MetadataDbHelper.VERSION_COLUMN); - final int formatVersionIndex = - results.getColumnIndex(MetadataDbHelper.FORMATVERSION_COLUMN); - do { - buildingMetadata.add(new WordListMetadata(results.getString(idIndex), - results.getInt(typeColumn), - results.getString(descriptionColumn), - results.getLong(updateIndex), - results.getLong(fileSizeIndex), - results.getString(rawChecksumIndex), - results.getString(checksumIndex), - results.getInt(retryCountIndex), - results.getString(localFilenameIndex), - results.getString(remoteFilenameIndex), - results.getInt(versionIndex), - results.getInt(formatVersionIndex), - 0, results.getString(localeColumn))); - } while (results.moveToNext()); - } - return Collections.unmodifiableList(buildingMetadata); - } - - /** - * Gets the whole metadata, for installed and not installed dictionaries. - * @param context The context to open files over. - * @param clientId the client id for retrieving the database. null for default (deprecated) - * @return The current metadata. - */ - public static List<WordListMetadata> getCurrentMetadata(final Context context, - final String clientId) { - // If clientId is null, we get a cursor on the default database (see - // MetadataDbHelper#getInstance() for more on this) - final Cursor results = MetadataDbHelper.queryCurrentMetadata(context, clientId); - // If null, we should return makeMetadataObject(null), so we go through. - try { - return makeMetadataObject(results); - } finally { - if (null != results) { - results.close(); - } - } - } - - /** - * Gets the metadata, for a specific dictionary. - * - * @param context The context to open files over. - * @param clientId the client id for retrieving the database. null for default (deprecated). - * @param wordListId the word list ID. - * @param version the word list version. - * @return the current metaData - */ - public static WordListMetadata getCurrentMetadataForWordList(final Context context, - final String clientId, final String wordListId, final int version) { - final ContentValues contentValues = MetadataDbHelper.getContentValuesByWordListId( - MetadataDbHelper.getDb(context, clientId), wordListId, version); - if (contentValues == null) { - // TODO: Figure out why this would happen. - // Check if this happens when the metadata gets updated in the background. - Log.e(TAG, String.format( "Unable to find the current metadata for wordlist " - + "(clientId=%s, wordListId=%s, version=%d) on the database", - clientId, wordListId, version)); - return null; - } - return WordListMetadata.createFromContentValues(contentValues); - } - - /** - * Read metadata from a stream. - * @param input The stream to read from. - * @return The read metadata. - * @throws IOException if the input stream cannot be read - * @throws BadFormatException if the stream is not in a known format - */ - public static List<WordListMetadata> readMetadata(final InputStreamReader input) - throws IOException, BadFormatException { - return MetadataParser.parseMetadata(input); - } - - /** - * Finds a single WordListMetadata inside a whole metadata chunk. - * - * Searches through the whole passed metadata for the first WordListMetadata associated - * with the passed ID. If several metadata chunks with the same id are found, it will - * always return the one with the bigger FormatVersion that is less or equal than the - * maximum supported format version (as listed in UpdateHandler). - * This will NEVER return the metadata with a FormatVersion bigger than what is supported, - * even if it is the only word list with this ID. - * - * @param metadata the metadata to search into. - * @param id the word list ID of the metadata to find. - * @return the associated metadata, or null if not found. - */ - public static WordListMetadata findWordListById(final List<WordListMetadata> metadata, - final String id) { - WordListMetadata bestWordList = null; - int bestFormatVersion = Integer.MIN_VALUE; // To be sure we can't be inadvertently smaller - for (WordListMetadata wordList : metadata) { - if (id.equals(wordList.mId) - && wordList.mFormatVersion <= UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION - && wordList.mFormatVersion > bestFormatVersion) { - bestWordList = wordList; - bestFormatVersion = wordList.mFormatVersion; - } - } - // If we didn't find any match we'll return null. - return bestWordList; - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java b/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java deleted file mode 100644 index 2b67ae9ff..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.text.TextUtils; -import android.util.JsonReader; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.TreeMap; - -/** - * Helper class containing functions to parse the dictionary metadata. - */ -public class MetadataParser { - - // Name of the fields in the JSON-formatted file. - private static final String ID_FIELD_NAME = MetadataDbHelper.WORDLISTID_COLUMN; - private static final String LOCALE_FIELD_NAME = "locale"; - private static final String DESCRIPTION_FIELD_NAME = MetadataDbHelper.DESCRIPTION_COLUMN; - private static final String UPDATE_FIELD_NAME = "update"; - private static final String FILESIZE_FIELD_NAME = MetadataDbHelper.FILESIZE_COLUMN; - private static final String RAW_CHECKSUM_FIELD_NAME = MetadataDbHelper.RAW_CHECKSUM_COLUMN; - private static final String CHECKSUM_FIELD_NAME = MetadataDbHelper.CHECKSUM_COLUMN; - private static final String REMOTE_FILENAME_FIELD_NAME = - MetadataDbHelper.REMOTE_FILENAME_COLUMN; - private static final String VERSION_FIELD_NAME = MetadataDbHelper.VERSION_COLUMN; - private static final String FORMATVERSION_FIELD_NAME = MetadataDbHelper.FORMATVERSION_COLUMN; - - /** - * Parse one JSON-formatted word list metadata. - * @param reader the reader containing the data. - * @return a WordListMetadata object from the parsed data. - * @throws IOException if the underlying reader throws IOException during reading. - */ - private static WordListMetadata parseOneWordList(final JsonReader reader) - throws IOException, BadFormatException { - final TreeMap<String, String> arguments = new TreeMap<>(); - reader.beginObject(); - while (reader.hasNext()) { - final String name = reader.nextName(); - if (!TextUtils.isEmpty(name)) { - arguments.put(name, reader.nextString()); - } - } - reader.endObject(); - if (TextUtils.isEmpty(arguments.get(ID_FIELD_NAME)) - || TextUtils.isEmpty(arguments.get(LOCALE_FIELD_NAME)) - || TextUtils.isEmpty(arguments.get(DESCRIPTION_FIELD_NAME)) - || TextUtils.isEmpty(arguments.get(UPDATE_FIELD_NAME)) - || TextUtils.isEmpty(arguments.get(FILESIZE_FIELD_NAME)) - || TextUtils.isEmpty(arguments.get(CHECKSUM_FIELD_NAME)) - || TextUtils.isEmpty(arguments.get(REMOTE_FILENAME_FIELD_NAME)) - || TextUtils.isEmpty(arguments.get(VERSION_FIELD_NAME)) - || TextUtils.isEmpty(arguments.get(FORMATVERSION_FIELD_NAME))) { - throw new BadFormatException(arguments.toString()); - } - // TODO: need to find out whether it's bulk or update - // The null argument is the local file name, which is not known at this time and will - // be decided later. - return new WordListMetadata( - arguments.get(ID_FIELD_NAME), - MetadataDbHelper.TYPE_BULK, - arguments.get(DESCRIPTION_FIELD_NAME), - Long.parseLong(arguments.get(UPDATE_FIELD_NAME)), - Long.parseLong(arguments.get(FILESIZE_FIELD_NAME)), - arguments.get(RAW_CHECKSUM_FIELD_NAME), - arguments.get(CHECKSUM_FIELD_NAME), - MetadataDbHelper.DICTIONARY_RETRY_THRESHOLD /* retryCount */, - null, - arguments.get(REMOTE_FILENAME_FIELD_NAME), - Integer.parseInt(arguments.get(VERSION_FIELD_NAME)), - Integer.parseInt(arguments.get(FORMATVERSION_FIELD_NAME)), - 0, arguments.get(LOCALE_FIELD_NAME)); - } - - /** - * Parses metadata in the JSON format. - * @param input a stream reader expected to contain JSON formatted metadata. - * @return dictionary metadata, as an array of WordListMetadata objects. - * @throws IOException if the underlying reader throws IOException during reading. - * @throws BadFormatException if the data was not in the expected format. - */ - public static List<WordListMetadata> parseMetadata(final InputStreamReader input) - throws IOException, BadFormatException { - JsonReader reader = new JsonReader(input); - final ArrayList<WordListMetadata> readInfo = new ArrayList<>(); - reader.beginArray(); - while (reader.hasNext()) { - final WordListMetadata thisMetadata = parseOneWordList(reader); - if (!TextUtils.isEmpty(thisMetadata.mLocale)) - readInfo.add(thisMetadata); - } - return Collections.unmodifiableList(readInfo); - } - -} diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataUriGetter.java b/java/src/com/android/inputmethod/dictionarypack/MetadataUriGetter.java deleted file mode 100644 index 512d426aa..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/MetadataUriGetter.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.dictionarypack; - -import android.content.Context; - -/** - * Helper to get the metadata URI from its base URI. - */ -@SuppressWarnings("unused") -public class MetadataUriGetter { - public static String getUri(final Context context, final String baseUri) { - return baseUri; - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/PrivateLog.java b/java/src/com/android/inputmethod/dictionarypack/PrivateLog.java deleted file mode 100644 index bb64721d5..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/PrivateLog.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.content.ContentValues; -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -/** - * Class to keep long-term log. This is inactive in production, and is only for debug purposes. - */ -public class PrivateLog { - - public static final boolean DEBUG = DictionaryProvider.DEBUG; - - private static final String LOG_DATABASE_NAME = "log"; - private static final String LOG_TABLE_NAME = "log"; - private static final int LOG_DATABASE_VERSION = 1; - - private static final String COLUMN_DATE = "date"; - private static final String COLUMN_EVENT = "event"; - - private static final String LOG_TABLE_CREATE = "CREATE TABLE " + LOG_TABLE_NAME + " (" - + COLUMN_DATE + " TEXT," - + COLUMN_EVENT + " TEXT);"; - - static final SimpleDateFormat sDateFormat = new SimpleDateFormat( - "yyyy/MM/dd HH:mm:ss", Locale.ROOT); - - private static PrivateLog sInstance = new PrivateLog(); - private static DebugHelper sDebugHelper = null; - - private PrivateLog() { - } - - public static synchronized PrivateLog getInstance(final Context context) { - if (!DEBUG) return sInstance; - synchronized(PrivateLog.class) { - if (sDebugHelper == null) { - sDebugHelper = new DebugHelper(context); - } - return sInstance; - } - } - - static class DebugHelper extends SQLiteOpenHelper { - - DebugHelper(final Context context) { - super(context, LOG_DATABASE_NAME, null, LOG_DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - if (!DEBUG) return; - db.execSQL(LOG_TABLE_CREATE); - insert(db, "Created table"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (!DEBUG) return; - // Remove all data. - db.execSQL("DROP TABLE IF EXISTS " + LOG_TABLE_NAME); - onCreate(db); - insert(db, "Upgrade finished"); - } - - static void insert(SQLiteDatabase db, String event) { - if (!DEBUG) return; - final ContentValues c = new ContentValues(2); - c.put(COLUMN_DATE, sDateFormat.format(new Date(System.currentTimeMillis()))); - c.put(COLUMN_EVENT, event); - db.insert(LOG_TABLE_NAME, null, c); - } - - } - - public static void log(String event) { - if (!DEBUG) return; - final SQLiteDatabase l = sDebugHelper.getWritableDatabase(); - DebugHelper.insert(l, event); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/ProblemReporter.java b/java/src/com/android/inputmethod/dictionarypack/ProblemReporter.java deleted file mode 100644 index 632819aa3..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/ProblemReporter.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -/** - * A simple interface to report problems. - */ -public interface ProblemReporter { - public void report(Exception e); -} diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java deleted file mode 100644 index bdea3e919..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java +++ /dev/null @@ -1,1083 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.app.DownloadManager; -import android.app.DownloadManager.Query; -import android.app.DownloadManager.Request; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.net.ConnectivityManager; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.makedict.FormatSpec; -import com.android.inputmethod.latin.utils.ApplicationUtils; -import com.android.inputmethod.latin.utils.DebugLogUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.nio.channels.FileChannel; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; - -import javax.annotation.Nullable; - -/** - * Handler for the update process. - * - * This class is in charge of coordinating the update process for the various dictionaries - * stored in the dictionary pack. - */ -public final class UpdateHandler { - static final String TAG = "DictionaryProvider:" + UpdateHandler.class.getSimpleName(); - private static final boolean DEBUG = DictionaryProvider.DEBUG; - - // Used to prevent trying to read the id of the downloaded file before it is written - static final Object sSharedIdProtector = new Object(); - - // Value used to mean this is not a real DownloadManager downloaded file id - // DownloadManager uses as an ID numbers returned out of an AUTOINCREMENT column - // in SQLite, so it should never return anything < 0. - public static final int NOT_AN_ID = -1; - public static final int MAXIMUM_SUPPORTED_FORMAT_VERSION = - FormatSpec.MAXIMUM_SUPPORTED_STATIC_VERSION; - - // Arbitrary. Probably good if it's a power of 2, and a couple thousand bytes long. - private static final int FILE_COPY_BUFFER_SIZE = 8192; - - // Table fixed values for metadata / downloads - final static String METADATA_NAME = "metadata"; - final static int METADATA_TYPE = 0; - final static int WORDLIST_TYPE = 1; - - // Suffix for generated dictionary files - private static final String DICT_FILE_SUFFIX = ".dict"; - // Name of the category for the main dictionary - public static final String MAIN_DICTIONARY_CATEGORY = "main"; - - public static final String TEMP_DICT_FILE_SUB = "___"; - - // The id for the "dictionary available" notification. - static final int DICT_AVAILABLE_NOTIFICATION_ID = 1; - - /** - * An interface for UIs or services that want to know when something happened. - * - * This is chiefly used by the dictionary manager UI. - */ - public interface UpdateEventListener { - void downloadedMetadata(boolean succeeded); - void wordListDownloadFinished(String wordListId, boolean succeeded); - void updateCycleCompleted(); - } - - /** - * The list of currently registered listeners. - */ - private static List<UpdateEventListener> sUpdateEventListeners - = Collections.synchronizedList(new LinkedList<UpdateEventListener>()); - - /** - * Register a new listener to be notified of updates. - * - * Don't forget to call unregisterUpdateEventListener when done with it, or - * it will leak the register. - */ - public static void registerUpdateEventListener(final UpdateEventListener listener) { - sUpdateEventListeners.add(listener); - } - - /** - * Unregister a previously registered listener. - */ - public static void unregisterUpdateEventListener(final UpdateEventListener listener) { - sUpdateEventListeners.remove(listener); - } - - private static final String DOWNLOAD_OVER_METERED_SETTING_PREFS_KEY = "downloadOverMetered"; - - /** - * Write the DownloadManager ID of the currently downloading metadata to permanent storage. - * - * @param context to open shared prefs - * @param uri the uri of the metadata - * @param downloadId the id returned by DownloadManager - */ - private static void writeMetadataDownloadId(final Context context, final String uri, - final long downloadId) { - MetadataDbHelper.registerMetadataDownloadId(context, uri, downloadId); - } - - public static final int DOWNLOAD_OVER_METERED_SETTING_UNKNOWN = 0; - public static final int DOWNLOAD_OVER_METERED_ALLOWED = 1; - public static final int DOWNLOAD_OVER_METERED_DISALLOWED = 2; - - /** - * Sets the setting that tells us whether we may download over a metered connection. - */ - public static void setDownloadOverMeteredSetting(final Context context, - final boolean shouldDownloadOverMetered) { - final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context); - final SharedPreferences.Editor editor = prefs.edit(); - editor.putInt(DOWNLOAD_OVER_METERED_SETTING_PREFS_KEY, shouldDownloadOverMetered - ? DOWNLOAD_OVER_METERED_ALLOWED : DOWNLOAD_OVER_METERED_DISALLOWED); - editor.apply(); - } - - /** - * Gets the setting that tells us whether we may download over a metered connection. - * - * This returns one of the constants above. - */ - public static int getDownloadOverMeteredSetting(final Context context) { - final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context); - final int setting = prefs.getInt(DOWNLOAD_OVER_METERED_SETTING_PREFS_KEY, - DOWNLOAD_OVER_METERED_SETTING_UNKNOWN); - return setting; - } - - /** - * Download latest metadata from the server through DownloadManager for all known clients - * @param context The context for retrieving resources - * @return true if an update successfully started, false otherwise. - */ - public static boolean tryUpdate(final Context context) { - // TODO: loop through all clients instead of only doing the default one. - final TreeSet<String> uris = new TreeSet<>(); - final Cursor cursor = MetadataDbHelper.queryClientIds(context); - if (null == cursor) return false; - try { - if (!cursor.moveToFirst()) return false; - do { - final String clientId = cursor.getString(0); - final String metadataUri = - MetadataDbHelper.getMetadataUriAsString(context, clientId); - PrivateLog.log("Update for clientId " + DebugLogUtils.s(clientId)); - DebugLogUtils.l("Update for clientId", clientId, " which uses URI ", metadataUri); - uris.add(metadataUri); - } while (cursor.moveToNext()); - } finally { - cursor.close(); - } - boolean started = false; - for (final String metadataUri : uris) { - if (!TextUtils.isEmpty(metadataUri)) { - // If the metadata URI is empty, that means we should never update it at all. - // It should not be possible to come here with a null metadata URI, because - // it should have been rejected at the time of client registration; if there - // is a bug and it happens anyway, doing nothing is the right thing to do. - // For more information, {@see DictionaryProvider#insert(Uri, ContentValues)}. - updateClientsWithMetadataUri(context, metadataUri); - started = true; - } - } - return started; - } - - /** - * Download latest metadata from the server through DownloadManager for all relevant clients - * - * @param context The context for retrieving resources - * @param metadataUri The client to update - */ - private static void updateClientsWithMetadataUri( - final Context context, final String metadataUri) { - Log.i(TAG, "updateClientsWithMetadataUri() : MetadataUri = " + metadataUri); - // Adding a disambiguator to circumvent a bug in older versions of DownloadManager. - // DownloadManager also stupidly cuts the extension to replace with its own that it - // gets from the content-type. We need to circumvent this. - final String disambiguator = "#" + System.currentTimeMillis() - + ApplicationUtils.getVersionName(context) + ".json"; - final Request metadataRequest = new Request(Uri.parse(metadataUri + disambiguator)); - DebugLogUtils.l("Request =", metadataRequest); - - final Resources res = context.getResources(); - metadataRequest.setAllowedNetworkTypes(Request.NETWORK_WIFI | Request.NETWORK_MOBILE); - metadataRequest.setTitle(res.getString(R.string.download_description)); - // Do not show the notification when downloading the metadata. - metadataRequest.setNotificationVisibility(Request.VISIBILITY_HIDDEN); - metadataRequest.setVisibleInDownloadsUi( - res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI)); - - final DownloadManagerWrapper manager = new DownloadManagerWrapper(context); - if (maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager, - DictionaryService.NO_CANCEL_DOWNLOAD_PERIOD_MILLIS)) { - // We already have a recent download in progress. Don't register a new download. - return; - } - final long downloadId; - synchronized (sSharedIdProtector) { - downloadId = manager.enqueue(metadataRequest); - DebugLogUtils.l("Metadata download requested with id", downloadId); - // If there is still a download in progress, it's been there for a while and - // there is probably something wrong with download manager. It's best to just - // overwrite the id and request it again. If the old one happens to finish - // anyway, we don't know about its ID any more, so the downloadFinished - // method will ignore it. - writeMetadataDownloadId(context, metadataUri, downloadId); - } - Log.i(TAG, "updateClientsWithMetadataUri() : DownloadId = " + downloadId); - } - - /** - * Cancels downloading a file if there is one for this URI and it's too long. - * - * If we are not currently downloading the file at this URI, this is a no-op. - * - * @param context the context to open the database on - * @param metadataUri the URI to cancel - * @param manager an wrapped instance of DownloadManager - * @param graceTime if there was a download started less than this many milliseconds, don't - * cancel and return true - * @return whether the download is still active - */ - private static boolean maybeCancelUpdateAndReturnIfStillRunning(final Context context, - final String metadataUri, final DownloadManagerWrapper manager, final long graceTime) { - synchronized (sSharedIdProtector) { - final DownloadIdAndStartDate metadataDownloadIdAndStartDate = - MetadataDbHelper.getMetadataDownloadIdAndStartDateForURI(context, metadataUri); - if (null == metadataDownloadIdAndStartDate) return false; - if (NOT_AN_ID == metadataDownloadIdAndStartDate.mId) return false; - if (metadataDownloadIdAndStartDate.mStartDate + graceTime - > System.currentTimeMillis()) { - return true; - } - manager.remove(metadataDownloadIdAndStartDate.mId); - writeMetadataDownloadId(context, metadataUri, NOT_AN_ID); - } - // Consider a cancellation as a failure. As such, inform listeners that the download - // has failed. - for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { - listener.downloadedMetadata(false); - } - return false; - } - - /** - * Cancels a pending update for this client, if there is one. - * - * If we are not currently updating metadata for this client, this is a no-op. This is a helper - * method that gets the download manager service and the metadata URI for this client. - * - * @param context the context, to get an instance of DownloadManager - * @param clientId the ID of the client we want to cancel the update of - */ - public static void cancelUpdate(final Context context, final String clientId) { - final DownloadManagerWrapper manager = new DownloadManagerWrapper(context); - final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId); - maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager, 0 /* graceTime */); - } - - /** - * Registers a download request and flags it as downloading in the metadata table. - * - * This is a helper method that exists to avoid race conditions where DownloadManager might - * finish downloading the file before the data is committed to the database. - * It registers the request with the DownloadManager service and also updates the metadata - * database directly within a synchronized section. - * This method has no intelligence about the data it commits to the database aside from the - * download request id, which is not known before submitting the request to the download - * manager. Hence, it only updates the relevant line. - * - * @param manager a wrapped download manager service to register the request with. - * @param request the request to register. - * @param db the metadata database. - * @param id the id of the word list. - * @param version the version of the word list. - * @return the download id returned by the download manager. - */ - public static long registerDownloadRequest(final DownloadManagerWrapper manager, - final Request request, final SQLiteDatabase db, final String id, final int version) { - Log.i(TAG, "registerDownloadRequest() : Id = " + id + " : Version = " + version); - final long downloadId; - synchronized (sSharedIdProtector) { - downloadId = manager.enqueue(request); - Log.i(TAG, "registerDownloadRequest() : DownloadId = " + downloadId); - MetadataDbHelper.markEntryAsDownloading(db, id, version, downloadId); - } - return downloadId; - } - - /** - * Retrieve information about a specific download from DownloadManager. - */ - private static CompletedDownloadInfo getCompletedDownloadInfo( - final DownloadManagerWrapper manager, final long downloadId) { - final Query query = new Query().setFilterById(downloadId); - final Cursor cursor = manager.query(query); - - if (null == cursor) { - return new CompletedDownloadInfo(null, downloadId, DownloadManager.STATUS_FAILED); - } - try { - final String uri; - final int status; - if (cursor.moveToNext()) { - final int columnStatus = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); - final int columnError = cursor.getColumnIndex(DownloadManager.COLUMN_REASON); - final int columnUri = cursor.getColumnIndex(DownloadManager.COLUMN_URI); - final int error = cursor.getInt(columnError); - status = cursor.getInt(columnStatus); - final String uriWithAnchor = cursor.getString(columnUri); - int anchorIndex = uriWithAnchor.indexOf('#'); - if (anchorIndex != -1) { - uri = uriWithAnchor.substring(0, anchorIndex); - } else { - uri = uriWithAnchor; - } - if (DownloadManager.STATUS_SUCCESSFUL != status) { - Log.e(TAG, "Permanent failure of download " + downloadId - + " with error code: " + error); - } - } else { - uri = null; - status = DownloadManager.STATUS_FAILED; - } - return new CompletedDownloadInfo(uri, downloadId, status); - } finally { - cursor.close(); - } - } - - private static ArrayList<DownloadRecord> getDownloadRecordsForCompletedDownloadInfo( - final Context context, final CompletedDownloadInfo downloadInfo) { - // Get and check the ID of the file we are waiting for, compare them to downloaded ones - synchronized(sSharedIdProtector) { - final ArrayList<DownloadRecord> downloadRecords = - MetadataDbHelper.getDownloadRecordsForDownloadId(context, - downloadInfo.mDownloadId); - // If any of these is metadata, we should update the DB - boolean hasMetadata = false; - for (DownloadRecord record : downloadRecords) { - if (record.isMetadata()) { - hasMetadata = true; - break; - } - } - if (hasMetadata) { - writeMetadataDownloadId(context, downloadInfo.mUri, NOT_AN_ID); - MetadataDbHelper.saveLastUpdateTimeOfUri(context, downloadInfo.mUri); - } - return downloadRecords; - } - } - - /** - * Take appropriate action after a download finished, in success or in error. - * - * This is called by the system upon broadcast from the DownloadManager that a file - * has been downloaded successfully. - * After a simple check that this is actually the file we are waiting for, this - * method basically coordinates the parsing and comparison of metadata, and fires - * the computation of the list of actions that should be taken then executes them. - * - * @param context The context for this action. - * @param intent The intent from the DownloadManager containing details about the download. - */ - /* package */ static void downloadFinished(final Context context, final Intent intent) { - // Get and check the ID of the file that was downloaded - final long fileId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, NOT_AN_ID); - Log.i(TAG, "downloadFinished() : DownloadId = " + fileId); - if (NOT_AN_ID == fileId) return; // Spurious wake-up: ignore - - final DownloadManagerWrapper manager = new DownloadManagerWrapper(context); - final CompletedDownloadInfo downloadInfo = getCompletedDownloadInfo(manager, fileId); - - final ArrayList<DownloadRecord> recordList = - getDownloadRecordsForCompletedDownloadInfo(context, downloadInfo); - if (null == recordList) return; // It was someone else's download. - DebugLogUtils.l("Received result for download ", fileId); - - // TODO: handle gracefully a null pointer here. This is practically impossible because - // we come here only when DownloadManager explicitly called us when it ended a - // download, so we are pretty sure it's alive. It's theoretically possible that it's - // disabled right inbetween the firing of the intent and the control reaching here. - - for (final DownloadRecord record : recordList) { - // downloadSuccessful is not final because we may still have exceptions from now on - boolean downloadSuccessful = false; - try { - if (downloadInfo.wasSuccessful()) { - downloadSuccessful = handleDownloadedFile(context, record, manager, fileId); - Log.i(TAG, "downloadFinished() : Success = " + downloadSuccessful); - } - } finally { - final String resultMessage = downloadSuccessful ? "Success" : "Failure"; - if (record.isMetadata()) { - Log.i(TAG, "downloadFinished() : Metadata " + resultMessage); - publishUpdateMetadataCompleted(context, downloadSuccessful); - } else { - Log.i(TAG, "downloadFinished() : WordList " + resultMessage); - final SQLiteDatabase db = MetadataDbHelper.getDb(context, record.mClientId); - publishUpdateWordListCompleted(context, downloadSuccessful, fileId, - db, record.mAttributes, record.mClientId); - } - } - } - // Now that we're done using it, we can remove this download from DLManager - manager.remove(fileId); - } - - /** - * Sends a broadcast informing listeners that the dictionaries were updated. - * - * This will call all local listeners through the UpdateEventListener#downloadedMetadata - * callback (for example, the dictionary provider interface uses this to stop the Loading - * animation) and send a broadcast about the metadata having been updated. For a client of - * the dictionary pack like Latin IME, this means it should re-query the dictionary pack - * for any relevant new data. - * - * @param context the context, to send the broadcast. - * @param downloadSuccessful whether the download of the metadata was successful or not. - */ - public static void publishUpdateMetadataCompleted(final Context context, - final boolean downloadSuccessful) { - // We need to warn all listeners of what happened. But some listeners may want to - // remove themselves or re-register something in response. Hence we should take a - // snapshot of the listener list and warn them all. This also prevents any - // concurrent modification problem of the static list. - for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { - listener.downloadedMetadata(downloadSuccessful); - } - publishUpdateCycleCompletedEvent(context); - } - - private static void publishUpdateWordListCompleted(final Context context, - final boolean downloadSuccessful, final long fileId, - final SQLiteDatabase db, final ContentValues downloadedFileRecord, - final String clientId) { - synchronized(sSharedIdProtector) { - if (downloadSuccessful) { - final ActionBatch actions = new ActionBatch(); - actions.add(new ActionBatch.InstallAfterDownloadAction(clientId, - downloadedFileRecord)); - actions.execute(context, new LogProblemReporter(TAG)); - } else { - MetadataDbHelper.deleteDownloadingEntry(db, fileId); - } - } - // See comment above about #linkedCopyOfLists - for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { - listener.wordListDownloadFinished(downloadedFileRecord.getAsString( - MetadataDbHelper.WORDLISTID_COLUMN), downloadSuccessful); - } - publishUpdateCycleCompletedEvent(context); - } - - private static void publishUpdateCycleCompletedEvent(final Context context) { - // Even if this is not successful, we have to publish the new state. - PrivateLog.log("Publishing update cycle completed event"); - DebugLogUtils.l("Publishing update cycle completed event"); - for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { - listener.updateCycleCompleted(); - } - signalNewDictionaryState(context); - } - - private static boolean handleDownloadedFile(final Context context, - final DownloadRecord downloadRecord, final DownloadManagerWrapper manager, - final long fileId) { - try { - // {@link handleWordList(Context,InputStream,ContentValues)}. - // Handle the downloaded file according to its type - if (downloadRecord.isMetadata()) { - DebugLogUtils.l("Data D/L'd is metadata for", downloadRecord.mClientId); - // #handleMetadata() closes its InputStream argument - handleMetadata(context, new ParcelFileDescriptor.AutoCloseInputStream( - manager.openDownloadedFile(fileId)), downloadRecord.mClientId); - } else { - DebugLogUtils.l("Data D/L'd is a word list"); - final int wordListStatus = downloadRecord.mAttributes.getAsInteger( - MetadataDbHelper.STATUS_COLUMN); - if (MetadataDbHelper.STATUS_DOWNLOADING == wordListStatus) { - // #handleWordList() closes its InputStream argument - handleWordList(context, new ParcelFileDescriptor.AutoCloseInputStream( - manager.openDownloadedFile(fileId)), downloadRecord); - } else { - Log.e(TAG, "Spurious download ended. Maybe a cancelled download?"); - } - } - return true; - } catch (FileNotFoundException e) { - Log.e(TAG, "A file was downloaded but it can't be opened", e); - } catch (IOException e) { - // Can't read the file... disk damage? - Log.e(TAG, "Can't read a file", e); - // TODO: Check with UX how we should warn the user. - } catch (IllegalStateException e) { - // The format of the downloaded file is incorrect. We should maybe report upstream? - Log.e(TAG, "Incorrect data received", e); - } catch (BadFormatException e) { - // The format of the downloaded file is incorrect. We should maybe report upstream? - Log.e(TAG, "Incorrect data received", e); - } - return false; - } - - /** - * Returns a copy of the specified list, with all elements copied. - * - * This returns a linked list. - */ - private static <T> List<T> linkedCopyOfList(final List<T> src) { - // Instantiation of a parameterized type is not possible in Java, so it's not possible to - // return the same type of list that was passed - probably the same reason why Collections - // does not do it. So we need to decide statically which concrete type to return. - return new LinkedList<>(src); - } - - /** - * Warn Android Keyboard that the state of dictionaries changed and it should refresh its data. - */ - private static void signalNewDictionaryState(final Context context) { - // TODO: Also provide the locale of the updated dictionary so that the LatinIme - // does not have to reset if it is a different locale. - final Intent newDictBroadcast = - new Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION); - context.sendBroadcast(newDictBroadcast); - } - - /** - * Parse metadata and take appropriate action (that is, upgrade dictionaries). - * @param context the context to read settings. - * @param stream an input stream pointing to the downloaded data. May not be null. - * Will be closed upon finishing. - * @param clientId the ID of the client to update - * @throws BadFormatException if the metadata is not in a known format. - * @throws IOException if the downloaded file can't be read from the disk - */ - public static void handleMetadata(final Context context, final InputStream stream, - final String clientId) throws IOException, BadFormatException { - DebugLogUtils.l("Entering handleMetadata"); - final List<WordListMetadata> newMetadata; - final InputStreamReader reader = new InputStreamReader(stream); - try { - // According to the doc InputStreamReader buffers, so no need to add a buffering layer - newMetadata = MetadataHandler.readMetadata(reader); - } finally { - reader.close(); - } - - DebugLogUtils.l("Downloaded metadata :", newMetadata); - PrivateLog.log("Downloaded metadata\n" + newMetadata); - - final ActionBatch actions = computeUpgradeTo(context, clientId, newMetadata); - // TODO: Check with UX how we should report to the user - // TODO: add an action to close the database - actions.execute(context, new LogProblemReporter(TAG)); - } - - /** - * Handle a word list: put it in its right place, and update the passed content values. - * @param context the context for opening files. - * @param inputStream an input stream pointing to the downloaded data. May not be null. - * Will be closed upon finishing. - * @param downloadRecord the content values to fill the file name in. - * @throws IOException if files can't be read or written. - * @throws BadFormatException if the md5 checksum doesn't match the metadata. - */ - private static void handleWordList(final Context context, - final InputStream inputStream, final DownloadRecord downloadRecord) - throws IOException, BadFormatException { - - // DownloadManager does not have the ability to put the file directly where we want - // it, so we had it download to a temporary place. Now we move it. It will be deleted - // automatically by DownloadManager. - DebugLogUtils.l("Downloaded a new word list :", downloadRecord.mAttributes.getAsString( - MetadataDbHelper.DESCRIPTION_COLUMN), "for", downloadRecord.mClientId); - PrivateLog.log("Downloaded a new word list with description : " - + downloadRecord.mAttributes.getAsString(MetadataDbHelper.DESCRIPTION_COLUMN) - + " for " + downloadRecord.mClientId); - - final String locale = - downloadRecord.mAttributes.getAsString(MetadataDbHelper.LOCALE_COLUMN); - final String destinationFile = getTempFileName(context, locale); - downloadRecord.mAttributes.put(MetadataDbHelper.LOCAL_FILENAME_COLUMN, destinationFile); - - FileOutputStream outputStream = null; - try { - outputStream = context.openFileOutput(destinationFile, Context.MODE_PRIVATE); - copyFile(inputStream, outputStream); - } finally { - inputStream.close(); - if (outputStream != null) { - outputStream.close(); - } - } - - // TODO: Consolidate this MD5 calculation with file copying above. - // We need to reopen the file because the inputstream bytes have been consumed, and there - // is nothing in InputStream to reopen or rewind the stream - FileInputStream copiedFile = null; - final String md5sum; - try { - copiedFile = context.openFileInput(destinationFile); - md5sum = MD5Calculator.checksum(copiedFile); - } finally { - if (copiedFile != null) { - copiedFile.close(); - } - } - if (TextUtils.isEmpty(md5sum)) { - return; // We can't compute the checksum anyway, so return and hope for the best - } - if (!md5sum.equals(downloadRecord.mAttributes.getAsString( - MetadataDbHelper.CHECKSUM_COLUMN))) { - context.deleteFile(destinationFile); - throw new BadFormatException("MD5 checksum check failed : \"" + md5sum + "\" <> \"" - + downloadRecord.mAttributes.getAsString(MetadataDbHelper.CHECKSUM_COLUMN) - + "\""); - } - } - - /** - * Copies in to out using FileChannels. - * - * This tries to use channels for fast copying. If it doesn't work, fall back to - * copyFileFallBack below. - * - * @param in the stream to copy from. - * @param out the stream to copy to. - * @throws IOException if both the normal and fallback methods raise exceptions. - */ - private static void copyFile(final InputStream in, final OutputStream out) - throws IOException { - DebugLogUtils.l("Copying files"); - if (!(in instanceof FileInputStream) || !(out instanceof FileOutputStream)) { - DebugLogUtils.l("Not the right types"); - copyFileFallback(in, out); - } else { - try { - final FileChannel sourceChannel = ((FileInputStream) in).getChannel(); - final FileChannel destinationChannel = ((FileOutputStream) out).getChannel(); - sourceChannel.transferTo(0, Integer.MAX_VALUE, destinationChannel); - } catch (IOException e) { - // Can't work with channels, or something went wrong. Copy by hand. - DebugLogUtils.l("Won't work"); - copyFileFallback(in, out); - } - } - } - - /** - * Copies in to out with read/write methods, not FileChannels. - * - * @param in the stream to copy from. - * @param out the stream to copy to. - * @throws IOException if a read or a write fails. - */ - private static void copyFileFallback(final InputStream in, final OutputStream out) - throws IOException { - DebugLogUtils.l("Falling back to slow copy"); - final byte[] buffer = new byte[FILE_COPY_BUFFER_SIZE]; - for (int readBytes = in.read(buffer); readBytes >= 0; readBytes = in.read(buffer)) - out.write(buffer, 0, readBytes); - } - - /** - * Creates and returns a new file to store a dictionary - * @param context the context to use to open the file. - * @param locale the locale for this dictionary, to make the file name more readable. - * @return the file name, or throw an exception. - * @throws IOException if the file cannot be created. - */ - private static String getTempFileName(final Context context, final String locale) - throws IOException { - DebugLogUtils.l("Entering openTempFileOutput"); - final File dir = context.getFilesDir(); - final File f = File.createTempFile(locale + TEMP_DICT_FILE_SUB, DICT_FILE_SUFFIX, dir); - DebugLogUtils.l("File name is", f.getName()); - return f.getName(); - } - - /** - * Compare metadata (collections of word lists). - * - * This method takes whole metadata sets directly and compares them, matching the wordlists in - * each of them on the id. It creates an ActionBatch object that can be .execute()'d to perform - * the actual upgrade from `from' to `to'. - * - * @param context the context to open databases on. - * @param clientId the id of the client. - * @param from the dictionary descriptor (as a list of wordlists) to upgrade from. - * @param to the dictionary descriptor (as a list of wordlists) to upgrade to. - * @return an ordered list of runnables to be called to upgrade. - */ - private static ActionBatch compareMetadataForUpgrade(final Context context, - final String clientId, @Nullable final List<WordListMetadata> from, - @Nullable final List<WordListMetadata> to) { - final ActionBatch actions = new ActionBatch(); - // Upgrade existing word lists - DebugLogUtils.l("Comparing dictionaries"); - final Set<String> wordListIds = new TreeSet<>(); - // TODO: Can these be null? - final List<WordListMetadata> fromList = (from == null) ? new ArrayList<WordListMetadata>() - : from; - final List<WordListMetadata> toList = (to == null) ? new ArrayList<WordListMetadata>() - : to; - for (WordListMetadata wlData : fromList) wordListIds.add(wlData.mId); - for (WordListMetadata wlData : toList) wordListIds.add(wlData.mId); - for (String id : wordListIds) { - final WordListMetadata currentInfo = MetadataHandler.findWordListById(fromList, id); - final WordListMetadata metadataInfo = MetadataHandler.findWordListById(toList, id); - // TODO: Remove the following unnecessary check, since we are now doing the filtering - // inside findWordListById. - final WordListMetadata newInfo = null == metadataInfo - || metadataInfo.mFormatVersion > MAXIMUM_SUPPORTED_FORMAT_VERSION - ? null : metadataInfo; - DebugLogUtils.l("Considering updating ", id, "currentInfo =", currentInfo); - - if (null == currentInfo && null == newInfo) { - // This may happen if a new word list appeared that we can't handle. - if (null == metadataInfo) { - // What happened? Bug in Set<>? - Log.e(TAG, "Got an id for a wordlist that is neither in from nor in to"); - } else { - // We may come here if there is a new word list that we can't handle. - Log.i(TAG, "Can't handle word list with id '" + id + "' because it has format" - + " version " + metadataInfo.mFormatVersion + " and the maximum version" - + " we can handle is " + MAXIMUM_SUPPORTED_FORMAT_VERSION); - } - continue; - } else if (null == currentInfo) { - // This is the case where a new list that we did not know of popped on the server. - // Make it available. - actions.add(new ActionBatch.MakeAvailableAction(clientId, newInfo)); - } else if (null == newInfo) { - // This is the case where an old list we had is not in the server data any more. - // Pass false to ForgetAction: this may be installed and we still want to apply - // a forget-like action (remove the URL) if it is, so we want to turn off the - // status == AVAILABLE check. If it's DELETING, this is the right thing to do, - // as we want to leave the record as long as Android Keyboard has not deleted it ; - // the record will be removed when the file is actually deleted. - actions.add(new ActionBatch.ForgetAction(clientId, currentInfo, false)); - } else { - final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId); - if (newInfo.mVersion == currentInfo.mVersion) { - if (TextUtils.equals(newInfo.mRemoteFilename, currentInfo.mRemoteFilename)) { - // If the dictionary url hasn't changed, we should preserve the retryCount. - newInfo.mRetryCount = currentInfo.mRetryCount; - } - // If it's the same id/version, we update the DB with the new values. - // It doesn't matter too much if they didn't change. - actions.add(new ActionBatch.UpdateDataAction(clientId, newInfo)); - } else if (newInfo.mVersion > currentInfo.mVersion) { - // If it's a new version, it's a different entry in the database. Make it - // available, and if it's installed, also start the download. - final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, - currentInfo.mId, currentInfo.mVersion); - final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); - actions.add(new ActionBatch.MakeAvailableAction(clientId, newInfo)); - if (status == MetadataDbHelper.STATUS_INSTALLED - || status == MetadataDbHelper.STATUS_DISABLED) { - actions.add(new ActionBatch.StartDownloadAction(clientId, newInfo)); - } else { - // Pass true to ForgetAction: this is indeed an update to a non-installed - // word list, so activate status == AVAILABLE check - // In case the status is DELETING, this is the right thing to do. It will - // leave the entry as DELETING and remove its URL so that Android Keyboard - // can delete it the next time it starts up. - actions.add(new ActionBatch.ForgetAction(clientId, currentInfo, true)); - } - } else if (DEBUG) { - Log.i(TAG, "Not updating word list " + id - + " : current list timestamp is " + currentInfo.mLastUpdate - + " ; new list timestamp is " + newInfo.mLastUpdate); - } - } - } - return actions; - } - - /** - * Computes an upgrade from the current state of the dictionaries to some desired state. - * @param context the context for reading settings and files. - * @param clientId the id of the client. - * @param newMetadata the state we want to upgrade to. - * @return the upgrade from the current state to the desired state, ready to be executed. - */ - public static ActionBatch computeUpgradeTo(final Context context, final String clientId, - final List<WordListMetadata> newMetadata) { - final List<WordListMetadata> currentMetadata = - MetadataHandler.getCurrentMetadata(context, clientId); - return compareMetadataForUpgrade(context, clientId, currentMetadata, newMetadata); - } - - /** - * Installs a word list if it has never been requested. - * - * This is called when a word list is requested, and is available but not installed. It checks - * the conditions for auto-installation: if the dictionary is a main dictionary for this - * language, and it has never been opted out through the dictionary interface, then we start - * installing it. For the user who enables a language and uses it for the first time, the - * dictionary should magically start being used a short time after they start typing. - * The mayPrompt argument indicates whether we should prompt the user for a decision to - * download or not, in case we decide we are in the case where we should download - this - * roughly happens when the current connectivity is 3G. See - * DictionaryProvider#getDictionaryWordListsForContentUri for details. - */ - // As opposed to many other methods, this method does not need the version of the word - // list because it may only install the latest version we know about for this specific - // word list ID / client ID combination. - public static void installIfNeverRequested(final Context context, final String clientId, - final String wordlistId) { - Log.i(TAG, "installIfNeverRequested() : ClientId = " + clientId - + " : WordListId = " + wordlistId); - final String[] idArray = wordlistId.split(DictionaryProvider.ID_CATEGORY_SEPARATOR); - // If we have a new-format dictionary id (category:manual_id), then use the - // specified category. Otherwise, it is a main dictionary, so force the - // MAIN category upon it. - final String category = 2 == idArray.length ? idArray[0] : MAIN_DICTIONARY_CATEGORY; - if (!MAIN_DICTIONARY_CATEGORY.equals(category)) { - // Not a main dictionary. We only auto-install main dictionaries, so we can return now. - return; - } - if (CommonPreferences.getCommonPreferences(context).contains(wordlistId)) { - // If some kind of settings has been done in the past for this specific id, then - // this is not a candidate for auto-install. Because it already is either true, - // in which case it may be installed or downloading or whatever, and we don't - // need to care about it because it's already handled or being handled, or it's false - // in which case it means the user explicitely turned it off and don't want to have - // it installed. So we quit right away. - return; - } - - final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId); - final ContentValues installCandidate = - MetadataDbHelper.getContentValuesOfLatestAvailableWordlistById(db, wordlistId); - if (MetadataDbHelper.STATUS_AVAILABLE - != installCandidate.getAsInteger(MetadataDbHelper.STATUS_COLUMN)) { - // If it's not "AVAILABLE", we want to stop now. Because candidates for auto-install - // are lists that we know are available, but we also know have never been installed. - // It does obviously not concern already installed lists, or downloading lists, - // or those that have been disabled, flagged as deleting... So anything else than - // AVAILABLE means we don't auto-install. - return; - } - - // We decided against prompting the user for a decision. This may be because we were - // explicitly asked not to, or because we are currently on wi-fi anyway, or because we - // already know the answer to the question. We'll enqueue a request ; StartDownloadAction - // knows to use the correct type of network according to the current settings. - - // Also note that once it's auto-installed, a word list will be marked as INSTALLED. It will - // thus receive automatic updates if there are any, which is what we want. If the user does - // not want this word list, they will have to go to the settings and change them, which will - // change the shared preferences. So there is no way for a word list that has been - // auto-installed once to get auto-installed again, and that's what we want. - final ActionBatch actions = new ActionBatch(); - WordListMetadata metadata = WordListMetadata.createFromContentValues(installCandidate); - actions.add(new ActionBatch.StartDownloadAction(clientId, metadata)); - final String localeString = installCandidate.getAsString(MetadataDbHelper.LOCALE_COLUMN); - - // We are in a content provider: we can't do any UI at all. We have to defer the displaying - // itself to the service. Also, we only display this when the user does not have a - // dictionary for this language already. During setup wizard, however, this UI is - // suppressed. - final boolean deviceProvisioned = Settings.Global.getInt(context.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0) != 0; - if (deviceProvisioned) { - final Intent intent = new Intent(); - intent.setClass(context, DictionaryService.class); - intent.setAction(DictionaryService.SHOW_DOWNLOAD_TOAST_INTENT_ACTION); - intent.putExtra(DictionaryService.LOCALE_INTENT_ARGUMENT, localeString); - context.startService(intent); - } else { - Log.i(TAG, "installIfNeverRequested() : Don't show download toast"); - } - - Log.i(TAG, "installIfNeverRequested() : StartDownloadAction for " + metadata); - actions.execute(context, new LogProblemReporter(TAG)); - } - - /** - * Marks the word list with the passed id as used. - * - * This will download/install the list as required. The action will see that the destination - * word list is a valid list, and take appropriate action - in this case, mark it as used. - * @see ActionBatch.Action#execute - * - * @param context the context for using action batches. - * @param clientId the id of the client. - * @param wordlistId the id of the word list to mark as installed. - * @param version the version of the word list to mark as installed. - * @param status the current status of the word list. - * @param allowDownloadOnMeteredData whether to download even on metered data connection - */ - // The version argument is not used yet, because we don't need it to retrieve the information - // we need. However, the pair (id, version) being the primary key to a word list in the database - // it feels better for consistency to pass it, and some methods retrieving information about a - // word list need it so we may need it in the future. - public static void markAsUsed(final Context context, final String clientId, - final String wordlistId, final int version, - final int status, final boolean allowDownloadOnMeteredData) { - final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList( - context, clientId, wordlistId, version); - - if (null == wordListMetaData) return; - - final ActionBatch actions = new ActionBatch(); - if (MetadataDbHelper.STATUS_DISABLED == status - || MetadataDbHelper.STATUS_DELETING == status) { - actions.add(new ActionBatch.EnableAction(clientId, wordListMetaData)); - } else if (MetadataDbHelper.STATUS_AVAILABLE == status) { - actions.add(new ActionBatch.StartDownloadAction(clientId, wordListMetaData)); - } else { - Log.e(TAG, "Unexpected state of the word list for markAsUsed : " + status); - } - actions.execute(context, new LogProblemReporter(TAG)); - signalNewDictionaryState(context); - } - - /** - * Marks the word list with the passed id as unused. - * - * This leaves the file on the disk for ulterior use. The action will see that the destination - * word list is null, and take appropriate action - in this case, mark it as unused. - * @see ActionBatch.Action#execute - * - * @param context the context for using action batches. - * @param clientId the id of the client. - * @param wordlistId the id of the word list to mark as installed. - * @param version the version of the word list to mark as installed. - * @param status the current status of the word list. - */ - // The version and status arguments are not used yet, but this method matches its interface to - // markAsUsed for consistency. - public static void markAsUnused(final Context context, final String clientId, - final String wordlistId, final int version, final int status) { - - final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList( - context, clientId, wordlistId, version); - - if (null == wordListMetaData) return; - final ActionBatch actions = new ActionBatch(); - actions.add(new ActionBatch.DisableAction(clientId, wordListMetaData)); - actions.execute(context, new LogProblemReporter(TAG)); - signalNewDictionaryState(context); - } - - /** - * Marks the word list with the passed id as deleting. - * - * This basically means that on the next chance there is (right away if Android Keyboard - * happens to be up, or the next time it gets up otherwise) the dictionary pack will - * supply an empty dictionary to it that will replace whatever dictionary is installed. - * This allows to release the space taken by a dictionary (except for the few bytes the - * empty dictionary takes up), and override a built-in default dictionary so that we - * can fake delete a built-in dictionary. - * - * @param context the context to open the database on. - * @param clientId the id of the client. - * @param wordlistId the id of the word list to mark as deleted. - * @param version the version of the word list to mark as deleted. - * @param status the current status of the word list. - */ - public static void markAsDeleting(final Context context, final String clientId, - final String wordlistId, final int version, final int status) { - - final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList( - context, clientId, wordlistId, version); - - if (null == wordListMetaData) return; - final ActionBatch actions = new ActionBatch(); - actions.add(new ActionBatch.DisableAction(clientId, wordListMetaData)); - actions.add(new ActionBatch.StartDeleteAction(clientId, wordListMetaData)); - actions.execute(context, new LogProblemReporter(TAG)); - signalNewDictionaryState(context); - } - - /** - * Marks the word list with the passed id as actually deleted. - * - * This reverts to available status or deletes the row as appropriate. - * - * @param context the context to open the database on. - * @param clientId the id of the client. - * @param wordlistId the id of the word list to mark as deleted. - * @param version the version of the word list to mark as deleted. - * @param status the current status of the word list. - */ - public static void markAsDeleted(final Context context, final String clientId, - final String wordlistId, final int version, final int status) { - final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList( - context, clientId, wordlistId, version); - - if (null == wordListMetaData) return; - - final ActionBatch actions = new ActionBatch(); - actions.add(new ActionBatch.FinishDeleteAction(clientId, wordListMetaData)); - actions.execute(context, new LogProblemReporter(TAG)); - signalNewDictionaryState(context); - } - - /** - * Checks whether the word list should be downloaded again; in which case an download & - * installation attempt is made. Otherwise the word list is marked broken. - * - * @param context the context to open the database on. - * @param clientId the id of the client. - * @param wordlistId the id of the word list which is broken. - * @param version the version of the broken word list. - */ - public static void markAsBrokenOrRetrying(final Context context, final String clientId, - final String wordlistId, final int version) { - boolean isRetryPossible = MetadataDbHelper.maybeMarkEntryAsRetrying( - MetadataDbHelper.getDb(context, clientId), wordlistId, version); - - if (isRetryPossible) { - if (DEBUG) { - Log.d(TAG, "Attempting to download & install the wordlist again."); - } - final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList( - context, clientId, wordlistId, version); - if (wordListMetaData == null) { - return; - } - - final ActionBatch actions = new ActionBatch(); - actions.add(new ActionBatch.StartDownloadAction(clientId, wordListMetaData)); - actions.execute(context, new LogProblemReporter(TAG)); - } else { - if (DEBUG) { - Log.d(TAG, "Retries for wordlist exhausted, deleting the wordlist from table."); - } - MetadataDbHelper.deleteEntry(MetadataDbHelper.getDb(context, clientId), - wordlistId, version); - } - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java b/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java deleted file mode 100644 index 99cffb816..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.content.ContentValues; - -import javax.annotation.Nonnull; - -/** - * The metadata for a single word list. - * - * Instances of this class are always immutable. - */ -public class WordListMetadata { - - public final String mId; - public final int mType; // Type, as of MetadataDbHelper#TYPE_* - public final String mDescription; - public final long mLastUpdate; - public final long mFileSize; - public final String mRawChecksum; - public final String mChecksum; - public final String mLocalFilename; - public final String mRemoteFilename; - public final int mVersion; // version of this word list - public final int mFlags; // Always 0 in this version, reserved for future use - public int mRetryCount; - - // The locale is matched against the locale requested by the client. The matching algorithm - // is a standard locale matching with fallback; it is implemented in - // DictionaryProvider#getDictionaryFileForContentUri. - public final String mLocale; - - - // Version number of the format. - // This implementation of the DictionaryDataService knows how to handle format 1 only. - // This is only for forward compatibility, to be able to upgrade the format without - // breaking old implementations. - public final int mFormatVersion; - - public WordListMetadata(final String id, final int type, - final String description, final long lastUpdate, final long fileSize, - final String rawChecksum, final String checksum, final int retryCount, - final String localFilename, final String remoteFilename, - final int version, final int formatVersion, - final int flags, final String locale) { - mId = id; - mType = type; - mDescription = description; - mLastUpdate = lastUpdate; // In milliseconds - mFileSize = fileSize; - mRawChecksum = rawChecksum; - mChecksum = checksum; - mRetryCount = retryCount; - mLocalFilename = localFilename; - mRemoteFilename = remoteFilename; - mVersion = version; - mFormatVersion = formatVersion; - mFlags = flags; - mLocale = locale; - } - - /** - * Create a WordListMetadata from the contents of a ContentValues. - * - * If this lacks any required field, IllegalArgumentException is thrown. - */ - public static WordListMetadata createFromContentValues(@Nonnull final ContentValues values) { - final String id = values.getAsString(MetadataDbHelper.WORDLISTID_COLUMN); - final Integer type = values.getAsInteger(MetadataDbHelper.TYPE_COLUMN); - final String description = values.getAsString(MetadataDbHelper.DESCRIPTION_COLUMN); - final Long lastUpdate = values.getAsLong(MetadataDbHelper.DATE_COLUMN); - final Long fileSize = values.getAsLong(MetadataDbHelper.FILESIZE_COLUMN); - final String rawChecksum = values.getAsString(MetadataDbHelper.RAW_CHECKSUM_COLUMN); - final String checksum = values.getAsString(MetadataDbHelper.CHECKSUM_COLUMN); - final int retryCount = values.getAsInteger(MetadataDbHelper.RETRY_COUNT_COLUMN); - final String localFilename = values.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN); - final String remoteFilename = values.getAsString(MetadataDbHelper.REMOTE_FILENAME_COLUMN); - final Integer version = values.getAsInteger(MetadataDbHelper.VERSION_COLUMN); - final Integer formatVersion = values.getAsInteger(MetadataDbHelper.FORMATVERSION_COLUMN); - final Integer flags = values.getAsInteger(MetadataDbHelper.FLAGS_COLUMN); - final String locale = values.getAsString(MetadataDbHelper.LOCALE_COLUMN); - if (null == id - || null == type - || null == description - || null == lastUpdate - || null == fileSize - || null == checksum - || null == localFilename - || null == remoteFilename - || null == version - || null == formatVersion - || null == flags - || null == locale) { - throw new IllegalArgumentException(); - } - return new WordListMetadata(id, type, description, lastUpdate, fileSize, rawChecksum, - checksum, retryCount, localFilename, remoteFilename, version, formatVersion, - flags, locale); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(WordListMetadata.class.getSimpleName()); - sb.append(" : ").append(mId); - sb.append("\nType : ").append(mType); - sb.append("\nDescription : ").append(mDescription); - sb.append("\nLastUpdate : ").append(mLastUpdate); - sb.append("\nFileSize : ").append(mFileSize); - sb.append("\nRawChecksum : ").append(mRawChecksum); - sb.append("\nChecksum : ").append(mChecksum); - sb.append("\nRetryCount: ").append(mRetryCount); - sb.append("\nLocalFilename : ").append(mLocalFilename); - sb.append("\nRemoteFilename : ").append(mRemoteFilename); - sb.append("\nVersion : ").append(mVersion); - sb.append("\nFormatVersion : ").append(mFormatVersion); - sb.append("\nFlags : ").append(mFlags); - sb.append("\nLocale : ").append(mLocale); - return sb.toString(); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java deleted file mode 100644 index 500e39e0e..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java +++ /dev/null @@ -1,310 +0,0 @@ -/** - * Copyright (C) 2011 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 com.android.inputmethod.dictionarypack; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.Preference; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.widget.ListView; -import android.widget.TextView; - -import com.android.inputmethod.latin.R; - -import java.util.Locale; - -/** - * A preference for one word list. - * - * This preference refers to a single word list, as available in the dictionary - * pack. Upon being pressed, it displays a menu to allow the user to install, disable, - * enable or delete it as appropriate for the current state of the word list. - */ -public final class WordListPreference extends Preference { - private static final String TAG = WordListPreference.class.getSimpleName(); - - // What to display in the "status" field when we receive unknown data as a status from - // the content provider. Empty string sounds sensible. - private static final String NO_STATUS_MESSAGE = ""; - - /// Actions - private static final int ACTION_UNKNOWN = 0; - private static final int ACTION_ENABLE_DICT = 1; - private static final int ACTION_DISABLE_DICT = 2; - private static final int ACTION_DELETE_DICT = 3; - - // Members - // The metadata word list id and version of this word list. - public final String mWordlistId; - public final int mVersion; - public final Locale mLocale; - public final String mDescription; - - // The id of the client for which this preference is. - private final String mClientId; - // The status - private int mStatus; - // The size of the dictionary file - private final int mFilesize; - - private final DictionaryListInterfaceState mInterfaceState; - - public WordListPreference(final Context context, - final DictionaryListInterfaceState dictionaryListInterfaceState, final String clientId, - final String wordlistId, final int version, final Locale locale, - final String description, final int status, final int filesize) { - super(context, null); - mInterfaceState = dictionaryListInterfaceState; - mClientId = clientId; - mVersion = version; - mWordlistId = wordlistId; - mFilesize = filesize; - mLocale = locale; - mDescription = description; - - setLayoutResource(R.layout.dictionary_line); - - setTitle(description); - setStatus(status); - setKey(wordlistId); - } - - public void setStatus(final int status) { - if (status == mStatus) return; - mStatus = status; - setSummary(getSummary(status)); - } - - public boolean hasStatus(final int status) { - return status == mStatus; - } - - @Override - public View onCreateView(final ViewGroup parent) { - final View orphanedView = mInterfaceState.findFirstOrphanedView(); - if (null != orphanedView) return orphanedView; // Will be sent to onBindView - final View newView = super.onCreateView(parent); - return mInterfaceState.addToCacheAndReturnView(newView); - } - - public boolean hasPriorityOver(final int otherPrefStatus) { - // Both of these should be one of MetadataDbHelper.STATUS_* - return mStatus > otherPrefStatus; - } - - private String getSummary(final int status) { - final Context context = getContext(); - switch (status) { - // If we are deleting the word list, for the user it's like it's already deleted. - // It should be reinstallable. Exposing to the user the whole complexity of - // the delayed deletion process between the dictionary pack and Android Keyboard - // would only be confusing. - case MetadataDbHelper.STATUS_DELETING: - case MetadataDbHelper.STATUS_AVAILABLE: - return context.getString(R.string.dictionary_available); - case MetadataDbHelper.STATUS_DOWNLOADING: - return context.getString(R.string.dictionary_downloading); - case MetadataDbHelper.STATUS_INSTALLED: - return context.getString(R.string.dictionary_installed); - case MetadataDbHelper.STATUS_DISABLED: - return context.getString(R.string.dictionary_disabled); - default: - return NO_STATUS_MESSAGE; - } - } - - // The table below needs to be kept in sync with MetadataDbHelper.STATUS_* since it uses - // the values as indices. - private static final int sStatusActionList[][] = { - // MetadataDbHelper.STATUS_UNKNOWN - {}, - // MetadataDbHelper.STATUS_AVAILABLE - { ButtonSwitcher.STATUS_INSTALL, ACTION_ENABLE_DICT }, - // MetadataDbHelper.STATUS_DOWNLOADING - { ButtonSwitcher.STATUS_CANCEL, ACTION_DISABLE_DICT }, - // MetadataDbHelper.STATUS_INSTALLED - { ButtonSwitcher.STATUS_DELETE, ACTION_DELETE_DICT }, - // MetadataDbHelper.STATUS_DISABLED - { ButtonSwitcher.STATUS_DELETE, ACTION_DELETE_DICT }, - // MetadataDbHelper.STATUS_DELETING - // We show 'install' because the file is supposed to be deleted. - // The user may reinstall it. - { ButtonSwitcher.STATUS_INSTALL, ACTION_ENABLE_DICT } - }; - - static int getButtonSwitcherStatus(final int status) { - if (status >= sStatusActionList.length) { - Log.e(TAG, "Unknown status " + status); - return ButtonSwitcher.STATUS_NO_BUTTON; - } - return sStatusActionList[status][0]; - } - - static int getActionIdFromStatusAndMenuEntry(final int status) { - if (status >= sStatusActionList.length) { - Log.e(TAG, "Unknown status " + status); - return ACTION_UNKNOWN; - } - return sStatusActionList[status][1]; - } - - private void disableDict() { - final Context context = getContext(); - final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context); - CommonPreferences.disable(prefs, mWordlistId); - UpdateHandler.markAsUnused(context, mClientId, mWordlistId, mVersion, mStatus); - if (MetadataDbHelper.STATUS_DOWNLOADING == mStatus) { - setStatus(MetadataDbHelper.STATUS_AVAILABLE); - } else if (MetadataDbHelper.STATUS_INSTALLED == mStatus) { - // Interface-wise, we should no longer be able to come here. However, this is still - // the right thing to do if we do come here. - setStatus(MetadataDbHelper.STATUS_DISABLED); - } else { - Log.e(TAG, "Unexpected state of the word list for disabling " + mStatus); - } - } - - private void enableDict() { - final Context context = getContext(); - final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context); - CommonPreferences.enable(prefs, mWordlistId); - // Explicit enabling by the user : allow downloading on metered data connection. - UpdateHandler.markAsUsed(context, mClientId, mWordlistId, mVersion, mStatus, true); - if (MetadataDbHelper.STATUS_AVAILABLE == mStatus) { - setStatus(MetadataDbHelper.STATUS_DOWNLOADING); - } else if (MetadataDbHelper.STATUS_DISABLED == mStatus - || MetadataDbHelper.STATUS_DELETING == mStatus) { - // If the status is DELETING, it means Android Keyboard - // has not deleted the word list yet, so we can safely - // turn it to 'installed'. The status DISABLED is still supported internally to - // avoid breaking older installations and all but there should not be a way to - // disable a word list through the interface any more. - setStatus(MetadataDbHelper.STATUS_INSTALLED); - } else { - Log.e(TAG, "Unexpected state of the word list for enabling " + mStatus); - } - } - - private void deleteDict() { - final Context context = getContext(); - final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context); - CommonPreferences.disable(prefs, mWordlistId); - setStatus(MetadataDbHelper.STATUS_DELETING); - UpdateHandler.markAsDeleting(context, mClientId, mWordlistId, mVersion, mStatus); - } - - @Override - protected void onBindView(final View view) { - super.onBindView(view); - ((ViewGroup)view).setLayoutTransition(null); - - final DictionaryDownloadProgressBar progressBar = - (DictionaryDownloadProgressBar)view.findViewById(R.id.dictionary_line_progress_bar); - final TextView status = (TextView)view.findViewById(android.R.id.summary); - progressBar.setIds(mClientId, mWordlistId); - progressBar.setMax(mFilesize); - final boolean showProgressBar = (MetadataDbHelper.STATUS_DOWNLOADING == mStatus); - setSummary(getSummary(mStatus)); - status.setVisibility(showProgressBar ? View.INVISIBLE : View.VISIBLE); - progressBar.setVisibility(showProgressBar ? View.VISIBLE : View.INVISIBLE); - - final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)view.findViewById( - R.id.wordlist_button_switcher); - // We need to clear the state of the button switcher, because we reuse views; if we didn't - // reset it would animate from whatever its old state was. - buttonSwitcher.reset(mInterfaceState); - if (mInterfaceState.isOpen(mWordlistId)) { - // The button is open. - final int previousStatus = mInterfaceState.getStatus(mWordlistId); - buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(previousStatus)); - if (previousStatus != mStatus) { - // We come here if the status has changed since last time. We need to animate - // the transition. - buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus)); - mInterfaceState.setOpen(mWordlistId, mStatus); - } - } else { - // The button is closed. - buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON); - } - buttonSwitcher.setInternalOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View v) { - onActionButtonClicked(); - } - }); - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View v) { - onWordListClicked(v); - } - }); - } - - void onWordListClicked(final View v) { - // Note : v is the preference view - final ViewParent parent = v.getParent(); - // Just in case something changed in the framework, test for the concrete class - if (!(parent instanceof ListView)) return; - final ListView listView = (ListView)parent; - final int indexToOpen; - // Close all first, we'll open back any item that needs to be open. - final boolean wasOpen = mInterfaceState.isOpen(mWordlistId); - mInterfaceState.closeAll(); - if (wasOpen) { - // This button being shown. Take note that we don't want to open any button in the - // loop below. - indexToOpen = -1; - } else { - // This button was not being shown. Open it, and remember the index of this - // child as the one to open in the following loop. - mInterfaceState.setOpen(mWordlistId, mStatus); - indexToOpen = listView.indexOfChild(v); - } - final int lastDisplayedIndex = - listView.getLastVisiblePosition() - listView.getFirstVisiblePosition(); - // The "lastDisplayedIndex" is actually displayed, hence the <= - for (int i = 0; i <= lastDisplayedIndex; ++i) { - final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)listView.getChildAt(i) - .findViewById(R.id.wordlist_button_switcher); - if (i == indexToOpen) { - buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus)); - } else { - buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON); - } - } - } - - void onActionButtonClicked() { - switch (getActionIdFromStatusAndMenuEntry(mStatus)) { - case ACTION_ENABLE_DICT: - enableDict(); - break; - case ACTION_DISABLE_DICT: - disableDict(); - break; - case ACTION_DELETE_DICT: - deleteDict(); - break; - default: - Log.e(TAG, "Unknown menu item pressed"); - } - } -} diff --git a/java/src/com/android/inputmethod/event/Combiner.java b/java/src/com/android/inputmethod/event/Combiner.java deleted file mode 100644 index fee93f0c6..000000000 --- a/java/src/com/android/inputmethod/event/Combiner.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.event; - -import java.util.ArrayList; - -import javax.annotation.Nonnull; - -/** - * A generic interface for combiners. Combiners are objects that transform chains of input events - * into committable strings and manage feedback to show to the user on the combining state. - */ -public interface Combiner { - /** - * Process an event, possibly combining it with the existing state and return the new event. - * - * If this event does not result in any new event getting passed down the chain, this method - * returns null. It may also modify the previous event list if appropriate. - * - * @param previousEvents the previous events in this composition. - * @param event the event to combine with the existing state. - * @return the resulting event. - */ - @Nonnull - Event processEvent(ArrayList<Event> previousEvents, Event event); - - /** - * Get the feedback that should be shown to the user for the current state of this combiner. - * @return A CharSequence representing the feedback to show users. It may include styles. - */ - CharSequence getCombiningStateFeedback(); - - /** - * Reset the state of this combiner, for example when the cursor was moved. - */ - void reset(); -} diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java deleted file mode 100644 index d77ece8e6..000000000 --- a/java/src/com/android/inputmethod/event/CombinerChain.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.event; - -import android.text.SpannableStringBuilder; -import android.text.TextUtils; - -import com.android.inputmethod.latin.common.Constants; - -import java.util.ArrayList; - -import javax.annotation.Nonnull; - -/** - * This class implements the logic chain between receiving events and generating code points. - * - * Event sources are multiple. It may be a hardware keyboard, a D-PAD, a software keyboard, - * or any exotic input source. - * This class will orchestrate the composing chain that starts with an event as its input. Each - * composer will be given turns one after the other. - * The output is composed of two sequences of code points: the first, representing the already - * finished combining part, will be shown normally as the composing string, while the second is - * feedback on the composing state and will typically be shown with different styling such as - * a colored background. - */ -public class CombinerChain { - // The already combined text, as described above - private StringBuilder mCombinedText; - // The feedback on the composing state, as described above - private SpannableStringBuilder mStateFeedback; - private final ArrayList<Combiner> mCombiners; - - /** - * Create an combiner chain. - * - * The combiner chain takes events as inputs and outputs code points and combining state. - * For example, if the input language is Japanese, the combining chain will typically perform - * kana conversion. This takes a string for initial text, taken to be present before the - * cursor: we'll start after this. - * - * @param initialText The text that has already been combined so far. - */ - public CombinerChain(final String initialText) { - mCombiners = new ArrayList<>(); - // The dead key combiner is always active, and always first - mCombiners.add(new DeadKeyCombiner()); - mCombinedText = new StringBuilder(initialText); - mStateFeedback = new SpannableStringBuilder(); - } - - public void reset() { - mCombinedText.setLength(0); - mStateFeedback.clear(); - for (final Combiner c : mCombiners) { - c.reset(); - } - } - - private void updateStateFeedback() { - mStateFeedback.clear(); - for (int i = mCombiners.size() - 1; i >= 0; --i) { - mStateFeedback.append(mCombiners.get(i).getCombiningStateFeedback()); - } - } - - /** - * Process an event through the combining chain, and return a processed event to apply. - * @param previousEvents the list of previous events in this composition - * @param newEvent the new event to process - * @return the processed event. It may be the same event, or a consumed event, or a completely - * new event. However it may never be null. - */ - @Nonnull - public Event processEvent(final ArrayList<Event> previousEvents, - @Nonnull final Event newEvent) { - final ArrayList<Event> modifiablePreviousEvents = new ArrayList<>(previousEvents); - Event event = newEvent; - for (final Combiner combiner : mCombiners) { - // A combiner can never return more than one event; it can return several - // code points, but they should be encapsulated within one event. - event = combiner.processEvent(modifiablePreviousEvents, event); - if (event.isConsumed()) { - // If the event is consumed, then we don't pass it to subsequent combiners: - // they should not see it at all. - break; - } - } - updateStateFeedback(); - return event; - } - - /** - * Apply a processed event. - * @param event the event to be applied - */ - public void applyProcessedEvent(final Event event) { - if (null != event) { - // TODO: figure out the generic way of doing this - if (Constants.CODE_DELETE == event.mKeyCode) { - final int length = mCombinedText.length(); - if (length > 0) { - final int lastCodePoint = mCombinedText.codePointBefore(length); - mCombinedText.delete(length - Character.charCount(lastCodePoint), length); - } - } else { - final CharSequence textToCommit = event.getTextToCommit(); - if (!TextUtils.isEmpty(textToCommit)) { - mCombinedText.append(textToCommit); - } - } - } - updateStateFeedback(); - } - - /** - * Get the char sequence that should be displayed as the composing word. It may include - * styling spans. - */ - public CharSequence getComposingWordWithCombiningFeedback() { - final SpannableStringBuilder s = new SpannableStringBuilder(mCombinedText); - return s.append(mStateFeedback); - } -} diff --git a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java deleted file mode 100644 index 1a28bb1d5..000000000 --- a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.event; - -import android.text.TextUtils; -import android.util.SparseIntArray; - -import com.android.inputmethod.latin.common.Constants; - -import java.text.Normalizer; -import java.util.ArrayList; - -import javax.annotation.Nonnull; - -/** - * A combiner that handles dead keys. - */ -public class DeadKeyCombiner implements Combiner { - - private static class Data { - // This class data taken from KeyCharacterMap.java. - - /* Characters used to display placeholders for dead keys. */ - private static final int ACCENT_ACUTE = '\u00B4'; - private static final int ACCENT_BREVE = '\u02D8'; - private static final int ACCENT_CARON = '\u02C7'; - private static final int ACCENT_CEDILLA = '\u00B8'; - private static final int ACCENT_CIRCUMFLEX = '\u02C6'; - private static final int ACCENT_COMMA_ABOVE = '\u1FBD'; - private static final int ACCENT_COMMA_ABOVE_RIGHT = '\u02BC'; - private static final int ACCENT_DOT_ABOVE = '\u02D9'; - private static final int ACCENT_DOT_BELOW = Constants.CODE_PERIOD; // approximate - private static final int ACCENT_DOUBLE_ACUTE = '\u02DD'; - private static final int ACCENT_GRAVE = '\u02CB'; - private static final int ACCENT_HOOK_ABOVE = '\u02C0'; - private static final int ACCENT_HORN = Constants.CODE_SINGLE_QUOTE; // approximate - private static final int ACCENT_MACRON = '\u00AF'; - private static final int ACCENT_MACRON_BELOW = '\u02CD'; - private static final int ACCENT_OGONEK = '\u02DB'; - private static final int ACCENT_REVERSED_COMMA_ABOVE = '\u02BD'; - private static final int ACCENT_RING_ABOVE = '\u02DA'; - private static final int ACCENT_STROKE = Constants.CODE_DASH; // approximate - private static final int ACCENT_TILDE = '\u02DC'; - private static final int ACCENT_TURNED_COMMA_ABOVE = '\u02BB'; - private static final int ACCENT_UMLAUT = '\u00A8'; - private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8'; - private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC'; - - /* Legacy dead key display characters used in previous versions of the API (before L) - * We still support these characters by mapping them to their non-legacy version. */ - private static final int ACCENT_GRAVE_LEGACY = Constants.CODE_GRAVE_ACCENT; - private static final int ACCENT_CIRCUMFLEX_LEGACY = Constants.CODE_CIRCUMFLEX_ACCENT; - private static final int ACCENT_TILDE_LEGACY = Constants.CODE_TILDE; - - /** - * Maps Unicode combining diacritical to display-form dead key. - */ - static final SparseIntArray sCombiningToAccent = new SparseIntArray(); - static final SparseIntArray sAccentToCombining = new SparseIntArray(); - static { - // U+0300: COMBINING GRAVE ACCENT - addCombining('\u0300', ACCENT_GRAVE); - // U+0301: COMBINING ACUTE ACCENT - addCombining('\u0301', ACCENT_ACUTE); - // U+0302: COMBINING CIRCUMFLEX ACCENT - addCombining('\u0302', ACCENT_CIRCUMFLEX); - // U+0303: COMBINING TILDE - addCombining('\u0303', ACCENT_TILDE); - // U+0304: COMBINING MACRON - addCombining('\u0304', ACCENT_MACRON); - // U+0306: COMBINING BREVE - addCombining('\u0306', ACCENT_BREVE); - // U+0307: COMBINING DOT ABOVE - addCombining('\u0307', ACCENT_DOT_ABOVE); - // U+0308: COMBINING DIAERESIS - addCombining('\u0308', ACCENT_UMLAUT); - // U+0309: COMBINING HOOK ABOVE - addCombining('\u0309', ACCENT_HOOK_ABOVE); - // U+030A: COMBINING RING ABOVE - addCombining('\u030A', ACCENT_RING_ABOVE); - // U+030B: COMBINING DOUBLE ACUTE ACCENT - addCombining('\u030B', ACCENT_DOUBLE_ACUTE); - // U+030C: COMBINING CARON - addCombining('\u030C', ACCENT_CARON); - // U+030D: COMBINING VERTICAL LINE ABOVE - addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE); - // U+030E: COMBINING DOUBLE VERTICAL LINE ABOVE - //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE); - // U+030F: COMBINING DOUBLE GRAVE ACCENT - //addCombining('\u030F', ACCENT_DOUBLE_GRAVE); - // U+0310: COMBINING CANDRABINDU - //addCombining('\u0310', ACCENT_CANDRABINDU); - // U+0311: COMBINING INVERTED BREVE - //addCombining('\u0311', ACCENT_INVERTED_BREVE); - // U+0312: COMBINING TURNED COMMA ABOVE - addCombining('\u0312', ACCENT_TURNED_COMMA_ABOVE); - // U+0313: COMBINING COMMA ABOVE - addCombining('\u0313', ACCENT_COMMA_ABOVE); - // U+0314: COMBINING REVERSED COMMA ABOVE - addCombining('\u0314', ACCENT_REVERSED_COMMA_ABOVE); - // U+0315: COMBINING COMMA ABOVE RIGHT - addCombining('\u0315', ACCENT_COMMA_ABOVE_RIGHT); - // U+031B: COMBINING HORN - addCombining('\u031B', ACCENT_HORN); - // U+0323: COMBINING DOT BELOW - addCombining('\u0323', ACCENT_DOT_BELOW); - // U+0326: COMBINING COMMA BELOW - //addCombining('\u0326', ACCENT_COMMA_BELOW); - // U+0327: COMBINING CEDILLA - addCombining('\u0327', ACCENT_CEDILLA); - // U+0328: COMBINING OGONEK - addCombining('\u0328', ACCENT_OGONEK); - // U+0329: COMBINING VERTICAL LINE BELOW - addCombining('\u0329', ACCENT_VERTICAL_LINE_BELOW); - // U+0331: COMBINING MACRON BELOW - addCombining('\u0331', ACCENT_MACRON_BELOW); - // U+0335: COMBINING SHORT STROKE OVERLAY - addCombining('\u0335', ACCENT_STROKE); - // U+0342: COMBINING GREEK PERISPOMENI - //addCombining('\u0342', ACCENT_PERISPOMENI); - // U+0344: COMBINING GREEK DIALYTIKA TONOS - //addCombining('\u0344', ACCENT_DIALYTIKA_TONOS); - // U+0345: COMBINING GREEK YPOGEGRAMMENI - //addCombining('\u0345', ACCENT_YPOGEGRAMMENI); - - // One-way mappings to equivalent preferred accents. - // U+0340: COMBINING GRAVE TONE MARK - sCombiningToAccent.append('\u0340', ACCENT_GRAVE); - // U+0341: COMBINING ACUTE TONE MARK - sCombiningToAccent.append('\u0341', ACCENT_ACUTE); - // U+0343: COMBINING GREEK KORONIS - sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE); - - // One-way legacy mappings to preserve compatibility with older applications. - // U+0300: COMBINING GRAVE ACCENT - sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300'); - // U+0302: COMBINING CIRCUMFLEX ACCENT - sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302'); - // U+0303: COMBINING TILDE - sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303'); - } - - private static void addCombining(int combining, int accent) { - sCombiningToAccent.append(combining, accent); - sAccentToCombining.append(accent, combining); - } - - // Caution! This may only contain chars, not supplementary code points. It's unlikely - // it will ever need to, but if it does we'll have to change this - private static final SparseIntArray sNonstandardDeadCombinations = new SparseIntArray(); - static { - // Non-standard decompositions. - // Stroke modifier for Finnish multilingual keyboard and others. - // U+0110: LATIN CAPITAL LETTER D WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'D', '\u0110'); - // U+01E4: LATIN CAPITAL LETTER G WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'G', '\u01e4'); - // U+0126: LATIN CAPITAL LETTER H WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'H', '\u0126'); - // U+0197: LATIN CAPITAL LETTER I WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'I', '\u0197'); - // U+0141: LATIN CAPITAL LETTER L WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'L', '\u0141'); - // U+00D8: LATIN CAPITAL LETTER O WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'O', '\u00d8'); - // U+0166: LATIN CAPITAL LETTER T WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'T', '\u0166'); - // U+0111: LATIN SMALL LETTER D WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'd', '\u0111'); - // U+01E5: LATIN SMALL LETTER G WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'g', '\u01e5'); - // U+0127: LATIN SMALL LETTER H WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'h', '\u0127'); - // U+0268: LATIN SMALL LETTER I WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'i', '\u0268'); - // U+0142: LATIN SMALL LETTER L WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'l', '\u0142'); - // U+00F8: LATIN SMALL LETTER O WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'o', '\u00f8'); - // U+0167: LATIN SMALL LETTER T WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 't', '\u0167'); - } - - private static void addNonStandardDeadCombination(final int deadCodePoint, - final int spacingCodePoint, final int result) { - final int combination = (deadCodePoint << 16) | spacingCodePoint; - sNonstandardDeadCombinations.put(combination, result); - } - - public static final int NOT_A_CHAR = 0; - public static final int BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION = 16; - // Get a non-standard combination - public static char getNonstandardCombination(final int deadCodePoint, - final int spacingCodePoint) { - final int combination = spacingCodePoint | - (deadCodePoint << BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION); - return (char)sNonstandardDeadCombinations.get(combination, NOT_A_CHAR); - } - } - - // TODO: make this a list of events instead - final StringBuilder mDeadSequence = new StringBuilder(); - - @Nonnull - private static Event createEventChainFromSequence(final @Nonnull CharSequence text, - @Nonnull final Event originalEvent) { - int index = text.length(); - if (index <= 0) { - return originalEvent; - } - Event lastEvent = null; - do { - final int codePoint = Character.codePointBefore(text, index); - lastEvent = Event.createHardwareKeypressEvent(codePoint, - originalEvent.mKeyCode, lastEvent, false /* isKeyRepeat */); - index -= Character.charCount(codePoint); - } while (index > 0); - return lastEvent; - } - - @Override - @Nonnull - public Event processEvent(final ArrayList<Event> previousEvents, final Event event) { - if (TextUtils.isEmpty(mDeadSequence)) { - // No dead char is currently being tracked: this is the most common case. - if (event.isDead()) { - // The event was a dead key. Start tracking it. - mDeadSequence.appendCodePoint(event.mCodePoint); - return Event.createConsumedEvent(event); - } - // Regular keystroke when not keeping track of a dead key. Simply said, there are - // no dead keys at all in the current input, so this combiner has nothing to do and - // simply returns the event as is. The majority of events will go through this path. - return event; - } - if (Character.isWhitespace(event.mCodePoint) - || event.mCodePoint == mDeadSequence.codePointBefore(mDeadSequence.length())) { - // When whitespace or twice the same dead key, we should output the dead sequence as is. - final Event resultEvent = createEventChainFromSequence(mDeadSequence.toString(), - event); - mDeadSequence.setLength(0); - return resultEvent; - } - if (event.isFunctionalKeyEvent()) { - if (Constants.CODE_DELETE == event.mKeyCode) { - // Remove the last code point - final int trimIndex = mDeadSequence.length() - Character.charCount( - mDeadSequence.codePointBefore(mDeadSequence.length())); - mDeadSequence.setLength(trimIndex); - return Event.createConsumedEvent(event); - } - return event; - } - if (event.isDead()) { - mDeadSequence.appendCodePoint(event.mCodePoint); - return Event.createConsumedEvent(event); - } - // Combine normally. - final StringBuilder sb = new StringBuilder(); - sb.appendCodePoint(event.mCodePoint); - int codePointIndex = 0; - while (codePointIndex < mDeadSequence.length()) { - final int deadCodePoint = mDeadSequence.codePointAt(codePointIndex); - final char replacementSpacingChar = - Data.getNonstandardCombination(deadCodePoint, event.mCodePoint); - if (Data.NOT_A_CHAR != replacementSpacingChar) { - sb.setCharAt(0, replacementSpacingChar); - } else { - final int combining = Data.sAccentToCombining.get(deadCodePoint); - sb.appendCodePoint(0 == combining ? deadCodePoint : combining); - } - codePointIndex += Character.isSupplementaryCodePoint(deadCodePoint) ? 2 : 1; - } - final String normalizedString = Normalizer.normalize(sb, Normalizer.Form.NFC); - final Event resultEvent = createEventChainFromSequence(normalizedString, event); - mDeadSequence.setLength(0); - return resultEvent; - } - - @Override - public void reset() { - mDeadSequence.setLength(0); - } - - @Override - public CharSequence getCombiningStateFeedback() { - return mDeadSequence; - } -} diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java deleted file mode 100644 index 58d878fbe..000000000 --- a/java/src/com/android/inputmethod/event/Event.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.event; - -import com.android.inputmethod.annotations.ExternallyReferenced; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; - -import javax.annotation.Nonnull; - -/** - * Class representing a generic input event as handled by Latin IME. - * - * This contains information about the origin of the event, but it is generalized and should - * represent a software keypress, hardware keypress, or d-pad move alike. - * Very importantly, this does not necessarily result in inputting one character, or even anything - * at all - it may be a dead key, it may be a partial input, it may be a special key on the - * keyboard, it may be a cancellation of a keypress (e.g. in a soft keyboard the finger of the - * user has slid out of the key), etc. It may also be a batch input from a gesture or handwriting - * for example. - * The combiner should figure out what to do with this. - */ -public class Event { - // Should the types below be represented by separate classes instead? It would be cleaner - // but probably a bit too much - // An event we don't handle in Latin IME, for example pressing Ctrl on a hardware keyboard. - final public static int EVENT_TYPE_NOT_HANDLED = 0; - // A key press that is part of input, for example pressing an alphabetic character on a - // hardware qwerty keyboard. It may be part of a sequence that will be re-interpreted later - // through combination. - final public static int EVENT_TYPE_INPUT_KEYPRESS = 1; - // A toggle event is triggered by a key that affects the previous character. An example would - // be a numeric key on a 10-key keyboard, which would toggle between 1 - a - b - c with - // repeated presses. - final public static int EVENT_TYPE_TOGGLE = 2; - // A mode event instructs the combiner to change modes. The canonical example would be the - // hankaku/zenkaku key on a Japanese keyboard, or even the caps lock key on a qwerty keyboard - // if handled at the combiner level. - final public static int EVENT_TYPE_MODE_KEY = 3; - // An event corresponding to a gesture. - final public static int EVENT_TYPE_GESTURE = 4; - // An event corresponding to the manual pick of a suggestion. - final public static int EVENT_TYPE_SUGGESTION_PICKED = 5; - // An event corresponding to a string generated by some software process. - final public static int EVENT_TYPE_SOFTWARE_GENERATED_STRING = 6; - // An event corresponding to a cursor move - final public static int EVENT_TYPE_CURSOR_MOVE = 7; - - // 0 is a valid code point, so we use -1 here. - final public static int NOT_A_CODE_POINT = -1; - // -1 is a valid key code, so we use 0 here. - final public static int NOT_A_KEY_CODE = 0; - - final private static int FLAG_NONE = 0; - // This event is a dead character, usually input by a dead key. Examples include dead-acute - // or dead-abovering. - final private static int FLAG_DEAD = 0x1; - // This event is coming from a key repeat, software or hardware. - final private static int FLAG_REPEAT = 0x2; - // This event has already been consumed. - final private static int FLAG_CONSUMED = 0x4; - - final private int mEventType; // The type of event - one of the constants above - // The code point associated with the event, if relevant. This is a unicode code point, and - // has nothing to do with other representations of the key. It is only relevant if this event - // is of KEYPRESS type, but for a mode key like hankaku/zenkaku or ctrl, there is no code point - // associated so this should be NOT_A_CODE_POINT to avoid unintentional use of its value when - // it's not relevant. - final public int mCodePoint; - - // If applicable, this contains the string that should be input. - final public CharSequence mText; - - // The key code associated with the event, if relevant. This is relevant whenever this event - // has been triggered by a key press, but not for a gesture for example. This has conceptually - // no link to the code point, although keys that enter a straight code point may often set - // this to be equal to mCodePoint for convenience. If this is not a key, this must contain - // NOT_A_KEY_CODE. - final public int mKeyCode; - - // Coordinates of the touch event, if relevant. If useful, we may want to replace this with - // a MotionEvent or something in the future. This is only relevant when the keypress is from - // a software keyboard obviously, unless there are touch-sensitive hardware keyboards in the - // future or some other awesome sauce. - final public int mX; - final public int mY; - - // Some flags that can't go into the key code. It's a bit field of FLAG_* - final private int mFlags; - - // If this is of type EVENT_TYPE_SUGGESTION_PICKED, this must not be null (and must be null in - // other cases). - final public SuggestedWordInfo mSuggestedWordInfo; - - // The next event, if any. Null if there is no next event yet. - final public Event mNextEvent; - - // This method is private - to create a new event, use one of the create* utility methods. - private Event(final int type, final CharSequence text, final int codePoint, final int keyCode, - final int x, final int y, final SuggestedWordInfo suggestedWordInfo, final int flags, - final Event next) { - mEventType = type; - mText = text; - mCodePoint = codePoint; - mKeyCode = keyCode; - mX = x; - mY = y; - mSuggestedWordInfo = suggestedWordInfo; - mFlags = flags; - mNextEvent = next; - // Validity checks - // mSuggestedWordInfo is non-null if and only if the type is SUGGESTION_PICKED - if (EVENT_TYPE_SUGGESTION_PICKED == mEventType) { - if (null == mSuggestedWordInfo) { - throw new RuntimeException("Wrong event: SUGGESTION_PICKED event must have a " - + "non-null SuggestedWordInfo"); - } - } else { - if (null != mSuggestedWordInfo) { - throw new RuntimeException("Wrong event: only SUGGESTION_PICKED events may have " + - "a non-null SuggestedWordInfo"); - } - } - } - - @Nonnull - public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode, - final int x, final int y, final boolean isKeyRepeat) { - return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, x, y, - null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, null); - } - - @Nonnull - public static Event createHardwareKeypressEvent(final int codePoint, final int keyCode, - final Event next, final boolean isKeyRepeat) { - return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, - Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE, - null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, next); - } - - // This creates an input event for a dead character. @see {@link #FLAG_DEAD} - @ExternallyReferenced - @Nonnull - public static Event createDeadEvent(final int codePoint, final int keyCode, final Event next) { - // TODO: add an argument or something if we ever create a software layout with dead keys. - return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, - Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE, - null /* suggestedWordInfo */, FLAG_DEAD, next); - } - - /** - * Create an input event with nothing but a code point. This is the most basic possible input - * event; it contains no information on many things the IME requires to function correctly, - * so avoid using it unless really nothing is known about this input. - * @param codePoint the code point. - * @return an event for this code point. - */ - @Nonnull - public static Event createEventForCodePointFromUnknownSource(final int codePoint) { - // TODO: should we have a different type of event for this? After all, it's not a key press. - return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, - null /* suggestedWordInfo */, FLAG_NONE, null /* next */); - } - - /** - * Creates an input event with a code point and x, y coordinates. This is typically used when - * resuming a previously-typed word, when the coordinates are still known. - * @param codePoint the code point to input. - * @param x the X coordinate. - * @param y the Y coordinate. - * @return an event for this code point and coordinates. - */ - @Nonnull - public static Event createEventForCodePointFromAlreadyTypedText(final int codePoint, - final int x, final int y) { - // TODO: should we have a different type of event for this? After all, it's not a key press. - return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE, - x, y, null /* suggestedWordInfo */, FLAG_NONE, null /* next */); - } - - /** - * Creates an input event representing the manual pick of a suggestion. - * @return an event for this suggestion pick. - */ - @Nonnull - public static Event createSuggestionPickedEvent(final SuggestedWordInfo suggestedWordInfo) { - return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, - NOT_A_CODE_POINT, NOT_A_KEY_CODE, - Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE, - suggestedWordInfo, FLAG_NONE, null /* next */); - } - - /** - * Creates an input event with a CharSequence. This is used by some software processes whose - * output is a string, possibly with styling. Examples include press on a multi-character key, - * or combination that outputs a string. - * @param text the CharSequence associated with this event. - * @param keyCode the key code, or NOT_A_KEYCODE if not applicable. - * @return an event for this text. - */ - @Nonnull - public static Event createSoftwareTextEvent(final CharSequence text, final int keyCode) { - return new Event(EVENT_TYPE_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, - null /* suggestedWordInfo */, FLAG_NONE, null /* next */); - } - - /** - * Creates an input event representing the manual pick of a punctuation suggestion. - * @return an event for this suggestion pick. - */ - @Nonnull - public static Event createPunctuationSuggestionPickedEvent( - final SuggestedWordInfo suggestedWordInfo) { - final int primaryCode = suggestedWordInfo.mWord.charAt(0); - return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, primaryCode, - NOT_A_KEY_CODE, Constants.SUGGESTION_STRIP_COORDINATE, - Constants.SUGGESTION_STRIP_COORDINATE, suggestedWordInfo, FLAG_NONE, - null /* next */); - } - - /** - * Creates an input event representing moving the cursor. The relative move amount is stored - * in mX. - * @param moveAmount the relative move amount. - * @return an event for this cursor move. - */ - @Nonnull - public static Event createCursorMovedEvent(final int moveAmount) { - return new Event(EVENT_TYPE_CURSOR_MOVE, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, - moveAmount, Constants.NOT_A_COORDINATE, null, FLAG_NONE, null); - } - - /** - * Creates an event identical to the passed event, but that has already been consumed. - * @param source the event to copy the properties of. - * @return an identical event marked as consumed. - */ - @Nonnull - public static Event createConsumedEvent(final Event source) { - // A consumed event should not input any text at all, so we pass the empty string as text. - return new Event(source.mEventType, source.mText, source.mCodePoint, source.mKeyCode, - source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags | FLAG_CONSUMED, - source.mNextEvent); - } - - @Nonnull - public static Event createNotHandledEvent() { - return new Event(EVENT_TYPE_NOT_HANDLED, null /* text */, NOT_A_CODE_POINT, NOT_A_KEY_CODE, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, - null /* suggestedWordInfo */, FLAG_NONE, null); - } - - // Returns whether this is a function key like backspace, ctrl, settings... as opposed to keys - // that result in input like letters or space. - public boolean isFunctionalKeyEvent() { - // This logic may need to be refined in the future - return NOT_A_CODE_POINT == mCodePoint; - } - - // Returns whether this event is for a dead character. @see {@link #FLAG_DEAD} - public boolean isDead() { - return 0 != (FLAG_DEAD & mFlags); - } - - public boolean isKeyRepeat() { - return 0 != (FLAG_REPEAT & mFlags); - } - - public boolean isConsumed() { return 0 != (FLAG_CONSUMED & mFlags); } - - public boolean isGesture() { return EVENT_TYPE_GESTURE == mEventType; } - - // Returns whether this is a fake key press from the suggestion strip. This happens with - // punctuation signs selected from the suggestion strip. - public boolean isSuggestionStripPress() { - return EVENT_TYPE_SUGGESTION_PICKED == mEventType; - } - - public boolean isHandled() { - return EVENT_TYPE_NOT_HANDLED != mEventType; - } - - public CharSequence getTextToCommit() { - if (isConsumed()) { - return ""; // A consumed event should input no text. - } - switch (mEventType) { - case EVENT_TYPE_MODE_KEY: - case EVENT_TYPE_NOT_HANDLED: - case EVENT_TYPE_TOGGLE: - case EVENT_TYPE_CURSOR_MOVE: - return ""; - case EVENT_TYPE_INPUT_KEYPRESS: - return StringUtils.newSingleCodePointString(mCodePoint); - case EVENT_TYPE_GESTURE: - case EVENT_TYPE_SOFTWARE_GENERATED_STRING: - case EVENT_TYPE_SUGGESTION_PICKED: - return mText; - } - throw new RuntimeException("Unknown event type: " + mEventType); - } -} diff --git a/java/src/com/android/inputmethod/event/EventDecoder.java b/java/src/com/android/inputmethod/event/EventDecoder.java deleted file mode 100644 index 7ff0166a3..000000000 --- a/java/src/com/android/inputmethod/event/EventDecoder.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.event; - -/** - * A generic interface for event decoders. - */ -public interface EventDecoder { - -} diff --git a/java/src/com/android/inputmethod/event/HardwareEventDecoder.java b/java/src/com/android/inputmethod/event/HardwareEventDecoder.java deleted file mode 100644 index 6a6bd7bc5..000000000 --- a/java/src/com/android/inputmethod/event/HardwareEventDecoder.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.event; - -import android.view.KeyEvent; - -/** - * An event decoder for hardware events. - */ -public interface HardwareEventDecoder extends EventDecoder { - public Event decodeHardwareKey(final KeyEvent keyEvent); -} diff --git a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java deleted file mode 100644 index 3a4097d7f..000000000 --- a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.event; - -import android.view.KeyCharacterMap; -import android.view.KeyEvent; - -import com.android.inputmethod.latin.common.Constants; - -/** - * A hardware event decoder for a hardware qwerty-ish keyboard. - * - * The events are always hardware keypresses, but they can be key down or key up events, they - * can be dead keys, they can be meta keys like shift or ctrl... This does not deal with - * 10-key like keyboards; a different decoder is used for this. - */ -public class HardwareKeyboardEventDecoder implements HardwareEventDecoder { - final int mDeviceId; - - public HardwareKeyboardEventDecoder(final int deviceId) { - mDeviceId = deviceId; - // TODO: get the layout for this hardware keyboard - } - - @Override - public Event decodeHardwareKey(final KeyEvent keyEvent) { - // KeyEvent#getUnicodeChar() does not exactly returns a unicode char, but rather a value - // that includes both the unicode char in the lower 21 bits and flags in the upper bits, - // hence the name "codePointAndFlags". {@see KeyEvent#getUnicodeChar()} for more info. - final int codePointAndFlags = keyEvent.getUnicodeChar(); - // The keyCode is the abstraction used by the KeyEvent to represent different keys that - // do not necessarily map to a unicode character. This represents a physical key, like - // the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock. - final int keyCode = keyEvent.getKeyCode(); - final boolean isKeyRepeat = (0 != keyEvent.getRepeatCount()); - if (KeyEvent.KEYCODE_DEL == keyCode) { - return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT, Constants.CODE_DELETE, - null /* next */, isKeyRepeat); - } - if (keyEvent.isPrintingKey() || KeyEvent.KEYCODE_SPACE == keyCode - || KeyEvent.KEYCODE_ENTER == keyCode) { - if (0 != (codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT)) { - // A dead key. - return Event.createDeadEvent( - codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT_MASK, keyCode, - null /* next */); - } - if (KeyEvent.KEYCODE_ENTER == keyCode) { - // The Enter key. If the Shift key is not being pressed, this should send a - // CODE_ENTER to trigger the action if any, or a carriage return otherwise. If the - // Shift key is being pressed, this should send a CODE_SHIFT_ENTER and let - // Latin IME decide what to do with it. - if (keyEvent.isShiftPressed()) { - return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT, - Constants.CODE_SHIFT_ENTER, null /* next */, isKeyRepeat); - } - return Event.createHardwareKeypressEvent(Constants.CODE_ENTER, keyCode, - null /* next */, isKeyRepeat); - } - // If not Enter, then this is just a regular keypress event for a normal character - // that can be committed right away, taking into account the current state. - return Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, null /* next */, - isKeyRepeat); - } - return Event.createNotHandledEvent(); - } -} diff --git a/java/src/com/android/inputmethod/event/InputTransaction.java b/java/src/com/android/inputmethod/event/InputTransaction.java deleted file mode 100644 index 5bc9111de..000000000 --- a/java/src/com/android/inputmethod/event/InputTransaction.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.event; - -import com.android.inputmethod.latin.settings.SettingsValues; - -/** - * An object encapsulating a single transaction for input. - */ -public class InputTransaction { - // UPDATE_LATER is stronger than UPDATE_NOW. The reason for this is, if we have to update later, - // it's because something will change that we can't evaluate now, which means that even if we - // re-evaluate now we'll have to do it again later. The only case where that wouldn't apply - // would be if we needed to update now to find out the new state right away, but then we - // can't do it with this deferred mechanism anyway. - public static final int SHIFT_NO_UPDATE = 0; - public static final int SHIFT_UPDATE_NOW = 1; - public static final int SHIFT_UPDATE_LATER = 2; - - // Initial conditions - public final SettingsValues mSettingsValues; - public final Event mEvent; - public final long mTimestamp; - public final int mSpaceState; - public final int mShiftState; - - // Outputs - private int mRequiredShiftUpdate = SHIFT_NO_UPDATE; - private boolean mRequiresUpdateSuggestions = false; - private boolean mDidAffectContents = false; - private boolean mDidAutoCorrect = false; - - public InputTransaction(final SettingsValues settingsValues, final Event event, - final long timestamp, final int spaceState, final int shiftState) { - mSettingsValues = settingsValues; - mEvent = event; - mTimestamp = timestamp; - mSpaceState = spaceState; - mShiftState = shiftState; - } - - /** - * Indicate that this transaction requires some type of shift update. - * @param updateType What type of shift update this requires. - */ - public void requireShiftUpdate(final int updateType) { - mRequiredShiftUpdate = Math.max(mRequiredShiftUpdate, updateType); - } - - /** - * Gets what type of shift update this transaction requires. - * @return The shift update type. - */ - public int getRequiredShiftUpdate() { - return mRequiredShiftUpdate; - } - - /** - * Indicate that this transaction requires updating the suggestions. - */ - public void setRequiresUpdateSuggestions() { - mRequiresUpdateSuggestions = true; - } - - /** - * Find out whether this transaction requires updating the suggestions. - * @return Whether this transaction requires updating the suggestions. - */ - public boolean requiresUpdateSuggestions() { - return mRequiresUpdateSuggestions; - } - - /** - * Indicate that this transaction affected the contents of the editor. - */ - public void setDidAffectContents() { - mDidAffectContents = true; - } - - /** - * Find out whether this transaction affected contents of the editor. - * @return Whether this transaction affected contents of the editor. - */ - public boolean didAffectContents() { - return mDidAffectContents; - } - - /** - * Indicate that this transaction performed an auto-correction. - */ - public void setDidAutoCorrect() { - mDidAutoCorrect = true; - } - - /** - * Find out whether this transaction performed an auto-correction. - * @return Whether this transaction performed an auto-correction. - */ - public boolean didAutoCorrect() { - return mDidAutoCorrect; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java deleted file mode 100644 index 299d1b7c5..000000000 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ /dev/null @@ -1,1022 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard; - -import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; -import static com.android.inputmethod.latin.common.Constants.CODE_OUTPUT_TEXT; -import static com.android.inputmethod.latin.common.Constants.CODE_SHIFT; -import static com.android.inputmethod.latin.common.Constants.CODE_SWITCH_ALPHA_SYMBOL; -import static com.android.inputmethod.latin.common.Constants.CODE_UNSPECIFIED; - -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; - -import com.android.inputmethod.keyboard.internal.KeyDrawParams; -import com.android.inputmethod.keyboard.internal.KeySpecParser; -import com.android.inputmethod.keyboard.internal.KeyStyle; -import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; -import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; -import com.android.inputmethod.keyboard.internal.KeyboardParams; -import com.android.inputmethod.keyboard.internal.KeyboardRow; -import com.android.inputmethod.keyboard.internal.MoreKeySpec; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; - -import java.util.Arrays; -import java.util.Locale; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Class for describing the position and characteristics of a single key in the keyboard. - */ -public class Key implements Comparable<Key> { - /** - * The key code (unicode or custom code) that this key generates. - */ - private final int mCode; - - /** Label to display */ - private final String mLabel; - /** Hint label to display on the key in conjunction with the label */ - private final String mHintLabel; - /** Flags of the label */ - private final int mLabelFlags; - private static final int LABEL_FLAGS_ALIGN_HINT_LABEL_TO_BOTTOM = 0x02; - private static final int LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM = 0x04; - private static final int LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER = 0x08; - // Font typeface specification. - private static final int LABEL_FLAGS_FONT_MASK = 0x30; - private static final int LABEL_FLAGS_FONT_NORMAL = 0x10; - private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20; - private static final int LABEL_FLAGS_FONT_DEFAULT = 0x30; - // Start of key text ratio enum values - private static final int LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK = 0x1C0; - private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO = 0x40; - private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80; - private static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0; - private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x140; - // End of key text ratio mask enum values - private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200; - private static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400; - private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800; - // The bit to calculate the ratio of key label width against key width. If autoXScale bit is on - // and autoYScale bit is off, the key label may be shrunk only for X-direction. - // If both autoXScale and autoYScale bits are on, the key label text size may be auto scaled. - private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000; - private static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000; - private static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE - | LABEL_FLAGS_AUTO_Y_SCALE; - private static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000; - private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000; - private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000; - private static final int LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR = 0x80000; - private static final int LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO = 0x100000; - private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000; - private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000; - - /** Icon to display instead of a label. Icon takes precedence over a label */ - private final int mIconId; - - /** Width of the key, excluding the gap */ - private final int mWidth; - /** Height of the key, excluding the gap */ - private final int mHeight; - /** - * The combined width in pixels of the horizontal gaps belonging to this key, both to the left - * and to the right. I.e., mWidth + mHorizontalGap = total width belonging to the key. - */ - private final int mHorizontalGap; - /** - * The combined height in pixels of the vertical gaps belonging to this key, both above and - * below. I.e., mHeight + mVerticalGap = total height belonging to the key. - */ - private final int mVerticalGap; - /** X coordinate of the top-left corner of the key in the keyboard layout, excluding the gap. */ - private final int mX; - /** Y coordinate of the top-left corner of the key in the keyboard layout, excluding the gap. */ - private final int mY; - /** Hit bounding box of the key */ - @Nonnull - private final Rect mHitBox = new Rect(); - - /** More keys. It is guaranteed that this is null or an array of one or more elements */ - @Nullable - private final MoreKeySpec[] mMoreKeys; - /** More keys column number and flags */ - private final int mMoreKeysColumnAndFlags; - private static final int MORE_KEYS_COLUMN_NUMBER_MASK = 0x000000ff; - // If this flag is specified, more keys keyboard should have the specified number of columns. - // Otherwise more keys keyboard should have less than or equal to the specified maximum number - // of columns. - private static final int MORE_KEYS_FLAGS_FIXED_COLUMN = 0x00000100; - // If this flag is specified, the order of more keys is determined by the order in the more - // keys' specification. Otherwise the order of more keys is automatically determined. - private static final int MORE_KEYS_FLAGS_FIXED_ORDER = 0x00000200; - private static final int MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER = 0; - private static final int MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER = - MORE_KEYS_FLAGS_FIXED_COLUMN; - private static final int MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER = - (MORE_KEYS_FLAGS_FIXED_COLUMN | MORE_KEYS_FLAGS_FIXED_ORDER); - private static final int MORE_KEYS_FLAGS_HAS_LABELS = 0x40000000; - private static final int MORE_KEYS_FLAGS_NEEDS_DIVIDERS = 0x20000000; - private static final int MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY = 0x10000000; - // TODO: Rename these specifiers to !autoOrder! and !fixedOrder! respectively. - private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!"; - private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!"; - private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!"; - private static final String MORE_KEYS_NEEDS_DIVIDERS = "!needsDividers!"; - private static final String MORE_KEYS_NO_PANEL_AUTO_MORE_KEY = "!noPanelAutoMoreKey!"; - - /** Background type that represents different key background visual than normal one. */ - private final int mBackgroundType; - public static final int BACKGROUND_TYPE_EMPTY = 0; - public static final int BACKGROUND_TYPE_NORMAL = 1; - public static final int BACKGROUND_TYPE_FUNCTIONAL = 2; - public static final int BACKGROUND_TYPE_STICKY_OFF = 3; - public static final int BACKGROUND_TYPE_STICKY_ON = 4; - public static final int BACKGROUND_TYPE_ACTION = 5; - public static final int BACKGROUND_TYPE_SPACEBAR = 6; - - private final int mActionFlags; - private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01; - private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02; - private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04; - private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08; - - @Nullable - private final KeyVisualAttributes mKeyVisualAttributes; - @Nullable - private final OptionalAttributes mOptionalAttributes; - - private static final class OptionalAttributes { - /** Text to output when pressed. This can be multiple characters, like ".com" */ - public final String mOutputText; - public final int mAltCode; - /** Icon for disabled state */ - public final int mDisabledIconId; - /** The visual insets */ - public final int mVisualInsetsLeft; - public final int mVisualInsetsRight; - - private OptionalAttributes(final String outputText, final int altCode, - final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight) { - mOutputText = outputText; - mAltCode = altCode; - mDisabledIconId = disabledIconId; - mVisualInsetsLeft = visualInsetsLeft; - mVisualInsetsRight = visualInsetsRight; - } - - @Nullable - public static OptionalAttributes newInstance(final String outputText, final int altCode, - final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight) { - if (outputText == null && altCode == CODE_UNSPECIFIED - && disabledIconId == ICON_UNDEFINED && visualInsetsLeft == 0 - && visualInsetsRight == 0) { - return null; - } - return new OptionalAttributes(outputText, altCode, disabledIconId, visualInsetsLeft, - visualInsetsRight); - } - } - - private final int mHashCode; - - /** The current pressed state of this key */ - private boolean mPressed; - /** Key is enabled and responds on press */ - private boolean mEnabled = true; - - /** - * Constructor for a key on <code>MoreKeyKeyboard</code>, on <code>MoreSuggestions</code>, - * and in a <GridRows/>. - */ - public Key(@Nullable final String label, final int iconId, final int code, - @Nullable final String outputText, @Nullable final String hintLabel, - final int labelFlags, final int backgroundType, final int x, final int y, - final int width, final int height, final int horizontalGap, final int verticalGap) { - mWidth = width - horizontalGap; - mHeight = height - verticalGap; - mHorizontalGap = horizontalGap; - mVerticalGap = verticalGap; - mHintLabel = hintLabel; - mLabelFlags = labelFlags; - mBackgroundType = backgroundType; - // TODO: Pass keyActionFlags as an argument. - mActionFlags = ACTION_FLAGS_NO_KEY_PREVIEW; - mMoreKeys = null; - mMoreKeysColumnAndFlags = 0; - mLabel = label; - mOptionalAttributes = OptionalAttributes.newInstance(outputText, CODE_UNSPECIFIED, - ICON_UNDEFINED, 0 /* visualInsetsLeft */, 0 /* visualInsetsRight */); - mCode = code; - mEnabled = (code != CODE_UNSPECIFIED); - mIconId = iconId; - // Horizontal gap is divided equally to both sides of the key. - mX = x + mHorizontalGap / 2; - mY = y; - mHitBox.set(x, y, x + width + 1, y + height); - mKeyVisualAttributes = null; - - mHashCode = computeHashCode(this); - } - - /** - * Create a key with the given top-left coordinate and extract its attributes from a key - * specification string, Key attribute array, key style, and etc. - * - * @param keySpec the key specification. - * @param keyAttr the Key XML attributes array. - * @param style the {@link KeyStyle} of this key. - * @param params the keyboard building parameters. - * @param row the row that this key belongs to. row's x-coordinate will be the right edge of - * this key. - */ - public Key(@Nullable final String keySpec, @Nonnull final TypedArray keyAttr, - @Nonnull final KeyStyle style, @Nonnull final KeyboardParams params, - @Nonnull final KeyboardRow row) { - mHorizontalGap = isSpacer() ? 0 : params.mHorizontalGap; - mVerticalGap = params.mVerticalGap; - - final float horizontalGapFloat = mHorizontalGap; - final int rowHeight = row.getRowHeight(); - mHeight = rowHeight - mVerticalGap; - - final float keyXPos = row.getKeyX(keyAttr); - final float keyWidth = row.getKeyWidth(keyAttr, keyXPos); - final int keyYPos = row.getKeyY(); - - // Horizontal gap is divided equally to both sides of the key. - mX = Math.round(keyXPos + horizontalGapFloat / 2); - mY = keyYPos; - mWidth = Math.round(keyWidth - horizontalGapFloat); - mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1, - keyYPos + rowHeight); - // Update row to have current x coordinate. - row.setXPos(keyXPos + keyWidth); - - mBackgroundType = style.getInt(keyAttr, - R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType()); - - final int baseWidth = params.mBaseWidth; - final int visualInsetsLeft = Math.round(keyAttr.getFraction( - R.styleable.Keyboard_Key_visualInsetsLeft, baseWidth, baseWidth, 0)); - final int visualInsetsRight = Math.round(keyAttr.getFraction( - R.styleable.Keyboard_Key_visualInsetsRight, baseWidth, baseWidth, 0)); - - mLabelFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags) - | row.getDefaultKeyLabelFlags(); - final boolean needsToUpcase = needsToUpcase(mLabelFlags, params.mId.mElementId); - final Locale localeForUpcasing = params.mId.getLocale(); - int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags); - String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); - - // Get maximum column order number and set a relevant mode value. - int moreKeysColumnAndFlags = MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER - | style.getInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn, - params.mMaxMoreKeysKeyboardColumn); - int value; - if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) { - // Override with fixed column order number and set a relevant mode value. - moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER - | (value & MORE_KEYS_COLUMN_NUMBER_MASK); - } - if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) { - // Override with fixed column order number and set a relevant mode value. - moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER - | (value & MORE_KEYS_COLUMN_NUMBER_MASK); - } - if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) { - moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS; - } - if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) { - moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS; - } - if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) { - moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY; - } - mMoreKeysColumnAndFlags = moreKeysColumnAndFlags; - - final String[] additionalMoreKeys; - if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) { - additionalMoreKeys = null; - } else { - additionalMoreKeys = style.getStringArray(keyAttr, - R.styleable.Keyboard_Key_additionalMoreKeys); - } - moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys); - if (moreKeys != null) { - actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS; - mMoreKeys = new MoreKeySpec[moreKeys.length]; - for (int i = 0; i < moreKeys.length; i++) { - mMoreKeys[i] = new MoreKeySpec(moreKeys[i], needsToUpcase, localeForUpcasing); - } - } else { - mMoreKeys = null; - } - mActionFlags = actionFlags; - - mIconId = KeySpecParser.getIconId(keySpec); - final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr, - R.styleable.Keyboard_Key_keyIconDisabled)); - - final int code = KeySpecParser.getCode(keySpec); - if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) { - mLabel = params.mId.mCustomActionLabel; - } else if (code >= Character.MIN_SUPPLEMENTARY_CODE_POINT) { - // This is a workaround to have a key that has a supplementary code point in its label. - // Because we can put a string in resource neither as a XML entity of a supplementary - // code point nor as a surrogate pair. - mLabel = new StringBuilder().appendCodePoint(code).toString(); - } else { - final String label = KeySpecParser.getLabel(keySpec); - mLabel = needsToUpcase - ? StringUtils.toTitleCaseOfKeyLabel(label, localeForUpcasing) - : label; - } - if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) { - mHintLabel = null; - } else { - final String hintLabel = style.getString( - keyAttr, R.styleable.Keyboard_Key_keyHintLabel); - mHintLabel = needsToUpcase - ? StringUtils.toTitleCaseOfKeyLabel(hintLabel, localeForUpcasing) - : hintLabel; - } - String outputText = KeySpecParser.getOutputText(keySpec); - if (needsToUpcase) { - outputText = StringUtils.toTitleCaseOfKeyLabel(outputText, localeForUpcasing); - } - // Choose the first letter of the label as primary code if not specified. - if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText) - && !TextUtils.isEmpty(mLabel)) { - if (StringUtils.codePointCount(mLabel) == 1) { - // Use the first letter of the hint label if shiftedLetterActivated flag is - // specified. - if (hasShiftedLetterHint() && isShiftedLetterActivated()) { - mCode = mHintLabel.codePointAt(0); - } else { - mCode = mLabel.codePointAt(0); - } - } else { - // In some locale and case, the character might be represented by multiple code - // points, such as upper case Eszett of German alphabet. - outputText = mLabel; - mCode = CODE_OUTPUT_TEXT; - } - } else if (code == CODE_UNSPECIFIED && outputText != null) { - if (StringUtils.codePointCount(outputText) == 1) { - mCode = outputText.codePointAt(0); - outputText = null; - } else { - mCode = CODE_OUTPUT_TEXT; - } - } else { - mCode = needsToUpcase ? StringUtils.toTitleCaseOfKeyCode(code, localeForUpcasing) - : code; - } - final int altCodeInAttr = KeySpecParser.parseCode( - style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), CODE_UNSPECIFIED); - final int altCode = needsToUpcase - ? StringUtils.toTitleCaseOfKeyCode(altCodeInAttr, localeForUpcasing) - : altCodeInAttr; - mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode, - disabledIconId, visualInsetsLeft, visualInsetsRight); - mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); - mHashCode = computeHashCode(this); - } - - /** - * Copy constructor for DynamicGridKeyboard.GridKey. - * - * @param key the original key. - */ - protected Key(@Nonnull final Key key) { - this(key, key.mMoreKeys); - } - - private Key(@Nonnull final Key key, @Nullable final MoreKeySpec[] moreKeys) { - // Final attributes. - mCode = key.mCode; - mLabel = key.mLabel; - mHintLabel = key.mHintLabel; - mLabelFlags = key.mLabelFlags; - mIconId = key.mIconId; - mWidth = key.mWidth; - mHeight = key.mHeight; - mHorizontalGap = key.mHorizontalGap; - mVerticalGap = key.mVerticalGap; - mX = key.mX; - mY = key.mY; - mHitBox.set(key.mHitBox); - mMoreKeys = moreKeys; - mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags; - mBackgroundType = key.mBackgroundType; - mActionFlags = key.mActionFlags; - mKeyVisualAttributes = key.mKeyVisualAttributes; - mOptionalAttributes = key.mOptionalAttributes; - mHashCode = key.mHashCode; - // Key state. - mPressed = key.mPressed; - mEnabled = key.mEnabled; - } - - @Nonnull - public static Key removeRedundantMoreKeys(@Nonnull final Key key, - @Nonnull final MoreKeySpec.LettersOnBaseLayout lettersOnBaseLayout) { - final MoreKeySpec[] moreKeys = key.getMoreKeys(); - final MoreKeySpec[] filteredMoreKeys = MoreKeySpec.removeRedundantMoreKeys( - moreKeys, lettersOnBaseLayout); - return (filteredMoreKeys == moreKeys) ? key : new Key(key, filteredMoreKeys); - } - - private static boolean needsToUpcase(final int labelFlags, final int keyboardElementId) { - if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false; - switch (keyboardElementId) { - case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: - case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: - case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: - case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: - return true; - default: - return false; - } - } - - private static int computeHashCode(final Key key) { - return Arrays.hashCode(new Object[] { - key.mX, - key.mY, - key.mWidth, - key.mHeight, - key.mCode, - key.mLabel, - key.mHintLabel, - key.mIconId, - key.mBackgroundType, - Arrays.hashCode(key.mMoreKeys), - key.getOutputText(), - key.mActionFlags, - key.mLabelFlags, - // Key can be distinguishable without the following members. - // key.mOptionalAttributes.mAltCode, - // key.mOptionalAttributes.mDisabledIconId, - // key.mOptionalAttributes.mPreviewIconId, - // key.mHorizontalGap, - // key.mVerticalGap, - // key.mOptionalAttributes.mVisualInsetLeft, - // key.mOptionalAttributes.mVisualInsetRight, - // key.mMaxMoreKeysColumn, - }); - } - - private boolean equalsInternal(final Key o) { - if (this == o) return true; - return o.mX == mX - && o.mY == mY - && o.mWidth == mWidth - && o.mHeight == mHeight - && o.mCode == mCode - && TextUtils.equals(o.mLabel, mLabel) - && TextUtils.equals(o.mHintLabel, mHintLabel) - && o.mIconId == mIconId - && o.mBackgroundType == mBackgroundType - && Arrays.equals(o.mMoreKeys, mMoreKeys) - && TextUtils.equals(o.getOutputText(), getOutputText()) - && o.mActionFlags == mActionFlags - && o.mLabelFlags == mLabelFlags; - } - - @Override - public int compareTo(Key o) { - if (equalsInternal(o)) return 0; - if (mHashCode > o.mHashCode) return 1; - return -1; - } - - @Override - public int hashCode() { - return mHashCode; - } - - @Override - public boolean equals(final Object o) { - return o instanceof Key && equalsInternal((Key)o); - } - - @Override - public String toString() { - return toShortString() + " " + getX() + "," + getY() + " " + getWidth() + "x" + getHeight(); - } - - public String toShortString() { - final int code = getCode(); - if (code == Constants.CODE_OUTPUT_TEXT) { - return getOutputText(); - } - return Constants.printableCode(code); - } - - public String toLongString() { - final int iconId = getIconId(); - final String topVisual = (iconId == KeyboardIconsSet.ICON_UNDEFINED) - ? KeyboardIconsSet.PREFIX_ICON + KeyboardIconsSet.getIconName(iconId) : getLabel(); - final String hintLabel = getHintLabel(); - final String visual = (hintLabel == null) ? topVisual : topVisual + "^" + hintLabel; - return toString() + " " + visual + "/" + backgroundName(mBackgroundType); - } - - private static String backgroundName(final int backgroundType) { - switch (backgroundType) { - case BACKGROUND_TYPE_EMPTY: return "empty"; - case BACKGROUND_TYPE_NORMAL: return "normal"; - case BACKGROUND_TYPE_FUNCTIONAL: return "functional"; - case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff"; - case BACKGROUND_TYPE_STICKY_ON: return "stickyOn"; - case BACKGROUND_TYPE_ACTION: return "action"; - case BACKGROUND_TYPE_SPACEBAR: return "spacebar"; - default: return null; - } - } - - public int getCode() { - return mCode; - } - - @Nullable - public String getLabel() { - return mLabel; - } - - @Nullable - public String getHintLabel() { - return mHintLabel; - } - - @Nullable - public MoreKeySpec[] getMoreKeys() { - return mMoreKeys; - } - - public void markAsLeftEdge(final KeyboardParams params) { - mHitBox.left = params.mLeftPadding; - } - - public void markAsRightEdge(final KeyboardParams params) { - mHitBox.right = params.mOccupiedWidth - params.mRightPadding; - } - - public void markAsTopEdge(final KeyboardParams params) { - mHitBox.top = params.mTopPadding; - } - - public void markAsBottomEdge(final KeyboardParams params) { - mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding; - } - - public final boolean isSpacer() { - return this instanceof Spacer; - } - - public final boolean isActionKey() { - return mBackgroundType == BACKGROUND_TYPE_ACTION; - } - - public final boolean isShift() { - return mCode == CODE_SHIFT; - } - - public final boolean isModifier() { - return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL; - } - - public final boolean isRepeatable() { - return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0; - } - - public final boolean noKeyPreview() { - return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0; - } - - public final boolean altCodeWhileTyping() { - return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0; - } - - public final boolean isLongPressEnabled() { - // We need not start long press timer on the key which has activated shifted letter. - return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0 - && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0; - } - - public KeyVisualAttributes getVisualAttributes() { - return mKeyVisualAttributes; - } - - @Nonnull - public final Typeface selectTypeface(final KeyDrawParams params) { - switch (mLabelFlags & LABEL_FLAGS_FONT_MASK) { - case LABEL_FLAGS_FONT_NORMAL: - return Typeface.DEFAULT; - case LABEL_FLAGS_FONT_MONO_SPACE: - return Typeface.MONOSPACE; - case LABEL_FLAGS_FONT_DEFAULT: - default: - // The type-face is specified by keyTypeface attribute. - return params.mTypeface; - } - } - - public final int selectTextSize(final KeyDrawParams params) { - switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) { - case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO: - return params.mLetterSize; - case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO: - return params.mLargeLetterSize; - case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO: - return params.mLabelSize; - case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO: - return params.mHintLabelSize; - default: // No follow key ratio flag specified. - return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize; - } - } - - public final int selectTextColor(final KeyDrawParams params) { - if ((mLabelFlags & LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR) != 0) { - return params.mFunctionalTextColor; - } - return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor; - } - - public final int selectHintTextSize(final KeyDrawParams params) { - if (hasHintLabel()) { - return params.mHintLabelSize; - } - if (hasShiftedLetterHint()) { - return params.mShiftedLetterHintSize; - } - return params.mHintLetterSize; - } - - public final int selectHintTextColor(final KeyDrawParams params) { - if (hasHintLabel()) { - return params.mHintLabelColor; - } - if (hasShiftedLetterHint()) { - return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor - : params.mShiftedLetterHintInactivatedColor; - } - return params.mHintLetterColor; - } - - public final int selectMoreKeyTextSize(final KeyDrawParams params) { - return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize; - } - - public final String getPreviewLabel() { - return isShiftedLetterActivated() ? mHintLabel : mLabel; - } - - private boolean previewHasLetterSize() { - return (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) != 0 - || StringUtils.codePointCount(getPreviewLabel()) == 1; - } - - public final int selectPreviewTextSize(final KeyDrawParams params) { - if (previewHasLetterSize()) { - return params.mPreviewTextSize; - } - return params.mLetterSize; - } - - @Nonnull - public Typeface selectPreviewTypeface(final KeyDrawParams params) { - if (previewHasLetterSize()) { - return selectTypeface(params); - } - return Typeface.DEFAULT_BOLD; - } - - public final boolean isAlignHintLabelToBottom(final int defaultFlags) { - return ((mLabelFlags | defaultFlags) & LABEL_FLAGS_ALIGN_HINT_LABEL_TO_BOTTOM) != 0; - } - - public final boolean isAlignIconToBottom() { - return (mLabelFlags & LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM) != 0; - } - - public final boolean isAlignLabelOffCenter() { - return (mLabelFlags & LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER) != 0; - } - - public final boolean hasPopupHint() { - return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0; - } - - public final boolean hasShiftedLetterHint() { - return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0 - && !TextUtils.isEmpty(mHintLabel); - } - - public final boolean hasHintLabel() { - return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0; - } - - public final boolean needsAutoXScale() { - return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0; - } - - public final boolean needsAutoScale() { - return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE; - } - - public final boolean needsToKeepBackgroundAspectRatio(final int defaultFlags) { - return ((mLabelFlags | defaultFlags) & LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO) != 0; - } - - public final boolean hasCustomActionLabel() { - return (mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0; - } - - private final boolean isShiftedLetterActivated() { - return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0 - && !TextUtils.isEmpty(mHintLabel); - } - - public final int getMoreKeysColumnNumber() { - return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_NUMBER_MASK; - } - - public final boolean isMoreKeysFixedColumn() { - return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN) != 0; - } - - public final boolean isMoreKeysFixedOrder() { - return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_ORDER) != 0; - } - - public final boolean hasLabelsInMoreKeys() { - return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0; - } - - public final int getMoreKeyLabelFlags() { - final int labelSizeFlag = hasLabelsInMoreKeys() - ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO - : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO; - return labelSizeFlag | LABEL_FLAGS_AUTO_X_SCALE; - } - - public final boolean needsDividersInMoreKeys() { - return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0; - } - - public final boolean hasNoPanelAutoMoreKey() { - return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0; - } - - @Nullable - public final String getOutputText() { - final OptionalAttributes attrs = mOptionalAttributes; - return (attrs != null) ? attrs.mOutputText : null; - } - - public final int getAltCode() { - final OptionalAttributes attrs = mOptionalAttributes; - return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED; - } - - public int getIconId() { - return mIconId; - } - - @Nullable - public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { - final OptionalAttributes attrs = mOptionalAttributes; - final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED; - final int iconId = mEnabled ? getIconId() : disabledIconId; - final Drawable icon = iconSet.getIconDrawable(iconId); - if (icon != null) { - icon.setAlpha(alpha); - } - return icon; - } - - @Nullable - public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) { - return iconSet.getIconDrawable(getIconId()); - } - - /** - * Gets the width of the key in pixels, excluding the gap. - * @return The width of the key in pixels, excluding the gap. - */ - public int getWidth() { - return mWidth; - } - - /** - * Gets the height of the key in pixels, excluding the gap. - * @return The height of the key in pixels, excluding the gap. - */ - public int getHeight() { - return mHeight; - } - - /** - * The combined width in pixels of the horizontal gaps belonging to this key, both above and - * below. I.e., getWidth() + getHorizontalGap() = total width belonging to the key. - * @return Horizontal gap belonging to this key. - */ - public int getHorizontalGap() { - return mHorizontalGap; - } - - /** - * The combined height in pixels of the vertical gaps belonging to this key, both above and - * below. I.e., getHeight() + getVerticalGap() = total height belonging to the key. - * @return Vertical gap belonging to this key. - */ - public int getVerticalGap() { - return mVerticalGap; - } - - /** - * Gets the x-coordinate of the top-left corner of the key in pixels, excluding the gap. - * @return The x-coordinate of the top-left corner of the key in pixels, excluding the gap. - */ - public int getX() { - return mX; - } - - /** - * Gets the y-coordinate of the top-left corner of the key in pixels, excluding the gap. - * @return The y-coordinate of the top-left corner of the key in pixels, excluding the gap. - */ - public int getY() { - return mY; - } - - public final int getDrawX() { - final int x = getX(); - final OptionalAttributes attrs = mOptionalAttributes; - return (attrs == null) ? x : x + attrs.mVisualInsetsLeft; - } - - public final int getDrawWidth() { - final OptionalAttributes attrs = mOptionalAttributes; - return (attrs == null) ? mWidth - : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight; - } - - /** - * Informs the key that it has been pressed, in case it needs to change its appearance or - * state. - * @see #onReleased() - */ - public void onPressed() { - mPressed = true; - } - - /** - * Informs the key that it has been released, in case it needs to change its appearance or - * state. - * @see #onPressed() - */ - public void onReleased() { - mPressed = false; - } - - public final boolean isEnabled() { - return mEnabled; - } - - public void setEnabled(final boolean enabled) { - mEnabled = enabled; - } - - @Nonnull - public Rect getHitBox() { - return mHitBox; - } - - /** - * Detects if a point falls on this key. - * @param x the x-coordinate of the point - * @param y the y-coordinate of the point - * @return whether or not the point falls on the key. If the key is attached to an edge, it - * will assume that all points between the key and the edge are considered to be on the key. - * @see #markAsLeftEdge(KeyboardParams) etc. - */ - public boolean isOnKey(final int x, final int y) { - return mHitBox.contains(x, y); - } - - /** - * Returns the square of the distance to the nearest edge of the key and the given point. - * @param x the x-coordinate of the point - * @param y the y-coordinate of the point - * @return the square of the distance of the point from the nearest edge of the key - */ - public int squaredDistanceToEdge(final int x, final int y) { - final int left = getX(); - final int right = left + mWidth; - final int top = getY(); - final int bottom = top + mHeight; - final int edgeX = x < left ? left : (x > right ? right : x); - final int edgeY = y < top ? top : (y > bottom ? bottom : y); - final int dx = x - edgeX; - final int dy = y - edgeY; - return dx * dx + dy * dy; - } - - static class KeyBackgroundState { - private final int[] mReleasedState; - private final int[] mPressedState; - - private KeyBackgroundState(final int ... attrs) { - mReleasedState = attrs; - mPressedState = Arrays.copyOf(attrs, attrs.length + 1); - mPressedState[attrs.length] = android.R.attr.state_pressed; - } - - public int[] getState(final boolean pressed) { - return pressed ? mPressedState : mReleasedState; - } - - public static final KeyBackgroundState[] STATES = { - // 0: BACKGROUND_TYPE_EMPTY - new KeyBackgroundState(android.R.attr.state_empty), - // 1: BACKGROUND_TYPE_NORMAL - new KeyBackgroundState(), - // 2: BACKGROUND_TYPE_FUNCTIONAL - new KeyBackgroundState(), - // 3: BACKGROUND_TYPE_STICKY_OFF - new KeyBackgroundState(android.R.attr.state_checkable), - // 4: BACKGROUND_TYPE_STICKY_ON - new KeyBackgroundState(android.R.attr.state_checkable, android.R.attr.state_checked), - // 5: BACKGROUND_TYPE_ACTION - new KeyBackgroundState(android.R.attr.state_active), - // 6: BACKGROUND_TYPE_SPACEBAR - new KeyBackgroundState(), - }; - } - - /** - * Returns the background drawable for the key, based on the current state and type of the key. - * @return the background drawable of the key. - * @see android.graphics.drawable.StateListDrawable#setState(int[]) - */ - @Nonnull - public final Drawable selectBackgroundDrawable(@Nonnull final Drawable keyBackground, - @Nonnull final Drawable functionalKeyBackground, - @Nonnull final Drawable spacebarBackground) { - final Drawable background; - if (mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) { - background = functionalKeyBackground; - } else if (mBackgroundType == BACKGROUND_TYPE_SPACEBAR) { - background = spacebarBackground; - } else { - background = keyBackground; - } - final int[] state = KeyBackgroundState.STATES[mBackgroundType].getState(mPressed); - background.setState(state); - return background; - } - - public static class Spacer extends Key { - public Spacer(final TypedArray keyAttr, final KeyStyle keyStyle, - final KeyboardParams params, final KeyboardRow row) { - super(null /* keySpec */, keyAttr, keyStyle, params, row); - } - - /** - * This constructor is being used only for divider in more keys keyboard. - */ - protected Spacer(final KeyboardParams params, final int x, final int y, final int width, - final int height) { - super(null /* label */, ICON_UNDEFINED, CODE_UNSPECIFIED, null /* outputText */, - null /* hintLabel */, 0 /* labelFlags */, BACKGROUND_TYPE_EMPTY, x, y, width, - height, params.mHorizontalGap, params.mVerticalGap); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java deleted file mode 100644 index 87368d4ef..000000000 --- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard; - -/** - * This class handles key detection. - */ -public class KeyDetector { - private final int mKeyHysteresisDistanceSquared; - private final int mKeyHysteresisDistanceForSlidingModifierSquared; - - private Keyboard mKeyboard; - private int mCorrectionX; - private int mCorrectionY; - - public KeyDetector() { - this(0.0f /* keyHysteresisDistance */, 0.0f /* keyHysteresisDistanceForSlidingModifier */); - } - - /** - * Key detection object constructor with key hysteresis distances. - * - * @param keyHysteresisDistance if the pointer movement distance is smaller than this, the - * movement will not be handled as meaningful movement. The unit is pixel. - * @param keyHysteresisDistanceForSlidingModifier the same parameter for sliding input that - * starts from a modifier key such as shift and symbols key. - */ - public KeyDetector(final float keyHysteresisDistance, - final float keyHysteresisDistanceForSlidingModifier) { - mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance); - mKeyHysteresisDistanceForSlidingModifierSquared = (int)( - keyHysteresisDistanceForSlidingModifier * keyHysteresisDistanceForSlidingModifier); - } - - public void setKeyboard(final Keyboard keyboard, final float correctionX, - final float correctionY) { - if (keyboard == null) { - throw new NullPointerException(); - } - mCorrectionX = (int)correctionX; - mCorrectionY = (int)correctionY; - mKeyboard = keyboard; - } - - public int getKeyHysteresisDistanceSquared(final boolean isSlidingFromModifier) { - return isSlidingFromModifier - ? mKeyHysteresisDistanceForSlidingModifierSquared : mKeyHysteresisDistanceSquared; - } - - public int getTouchX(final int x) { - return x + mCorrectionX; - } - - // TODO: Remove vertical correction. - public int getTouchY(final int y) { - return y + mCorrectionY; - } - - public Keyboard getKeyboard() { - return mKeyboard; - } - - public boolean alwaysAllowsKeySelectionByDraggingFinger() { - return false; - } - - /** - * Detect the key whose hitbox the touch point is in. - * - * @param x The x-coordinate of a touch point - * @param y The y-coordinate of a touch point - * @return the key that the touch point hits. - */ - public Key detectHitKey(final int x, final int y) { - if (mKeyboard == null) { - return null; - } - final int touchX = getTouchX(x); - final int touchY = getTouchY(y); - - int minDistance = Integer.MAX_VALUE; - Key primaryKey = null; - for (final Key key: mKeyboard.getNearestKeys(touchX, touchY)) { - // An edge key always has its enlarged hitbox to respond to an event that occurred in - // the empty area around the key. (@see Key#markAsLeftEdge(KeyboardParams)} etc.) - if (!key.isOnKey(touchX, touchY)) { - continue; - } - final int distance = key.squaredDistanceToEdge(touchX, touchY); - if (distance > minDistance) { - continue; - } - // To take care of hitbox overlaps, we compare key's code here too. - if (primaryKey == null || distance < minDistance - || key.getCode() > primaryKey.getCode()) { - minDistance = distance; - primaryKey = key; - } - } - return primaryKey; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java deleted file mode 100644 index 7318d4738..000000000 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard; - -import android.util.SparseArray; - -import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; -import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; -import com.android.inputmethod.keyboard.internal.KeyboardParams; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.CoordinateUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard - * consists of rows of keys. - * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p> - * <pre> - * <Keyboard - * latin:keyWidth="10%p" - * latin:rowHeight="50px" - * latin:horizontalGap="2%p" - * latin:verticalGap="2%p" > - * <Row latin:keyWidth="10%p" > - * <Key latin:keyLabel="A" /> - * ... - * </Row> - * ... - * </Keyboard> - * </pre> - */ -public class Keyboard { - @Nonnull - public final KeyboardId mId; - public final int mThemeId; - - /** Total height of the keyboard, including the padding and keys */ - public final int mOccupiedHeight; - /** Total width of the keyboard, including the padding and keys */ - public final int mOccupiedWidth; - - /** Base height of the keyboard, used to calculate rows' height */ - public final int mBaseHeight; - /** Base width of the keyboard, used to calculate keys' width */ - public final int mBaseWidth; - - /** The padding above the keyboard */ - public final int mTopPadding; - /** Default gap between rows */ - public final int mVerticalGap; - - /** Per keyboard key visual parameters */ - public final KeyVisualAttributes mKeyVisualAttributes; - - public final int mMostCommonKeyHeight; - public final int mMostCommonKeyWidth; - - /** More keys keyboard template */ - public final int mMoreKeysTemplate; - - /** Maximum column for more keys keyboard */ - public final int mMaxMoreKeysKeyboardColumn; - - /** List of keys in this keyboard */ - @Nonnull - private final List<Key> mSortedKeys; - @Nonnull - public final List<Key> mShiftKeys; - @Nonnull - public final List<Key> mAltCodeKeysWhileTyping; - @Nonnull - public final KeyboardIconsSet mIconsSet; - - private final SparseArray<Key> mKeyCache = new SparseArray<>(); - - @Nonnull - private final ProximityInfo mProximityInfo; - @Nonnull - private final KeyboardLayout mKeyboardLayout; - - private final boolean mProximityCharsCorrectionEnabled; - - public Keyboard(@Nonnull final KeyboardParams params) { - mId = params.mId; - mThemeId = params.mThemeId; - mOccupiedHeight = params.mOccupiedHeight; - mOccupiedWidth = params.mOccupiedWidth; - mBaseHeight = params.mBaseHeight; - mBaseWidth = params.mBaseWidth; - mMostCommonKeyHeight = params.mMostCommonKeyHeight; - mMostCommonKeyWidth = params.mMostCommonKeyWidth; - mMoreKeysTemplate = params.mMoreKeysTemplate; - mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn; - mKeyVisualAttributes = params.mKeyVisualAttributes; - mTopPadding = params.mTopPadding; - mVerticalGap = params.mVerticalGap; - - mSortedKeys = Collections.unmodifiableList(new ArrayList<>(params.mSortedKeys)); - mShiftKeys = Collections.unmodifiableList(params.mShiftKeys); - mAltCodeKeysWhileTyping = Collections.unmodifiableList(params.mAltCodeKeysWhileTyping); - mIconsSet = params.mIconsSet; - - mProximityInfo = new ProximityInfo(params.GRID_WIDTH, params.GRID_HEIGHT, - mOccupiedWidth, mOccupiedHeight, mMostCommonKeyWidth, mMostCommonKeyHeight, - mSortedKeys, params.mTouchPositionCorrection); - mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled; - mKeyboardLayout = KeyboardLayout.newKeyboardLayout(mSortedKeys, mMostCommonKeyWidth, - mMostCommonKeyHeight, mOccupiedWidth, mOccupiedHeight); - } - - protected Keyboard(@Nonnull final Keyboard keyboard) { - mId = keyboard.mId; - mThemeId = keyboard.mThemeId; - mOccupiedHeight = keyboard.mOccupiedHeight; - mOccupiedWidth = keyboard.mOccupiedWidth; - mBaseHeight = keyboard.mBaseHeight; - mBaseWidth = keyboard.mBaseWidth; - mMostCommonKeyHeight = keyboard.mMostCommonKeyHeight; - mMostCommonKeyWidth = keyboard.mMostCommonKeyWidth; - mMoreKeysTemplate = keyboard.mMoreKeysTemplate; - mMaxMoreKeysKeyboardColumn = keyboard.mMaxMoreKeysKeyboardColumn; - mKeyVisualAttributes = keyboard.mKeyVisualAttributes; - mTopPadding = keyboard.mTopPadding; - mVerticalGap = keyboard.mVerticalGap; - - mSortedKeys = keyboard.mSortedKeys; - mShiftKeys = keyboard.mShiftKeys; - mAltCodeKeysWhileTyping = keyboard.mAltCodeKeysWhileTyping; - mIconsSet = keyboard.mIconsSet; - - mProximityInfo = keyboard.mProximityInfo; - mProximityCharsCorrectionEnabled = keyboard.mProximityCharsCorrectionEnabled; - mKeyboardLayout = keyboard.mKeyboardLayout; - } - - public boolean hasProximityCharsCorrection(final int code) { - if (!mProximityCharsCorrectionEnabled) { - return false; - } - // Note: The native code has the main keyboard layout only at this moment. - // TODO: Figure out how to handle proximity characters information of all layouts. - final boolean canAssumeNativeHasProximityCharsInfoOfAllKeys = ( - mId.mElementId == KeyboardId.ELEMENT_ALPHABET - || mId.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED); - return canAssumeNativeHasProximityCharsInfoOfAllKeys || Character.isLetter(code); - } - - @Nonnull - public ProximityInfo getProximityInfo() { - return mProximityInfo; - } - - @Nonnull - public KeyboardLayout getKeyboardLayout() { - return mKeyboardLayout; - } - - /** - * Return the sorted list of keys of this keyboard. - * The keys are sorted from top-left to bottom-right order. - * The list may contain {@link Key.Spacer} object as well. - * @return the sorted unmodifiable list of {@link Key}s of this keyboard. - */ - @Nonnull - public List<Key> getSortedKeys() { - return mSortedKeys; - } - - @Nullable - public Key getKey(final int code) { - if (code == Constants.CODE_UNSPECIFIED) { - return null; - } - synchronized (mKeyCache) { - final int index = mKeyCache.indexOfKey(code); - if (index >= 0) { - return mKeyCache.valueAt(index); - } - - for (final Key key : getSortedKeys()) { - if (key.getCode() == code) { - mKeyCache.put(code, key); - return key; - } - } - mKeyCache.put(code, null); - return null; - } - } - - public boolean hasKey(@Nonnull final Key aKey) { - if (mKeyCache.indexOfValue(aKey) >= 0) { - return true; - } - - for (final Key key : getSortedKeys()) { - if (key == aKey) { - mKeyCache.put(key.getCode(), key); - return true; - } - } - return false; - } - - @Override - public String toString() { - return mId.toString(); - } - - /** - * Returns the array of the keys that are closest to the given point. - * @param x the x-coordinate of the point - * @param y the y-coordinate of the point - * @return the list of the nearest keys to the given point. If the given - * point is out of range, then an array of size zero is returned. - */ - @Nonnull - public List<Key> getNearestKeys(final int x, final int y) { - // Avoid dead pixels at edges of the keyboard - final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1)); - final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1)); - return mProximityInfo.getNearestKeys(adjustedX, adjustedY); - } - - @Nonnull - public int[] getCoordinates(@Nonnull final int[] codePoints) { - final int length = codePoints.length; - final int[] coordinates = CoordinateUtils.newCoordinateArray(length); - for (int i = 0; i < length; ++i) { - final Key key = getKey(codePoints[i]); - if (null != key) { - CoordinateUtils.setXYInArray(coordinates, i, - key.getX() + key.getWidth() / 2, key.getY() + key.getHeight() / 2); - } else { - CoordinateUtils.setXYInArray(coordinates, i, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); - } - } - return coordinates; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java deleted file mode 100644 index cdd632bc8..000000000 --- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard; - -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.InputPointers; - -public interface KeyboardActionListener { - /** - * Called when the user presses a key. This is sent before the {@link #onCodeInput} is called. - * For keys that repeat, this is only called once. - * - * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key, - * the value will be zero. - * @param repeatCount how many times the key was repeated. Zero if it is the first press. - * @param isSinglePointer true if pressing has occurred while no other key is being pressed. - */ - public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer); - - /** - * Called when the user releases a key. This is sent after the {@link #onCodeInput} is called. - * For keys that repeat, this is only called once. - * - * @param primaryCode the code of the key that was released - * @param withSliding true if releasing has occurred because the user slid finger from the key - * to other key without releasing the finger. - */ - public void onReleaseKey(int primaryCode, boolean withSliding); - - /** - * Send a key code to the listener. - * - * @param primaryCode this is the code of the key that was pressed - * @param x x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by - * {@link PointerTracker} or so, the value should be - * {@link Constants#NOT_A_COORDINATE}. If it's called on insertion from the - * suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}. - * @param y y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by - * {@link PointerTracker} or so, the value should be - * {@link Constants#NOT_A_COORDINATE}.If it's called on insertion from the - * suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}. - * @param isKeyRepeat true if this is a key repeat, false otherwise - */ - // TODO: change this to send an Event object instead - public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat); - - /** - * Sends a string of characters to the listener. - * - * @param text the string of characters to be registered. - */ - public void onTextInput(String text); - - /** - * Called when user started batch input. - */ - public void onStartBatchInput(); - - /** - * Sends the ongoing batch input points data. - * @param batchPointers the batch input points representing the user input - */ - public void onUpdateBatchInput(InputPointers batchPointers); - - /** - * Sends the final batch input points data. - * - * @param batchPointers the batch input points representing the user input - */ - public void onEndBatchInput(InputPointers batchPointers); - - public void onCancelBatchInput(); - - /** - * Called when user released a finger outside any key. - */ - public void onCancelInput(); - - /** - * Called when user finished sliding key input. - */ - public void onFinishSlidingInput(); - - /** - * Send a non-"code input" custom request to the listener. - * @return true if the request has been consumed, false otherwise. - */ - public boolean onCustomRequest(int requestCode); - - public static final KeyboardActionListener EMPTY_LISTENER = new Adapter(); - - public static class Adapter implements KeyboardActionListener { - @Override - public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer) {} - @Override - public void onReleaseKey(int primaryCode, boolean withSliding) {} - @Override - public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat) {} - @Override - public void onTextInput(String text) {} - @Override - public void onStartBatchInput() {} - @Override - public void onUpdateBatchInput(InputPointers batchPointers) {} - @Override - public void onEndBatchInput(InputPointers batchPointers) {} - @Override - public void onCancelBatchInput() {} - @Override - public void onCancelInput() {} - @Override - public void onFinishSlidingInput() {} - @Override - public boolean onCustomRequest(int requestCode) { - return false; - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java deleted file mode 100644 index 7352f911b..000000000 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.inputmethod.keyboard; - -import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; - -import android.text.InputType; -import android.text.TextUtils; -import android.view.inputmethod.EditorInfo; - -import com.android.inputmethod.compat.EditorInfoCompatUtils; -import com.android.inputmethod.latin.RichInputMethodSubtype; -import com.android.inputmethod.latin.utils.InputTypeUtils; - -import java.util.Arrays; -import java.util.Locale; - -/** - * Unique identifier for each keyboard type. - */ -public final class KeyboardId { - public static final int MODE_TEXT = 0; - public static final int MODE_URL = 1; - public static final int MODE_EMAIL = 2; - public static final int MODE_IM = 3; - public static final int MODE_PHONE = 4; - public static final int MODE_NUMBER = 5; - public static final int MODE_DATE = 6; - public static final int MODE_TIME = 7; - public static final int MODE_DATETIME = 8; - - public static final int ELEMENT_ALPHABET = 0; - public static final int ELEMENT_ALPHABET_MANUAL_SHIFTED = 1; - public static final int ELEMENT_ALPHABET_AUTOMATIC_SHIFTED = 2; - public static final int ELEMENT_ALPHABET_SHIFT_LOCKED = 3; - public static final int ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED = 4; - public static final int ELEMENT_SYMBOLS = 5; - public static final int ELEMENT_SYMBOLS_SHIFTED = 6; - public static final int ELEMENT_PHONE = 7; - public static final int ELEMENT_PHONE_SYMBOLS = 8; - public static final int ELEMENT_NUMBER = 9; - public static final int ELEMENT_EMOJI_RECENTS = 10; - public static final int ELEMENT_EMOJI_CATEGORY1 = 11; - public static final int ELEMENT_EMOJI_CATEGORY2 = 12; - public static final int ELEMENT_EMOJI_CATEGORY3 = 13; - public static final int ELEMENT_EMOJI_CATEGORY4 = 14; - public static final int ELEMENT_EMOJI_CATEGORY5 = 15; - public static final int ELEMENT_EMOJI_CATEGORY6 = 16; - public static final int ELEMENT_EMOJI_CATEGORY7 = 17; - public static final int ELEMENT_EMOJI_CATEGORY8 = 18; - public static final int ELEMENT_EMOJI_CATEGORY9 = 19; - public static final int ELEMENT_EMOJI_CATEGORY10 = 20; - public static final int ELEMENT_EMOJI_CATEGORY11 = 21; - public static final int ELEMENT_EMOJI_CATEGORY12 = 22; - public static final int ELEMENT_EMOJI_CATEGORY13 = 23; - public static final int ELEMENT_EMOJI_CATEGORY14 = 24; - public static final int ELEMENT_EMOJI_CATEGORY15 = 25; - public static final int ELEMENT_EMOJI_CATEGORY16 = 26; - - public final RichInputMethodSubtype mSubtype; - public final int mWidth; - public final int mHeight; - public final int mMode; - public final int mElementId; - public final EditorInfo mEditorInfo; - public final boolean mClobberSettingsKey; - public final boolean mLanguageSwitchKeyEnabled; - public final String mCustomActionLabel; - public final boolean mHasShortcutKey; - public final boolean mIsSplitLayout; - - private final int mHashCode; - - public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) { - mSubtype = params.mSubtype; - mWidth = params.mKeyboardWidth; - mHeight = params.mKeyboardHeight; - mMode = params.mMode; - mElementId = elementId; - mEditorInfo = params.mEditorInfo; - mClobberSettingsKey = params.mNoSettingsKey; - mLanguageSwitchKeyEnabled = params.mLanguageSwitchKeyEnabled; - mCustomActionLabel = (mEditorInfo.actionLabel != null) - ? mEditorInfo.actionLabel.toString() : null; - mHasShortcutKey = params.mVoiceInputKeyEnabled; - mIsSplitLayout = params.mIsSplitLayoutEnabled; - - mHashCode = computeHashCode(this); - } - - private static int computeHashCode(final KeyboardId id) { - return Arrays.hashCode(new Object[] { - id.mElementId, - id.mMode, - id.mWidth, - id.mHeight, - id.passwordInput(), - id.mClobberSettingsKey, - id.mHasShortcutKey, - id.mLanguageSwitchKeyEnabled, - id.isMultiLine(), - id.imeAction(), - id.mCustomActionLabel, - id.navigateNext(), - id.navigatePrevious(), - id.mSubtype, - id.mIsSplitLayout - }); - } - - private boolean equals(final KeyboardId other) { - if (other == this) - return true; - return other.mElementId == mElementId - && other.mMode == mMode - && other.mWidth == mWidth - && other.mHeight == mHeight - && other.passwordInput() == passwordInput() - && other.mClobberSettingsKey == mClobberSettingsKey - && other.mHasShortcutKey == mHasShortcutKey - && other.mLanguageSwitchKeyEnabled == mLanguageSwitchKeyEnabled - && other.isMultiLine() == isMultiLine() - && other.imeAction() == imeAction() - && TextUtils.equals(other.mCustomActionLabel, mCustomActionLabel) - && other.navigateNext() == navigateNext() - && other.navigatePrevious() == navigatePrevious() - && other.mSubtype.equals(mSubtype) - && other.mIsSplitLayout == mIsSplitLayout; - } - - private static boolean isAlphabetKeyboard(final int elementId) { - return elementId < ELEMENT_SYMBOLS; - } - - public boolean isAlphabetKeyboard() { - return isAlphabetKeyboard(mElementId); - } - - public boolean navigateNext() { - return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0 - || imeAction() == EditorInfo.IME_ACTION_NEXT; - } - - public boolean navigatePrevious() { - return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0 - || imeAction() == EditorInfo.IME_ACTION_PREVIOUS; - } - - public boolean passwordInput() { - final int inputType = mEditorInfo.inputType; - return InputTypeUtils.isPasswordInputType(inputType) - || InputTypeUtils.isVisiblePasswordInputType(inputType); - } - - public boolean isMultiLine() { - return (mEditorInfo.inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0; - } - - public int imeAction() { - return InputTypeUtils.getImeOptionsActionIdFromEditorInfo(mEditorInfo); - } - - public Locale getLocale() { - return mSubtype.getLocale(); - } - - @Override - public boolean equals(final Object other) { - return other instanceof KeyboardId && equals((KeyboardId) other); - } - - @Override - public int hashCode() { - return mHashCode; - } - - @Override - public String toString() { - return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s%s]", - elementIdToName(mElementId), - mSubtype.getLocale(), - mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET), - mWidth, mHeight, - modeName(mMode), - actionName(imeAction()), - (navigateNext() ? " navigateNext" : ""), - (navigatePrevious() ? " navigatePrevious" : ""), - (mClobberSettingsKey ? " clobberSettingsKey" : ""), - (passwordInput() ? " passwordInput" : ""), - (mHasShortcutKey ? " hasShortcutKey" : ""), - (mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""), - (isMultiLine() ? " isMultiLine" : ""), - (mIsSplitLayout ? " isSplitLayout" : "") - ); - } - - public static boolean equivalentEditorInfoForKeyboard(final EditorInfo a, final EditorInfo b) { - if (a == null && b == null) return true; - if (a == null || b == null) return false; - return a.inputType == b.inputType - && a.imeOptions == b.imeOptions - && TextUtils.equals(a.privateImeOptions, b.privateImeOptions); - } - - public static String elementIdToName(final int elementId) { - switch (elementId) { - case ELEMENT_ALPHABET: return "alphabet"; - case ELEMENT_ALPHABET_MANUAL_SHIFTED: return "alphabetManualShifted"; - case ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: return "alphabetAutomaticShifted"; - case ELEMENT_ALPHABET_SHIFT_LOCKED: return "alphabetShiftLocked"; - case ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: return "alphabetShiftLockShifted"; - case ELEMENT_SYMBOLS: return "symbols"; - case ELEMENT_SYMBOLS_SHIFTED: return "symbolsShifted"; - case ELEMENT_PHONE: return "phone"; - case ELEMENT_PHONE_SYMBOLS: return "phoneSymbols"; - case ELEMENT_NUMBER: return "number"; - case ELEMENT_EMOJI_RECENTS: return "emojiRecents"; - case ELEMENT_EMOJI_CATEGORY1: return "emojiCategory1"; - case ELEMENT_EMOJI_CATEGORY2: return "emojiCategory2"; - case ELEMENT_EMOJI_CATEGORY3: return "emojiCategory3"; - case ELEMENT_EMOJI_CATEGORY4: return "emojiCategory4"; - case ELEMENT_EMOJI_CATEGORY5: return "emojiCategory5"; - case ELEMENT_EMOJI_CATEGORY6: return "emojiCategory6"; - case ELEMENT_EMOJI_CATEGORY7: return "emojiCategory7"; - case ELEMENT_EMOJI_CATEGORY8: return "emojiCategory8"; - case ELEMENT_EMOJI_CATEGORY9: return "emojiCategory9"; - case ELEMENT_EMOJI_CATEGORY10: return "emojiCategory10"; - case ELEMENT_EMOJI_CATEGORY11: return "emojiCategory11"; - case ELEMENT_EMOJI_CATEGORY12: return "emojiCategory12"; - case ELEMENT_EMOJI_CATEGORY13: return "emojiCategory13"; - case ELEMENT_EMOJI_CATEGORY14: return "emojiCategory14"; - case ELEMENT_EMOJI_CATEGORY15: return "emojiCategory15"; - case ELEMENT_EMOJI_CATEGORY16: return "emojiCategory16"; - default: return null; - } - } - - public static String modeName(final int mode) { - switch (mode) { - case MODE_TEXT: return "text"; - case MODE_URL: return "url"; - case MODE_EMAIL: return "email"; - case MODE_IM: return "im"; - case MODE_PHONE: return "phone"; - case MODE_NUMBER: return "number"; - case MODE_DATE: return "date"; - case MODE_TIME: return "time"; - case MODE_DATETIME: return "datetime"; - default: return null; - } - } - - public static String actionName(final int actionId) { - return (actionId == InputTypeUtils.IME_ACTION_CUSTOM_LABEL) ? "actionCustomLabel" - : EditorInfoCompatUtils.imeActionName(actionId); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayout.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayout.java deleted file mode 100644 index d0f32078e..000000000 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayout.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.inputmethod.keyboard; - -import com.android.inputmethod.annotations.UsedForTesting; - -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.Nonnull; - -/** - * KeyboardLayout maintains the keyboard layout information. - */ -public class KeyboardLayout { - - private final int[] mKeyCodes; - - private final int[] mKeyXCoordinates; - private final int[] mKeyYCoordinates; - - private final int[] mKeyWidths; - private final int[] mKeyHeights; - - public final int mMostCommonKeyWidth; - public final int mMostCommonKeyHeight; - - public final int mKeyboardWidth; - public final int mKeyboardHeight; - - public KeyboardLayout(ArrayList<Key> layoutKeys, int mostCommonKeyWidth, - int mostCommonKeyHeight, int keyboardWidth, int keyboardHeight) { - mMostCommonKeyWidth = mostCommonKeyWidth; - mMostCommonKeyHeight = mostCommonKeyHeight; - mKeyboardWidth = keyboardWidth; - mKeyboardHeight = keyboardHeight; - - mKeyCodes = new int[layoutKeys.size()]; - mKeyXCoordinates = new int[layoutKeys.size()]; - mKeyYCoordinates = new int[layoutKeys.size()]; - mKeyWidths = new int[layoutKeys.size()]; - mKeyHeights = new int[layoutKeys.size()]; - - for (int i = 0; i < layoutKeys.size(); i++) { - Key key = layoutKeys.get(i); - mKeyCodes[i] = Character.toLowerCase(key.getCode()); - mKeyXCoordinates[i] = key.getX(); - mKeyYCoordinates[i] = key.getY(); - mKeyWidths[i] = key.getWidth(); - mKeyHeights[i] = key.getHeight(); - } - } - - @UsedForTesting - public int[] getKeyCodes() { - return mKeyCodes; - } - - /** - * The x-coordinate for the top-left corner of the keys. - * - */ - public int[] getKeyXCoordinates() { - return mKeyXCoordinates; - } - - /** - * The y-coordinate for the top-left corner of the keys. - */ - public int[] getKeyYCoordinates() { - return mKeyYCoordinates; - } - - /** - * The widths of the keys which are smaller than the true hit-area due to the gaps - * between keys. The mostCommonKey(Width/Height) represents the true key width/height - * including the gaps. - */ - public int[] getKeyWidths() { - return mKeyWidths; - } - - /** - * The heights of the keys which are smaller than the true hit-area due to the gaps - * between keys. The mostCommonKey(Width/Height) represents the true key width/height - * including the gaps. - */ - public int[] getKeyHeights() { - return mKeyHeights; - } - - /** - * Factory method to create {@link KeyboardLayout} objects. - */ - public static KeyboardLayout newKeyboardLayout(@Nonnull final List<Key> sortedKeys, - int mostCommonKeyWidth, int mostCommonKeyHeight, - int occupiedWidth, int occupiedHeight) { - final ArrayList<Key> layoutKeys = new ArrayList<Key>(); - for (final Key key : sortedKeys) { - if (!ProximityInfo.needsProximityInfo(key)) { - continue; - } - if (key.getCode() != ',') { - layoutKeys.add(key); - } - } - return new KeyboardLayout(layoutKeys, mostCommonKeyWidth, - mostCommonKeyHeight, occupiedWidth, occupiedHeight); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java deleted file mode 100644 index 26ff051bb..000000000 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.keyboard; - -import static com.android.inputmethod.latin.common.Constants.ImeOption.FORCE_ASCII; -import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_SETTINGS_KEY; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.text.InputType; -import android.util.Log; -import android.util.SparseArray; -import android.util.Xml; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.compat.EditorInfoCompatUtils; -import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; -import com.android.inputmethod.compat.UserManagerCompatUtils; -import com.android.inputmethod.keyboard.internal.KeyboardBuilder; -import com.android.inputmethod.keyboard.internal.KeyboardParams; -import com.android.inputmethod.keyboard.internal.UniqueKeysCache; -import com.android.inputmethod.latin.InputAttributes; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodSubtype; -import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.utils.InputTypeUtils; -import com.android.inputmethod.latin.utils.ScriptUtils; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; -import com.android.inputmethod.latin.utils.XmlParseUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.lang.ref.SoftReference; -import java.util.HashMap; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * This class represents a set of keyboard layouts. Each of them represents a different keyboard - * specific to a keyboard state, such as alphabet, symbols, and so on. Layouts in the same - * {@link KeyboardLayoutSet} are related to each other. - * A {@link KeyboardLayoutSet} needs to be created for each - * {@link android.view.inputmethod.EditorInfo}. - */ -public final class KeyboardLayoutSet { - private static final String TAG = KeyboardLayoutSet.class.getSimpleName(); - private static final boolean DEBUG_CACHE = false; - - private static final String TAG_KEYBOARD_SET = "KeyboardLayoutSet"; - private static final String TAG_ELEMENT = "Element"; - private static final String TAG_FEATURE = "Feature"; - - private static final String KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX = "keyboard_layout_set_"; - - private final Context mContext; - @Nonnull - private final Params mParams; - - // How many layouts we forcibly keep in cache. This only includes ALPHABET (default) and - // ALPHABET_AUTOMATIC_SHIFTED layouts - other layouts may stay in memory in the map of - // soft-references, but we forcibly cache this many alphabetic/auto-shifted layouts. - private static final int FORCIBLE_CACHE_SIZE = 4; - // By construction of soft references, anything that is also referenced somewhere else - // will stay in the cache. So we forcibly keep some references in an array to prevent - // them from disappearing from sKeyboardCache. - private static final Keyboard[] sForcibleKeyboardCache = new Keyboard[FORCIBLE_CACHE_SIZE]; - private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache = - new HashMap<>(); - @Nonnull - private static final UniqueKeysCache sUniqueKeysCache = UniqueKeysCache.newInstance(); - private final static HashMap<InputMethodSubtype, Integer> sScriptIdsForSubtypes = - new HashMap<>(); - - @SuppressWarnings("serial") - public static final class KeyboardLayoutSetException extends RuntimeException { - public final KeyboardId mKeyboardId; - - public KeyboardLayoutSetException(final Throwable cause, final KeyboardId keyboardId) { - super(cause); - mKeyboardId = keyboardId; - } - } - - private static final class ElementParams { - int mKeyboardXmlId; - boolean mProximityCharsCorrectionEnabled; - boolean mSupportsSplitLayout; - boolean mAllowRedundantMoreKeys; - public ElementParams() {} - } - - public static final class Params { - String mKeyboardLayoutSetName; - int mMode; - boolean mDisableTouchPositionCorrectionDataForTest; - // TODO: Use {@link InputAttributes} instead of these variables. - EditorInfo mEditorInfo; - boolean mIsPasswordField; - boolean mVoiceInputKeyEnabled; - boolean mNoSettingsKey; - boolean mLanguageSwitchKeyEnabled; - RichInputMethodSubtype mSubtype; - boolean mIsSpellChecker; - int mKeyboardWidth; - int mKeyboardHeight; - int mScriptId = ScriptUtils.SCRIPT_LATIN; - // Indicates if the user has enabled the split-layout preference - // and the required ProductionFlags are enabled. - boolean mIsSplitLayoutEnabledByUser; - // Indicates if split layout is actually enabled, taking into account - // whether the user has enabled it, and the keyboard layout supports it. - boolean mIsSplitLayoutEnabled; - // Sparse array of KeyboardLayoutSet element parameters indexed by element's id. - final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap = - new SparseArray<>(); - } - - public static void onSystemLocaleChanged() { - clearKeyboardCache(); - } - - public static void onKeyboardThemeChanged() { - clearKeyboardCache(); - } - - private static void clearKeyboardCache() { - sKeyboardCache.clear(); - sUniqueKeysCache.clear(); - } - - public static int getScriptId(final Resources resources, - @Nonnull final InputMethodSubtype subtype) { - final Integer value = sScriptIdsForSubtypes.get(subtype); - if (null == value) { - final int scriptId = Builder.readScriptId(resources, subtype); - sScriptIdsForSubtypes.put(subtype, scriptId); - return scriptId; - } - return value; - } - - KeyboardLayoutSet(final Context context, @Nonnull final Params params) { - mContext = context; - mParams = params; - } - - @Nonnull - public Keyboard getKeyboard(final int baseKeyboardLayoutSetElementId) { - final int keyboardLayoutSetElementId; - switch (mParams.mMode) { - case KeyboardId.MODE_PHONE: - if (baseKeyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS) { - keyboardLayoutSetElementId = KeyboardId.ELEMENT_PHONE_SYMBOLS; - } else { - keyboardLayoutSetElementId = KeyboardId.ELEMENT_PHONE; - } - break; - case KeyboardId.MODE_NUMBER: - case KeyboardId.MODE_DATE: - case KeyboardId.MODE_TIME: - case KeyboardId.MODE_DATETIME: - keyboardLayoutSetElementId = KeyboardId.ELEMENT_NUMBER; - break; - default: - keyboardLayoutSetElementId = baseKeyboardLayoutSetElementId; - break; - } - - ElementParams elementParams = mParams.mKeyboardLayoutSetElementIdToParamsMap.get( - keyboardLayoutSetElementId); - if (elementParams == null) { - elementParams = mParams.mKeyboardLayoutSetElementIdToParamsMap.get( - KeyboardId.ELEMENT_ALPHABET); - } - // Note: The keyboard for each shift state, and mode are represented as an elementName - // attribute in a keyboard_layout_set XML file. Also each keyboard layout XML resource is - // specified as an elementKeyboard attribute in the file. - // The KeyboardId is an internal key for a Keyboard object. - - mParams.mIsSplitLayoutEnabled = mParams.mIsSplitLayoutEnabledByUser - && elementParams.mSupportsSplitLayout; - final KeyboardId id = new KeyboardId(keyboardLayoutSetElementId, mParams); - try { - return getKeyboard(elementParams, id); - } catch (final RuntimeException e) { - Log.e(TAG, "Can't create keyboard: " + id, e); - throw new KeyboardLayoutSetException(e, id); - } - } - - @Nonnull - private Keyboard getKeyboard(final ElementParams elementParams, final KeyboardId id) { - final SoftReference<Keyboard> ref = sKeyboardCache.get(id); - final Keyboard cachedKeyboard = (ref == null) ? null : ref.get(); - if (cachedKeyboard != null) { - if (DEBUG_CACHE) { - Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": HIT id=" + id); - } - return cachedKeyboard; - } - - final KeyboardBuilder<KeyboardParams> builder = - new KeyboardBuilder<>(mContext, new KeyboardParams(sUniqueKeysCache)); - sUniqueKeysCache.setEnabled(id.isAlphabetKeyboard()); - builder.setAllowRedundantMoreKes(elementParams.mAllowRedundantMoreKeys); - final int keyboardXmlId = elementParams.mKeyboardXmlId; - builder.load(keyboardXmlId, id); - if (mParams.mDisableTouchPositionCorrectionDataForTest) { - builder.disableTouchPositionCorrectionDataForTest(); - } - builder.setProximityCharsCorrectionEnabled(elementParams.mProximityCharsCorrectionEnabled); - final Keyboard keyboard = builder.build(); - sKeyboardCache.put(id, new SoftReference<>(keyboard)); - if ((id.mElementId == KeyboardId.ELEMENT_ALPHABET - || id.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) - && !mParams.mIsSpellChecker) { - // We only forcibly cache the primary, "ALPHABET", layouts. - for (int i = sForcibleKeyboardCache.length - 1; i >= 1; --i) { - sForcibleKeyboardCache[i] = sForcibleKeyboardCache[i - 1]; - } - sForcibleKeyboardCache[0] = keyboard; - if (DEBUG_CACHE) { - Log.d(TAG, "forcing caching of keyboard with id=" + id); - } - } - if (DEBUG_CACHE) { - Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": " - + ((ref == null) ? "LOAD" : "GCed") + " id=" + id); - } - return keyboard; - } - - public int getScriptId() { - return mParams.mScriptId; - } - - public static final class Builder { - private final Context mContext; - private final String mPackageName; - private final Resources mResources; - - private final Params mParams = new Params(); - - private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo(); - - public Builder(final Context context, @Nullable final EditorInfo ei) { - mContext = context; - mPackageName = context.getPackageName(); - mResources = context.getResources(); - final Params params = mParams; - - final EditorInfo editorInfo = (ei != null) ? ei : EMPTY_EDITOR_INFO; - params.mMode = getKeyboardMode(editorInfo); - // TODO: Consolidate those with {@link InputAttributes}. - params.mEditorInfo = editorInfo; - params.mIsPasswordField = InputTypeUtils.isPasswordInputType(editorInfo.inputType); - params.mNoSettingsKey = InputAttributes.inPrivateImeOptions( - mPackageName, NO_SETTINGS_KEY, editorInfo); - - // When the device is still unlocked, features like showing the IME setting app need to - // be locked down. - // TODO: Switch to {@code UserManagerCompat.isUserUnlocked()} in the support-v4 library - // when it becomes publicly available. - @UserManagerCompatUtils.LockState - final int lockState = UserManagerCompatUtils.getUserLockState(context); - if (lockState == UserManagerCompatUtils.LOCK_STATE_LOCKED) { - params.mNoSettingsKey = true; - } - } - - public Builder setKeyboardGeometry(final int keyboardWidth, final int keyboardHeight) { - mParams.mKeyboardWidth = keyboardWidth; - mParams.mKeyboardHeight = keyboardHeight; - return this; - } - - public Builder setSubtype(@Nonnull final RichInputMethodSubtype subtype) { - final boolean asciiCapable = InputMethodSubtypeCompatUtils.isAsciiCapable(subtype); - // TODO: Consolidate with {@link InputAttributes}. - @SuppressWarnings("deprecation") - final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions( - mPackageName, FORCE_ASCII, mParams.mEditorInfo); - final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii( - mParams.mEditorInfo.imeOptions) - || deprecatedForceAscii; - final RichInputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable) - ? RichInputMethodSubtype.getNoLanguageSubtype() - : subtype; - mParams.mSubtype = keyboardSubtype; - mParams.mKeyboardLayoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX - + keyboardSubtype.getKeyboardLayoutSetName(); - return this; - } - - public Builder setIsSpellChecker(final boolean isSpellChecker) { - mParams.mIsSpellChecker = isSpellChecker; - return this; - } - - public Builder setVoiceInputKeyEnabled(final boolean enabled) { - mParams.mVoiceInputKeyEnabled = enabled; - return this; - } - - public Builder setLanguageSwitchKeyEnabled(final boolean enabled) { - mParams.mLanguageSwitchKeyEnabled = enabled; - return this; - } - - public Builder disableTouchPositionCorrectionData() { - mParams.mDisableTouchPositionCorrectionDataForTest = true; - return this; - } - - public Builder setSplitLayoutEnabledByUser(final boolean enabled) { - mParams.mIsSplitLayoutEnabledByUser = enabled; - return this; - } - - // Super redux version of reading the script ID for some subtype from Xml. - static int readScriptId(final Resources resources, final InputMethodSubtype subtype) { - final String layoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX - + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); - final int xmlId = getXmlId(resources, layoutSetName); - final XmlResourceParser parser = resources.getXml(xmlId); - try { - while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { - // Bovinate through the XML stupidly searching for TAG_FEATURE, and read - // the script Id from it. - parser.next(); - final String tag = parser.getName(); - if (TAG_FEATURE.equals(tag)) { - return readScriptIdFromTagFeature(resources, parser); - } - } - } catch (final IOException | XmlPullParserException e) { - throw new RuntimeException(e.getMessage() + " in " + layoutSetName, e); - } finally { - parser.close(); - } - // If the tag is not found, then the default script is Latin. - return ScriptUtils.SCRIPT_LATIN; - } - - private static int readScriptIdFromTagFeature(final Resources resources, - final XmlPullParser parser) throws IOException, XmlPullParserException { - final TypedArray featureAttr = resources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.KeyboardLayoutSet_Feature); - try { - final int scriptId = - featureAttr.getInt(R.styleable.KeyboardLayoutSet_Feature_supportedScript, - ScriptUtils.SCRIPT_UNKNOWN); - XmlParseUtils.checkEndTag(TAG_FEATURE, parser); - return scriptId; - } finally { - featureAttr.recycle(); - } - } - - public KeyboardLayoutSet build() { - if (mParams.mSubtype == null) - throw new RuntimeException("KeyboardLayoutSet subtype is not specified"); - final int xmlId = getXmlId(mResources, mParams.mKeyboardLayoutSetName); - try { - parseKeyboardLayoutSet(mResources, xmlId); - } catch (final IOException | XmlPullParserException e) { - throw new RuntimeException(e.getMessage() + " in " + mParams.mKeyboardLayoutSetName, - e); - } - return new KeyboardLayoutSet(mContext, mParams); - } - - private static int getXmlId(final Resources resources, final String keyboardLayoutSetName) { - final String packageName = resources.getResourcePackageName( - R.xml.keyboard_layout_set_qwerty); - return resources.getIdentifier(keyboardLayoutSetName, "xml", packageName); - } - - private void parseKeyboardLayoutSet(final Resources res, final int resId) - throws XmlPullParserException, IOException { - final XmlResourceParser parser = res.getXml(resId); - try { - while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { - final int event = parser.next(); - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_KEYBOARD_SET.equals(tag)) { - parseKeyboardLayoutSetContent(parser); - } else { - throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD_SET); - } - } - } - } finally { - parser.close(); - } - } - - private void parseKeyboardLayoutSetContent(final XmlPullParser parser) - throws XmlPullParserException, IOException { - while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { - final int event = parser.next(); - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_ELEMENT.equals(tag)) { - parseKeyboardLayoutSetElement(parser); - } else if (TAG_FEATURE.equals(tag)) { - mParams.mScriptId = readScriptIdFromTagFeature(mResources, parser); - } else { - throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD_SET); - } - } else if (event == XmlPullParser.END_TAG) { - final String tag = parser.getName(); - if (TAG_KEYBOARD_SET.equals(tag)) { - break; - } - throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_KEYBOARD_SET); - } - } - } - - private void parseKeyboardLayoutSetElement(final XmlPullParser parser) - throws XmlPullParserException, IOException { - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.KeyboardLayoutSet_Element); - try { - XmlParseUtils.checkAttributeExists(a, - R.styleable.KeyboardLayoutSet_Element_elementName, "elementName", - TAG_ELEMENT, parser); - XmlParseUtils.checkAttributeExists(a, - R.styleable.KeyboardLayoutSet_Element_elementKeyboard, "elementKeyboard", - TAG_ELEMENT, parser); - XmlParseUtils.checkEndTag(TAG_ELEMENT, parser); - - final ElementParams elementParams = new ElementParams(); - final int elementName = a.getInt( - R.styleable.KeyboardLayoutSet_Element_elementName, 0); - elementParams.mKeyboardXmlId = a.getResourceId( - R.styleable.KeyboardLayoutSet_Element_elementKeyboard, 0); - elementParams.mProximityCharsCorrectionEnabled = a.getBoolean( - R.styleable.KeyboardLayoutSet_Element_enableProximityCharsCorrection, - false); - elementParams.mSupportsSplitLayout = a.getBoolean( - R.styleable.KeyboardLayoutSet_Element_supportsSplitLayout, false); - elementParams.mAllowRedundantMoreKeys = a.getBoolean( - R.styleable.KeyboardLayoutSet_Element_allowRedundantMoreKeys, true); - mParams.mKeyboardLayoutSetElementIdToParamsMap.put(elementName, elementParams); - } finally { - a.recycle(); - } - } - - private static int getKeyboardMode(final EditorInfo editorInfo) { - final int inputType = editorInfo.inputType; - final int variation = inputType & InputType.TYPE_MASK_VARIATION; - - switch (inputType & InputType.TYPE_MASK_CLASS) { - case InputType.TYPE_CLASS_NUMBER: - return KeyboardId.MODE_NUMBER; - case InputType.TYPE_CLASS_DATETIME: - switch (variation) { - case InputType.TYPE_DATETIME_VARIATION_DATE: - return KeyboardId.MODE_DATE; - case InputType.TYPE_DATETIME_VARIATION_TIME: - return KeyboardId.MODE_TIME; - default: // InputType.TYPE_DATETIME_VARIATION_NORMAL - return KeyboardId.MODE_DATETIME; - } - case InputType.TYPE_CLASS_PHONE: - return KeyboardId.MODE_PHONE; - case InputType.TYPE_CLASS_TEXT: - if (InputTypeUtils.isEmailVariation(variation)) { - return KeyboardId.MODE_EMAIL; - } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { - return KeyboardId.MODE_URL; - } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { - return KeyboardId.MODE_IM; - } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) { - return KeyboardId.MODE_TEXT; - } else { - return KeyboardId.MODE_TEXT; - } - default: - return KeyboardId.MODE_TEXT; - } - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java deleted file mode 100644 index d398ab8c5..000000000 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.inputmethod.keyboard; - -import android.content.Context; -import android.content.res.Resources; -import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.inputmethod.EditorInfo; - -import androidx.annotation.NonNull; - -import com.android.inputmethod.compat.InputMethodServiceCompatUtils; -import com.android.inputmethod.event.Event; -import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException; -import com.android.inputmethod.keyboard.emoji.EmojiPalettesView; -import com.android.inputmethod.keyboard.internal.KeyboardState; -import com.android.inputmethod.keyboard.internal.KeyboardTextsSet; -import com.android.inputmethod.latin.InputView; -import com.android.inputmethod.latin.LatinIME; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodManager; -import com.android.inputmethod.latin.WordComposer; -import com.android.inputmethod.latin.define.ProductionFlags; -import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.settings.SettingsValues; -import com.android.inputmethod.latin.utils.CapsModeUtils; -import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils; -import com.android.inputmethod.latin.utils.RecapitalizeStatus; -import com.android.inputmethod.latin.utils.ResourceUtils; -import com.android.inputmethod.latin.utils.ScriptUtils; - -import javax.annotation.Nonnull; - -public final class KeyboardSwitcher implements KeyboardState.SwitchActions { - private static final String TAG = KeyboardSwitcher.class.getSimpleName(); - - private InputView mCurrentInputView; - private View mMainKeyboardFrame; - private MainKeyboardView mKeyboardView; - private EmojiPalettesView mEmojiPalettesView; - private LatinIME mLatinIME; - private RichInputMethodManager mRichImm; - private boolean mIsHardwareAcceleratedDrawingEnabled; - - private KeyboardState mState; - - private KeyboardLayoutSet mKeyboardLayoutSet; - // TODO: The following {@link KeyboardTextsSet} should be in {@link KeyboardLayoutSet}. - private final KeyboardTextsSet mKeyboardTextsSet = new KeyboardTextsSet(); - - private KeyboardTheme mKeyboardTheme; - private Context mThemeContext; - - private static final KeyboardSwitcher sInstance = new KeyboardSwitcher(); - - public static KeyboardSwitcher getInstance() { - return sInstance; - } - - private KeyboardSwitcher() { - // Intentional empty constructor for singleton. - } - - public static void init(final LatinIME latinIme) { - sInstance.initInternal(latinIme); - } - - private void initInternal(final LatinIME latinIme) { - mLatinIME = latinIme; - mRichImm = RichInputMethodManager.getInstance(); - mState = new KeyboardState(this); - mIsHardwareAcceleratedDrawingEnabled = - InputMethodServiceCompatUtils.enableHardwareAcceleration(mLatinIME); - } - - public void updateKeyboardTheme(@NonNull Context displayContext) { - final boolean themeUpdated = updateKeyboardThemeAndContextThemeWrapper( - displayContext, KeyboardTheme.getKeyboardTheme(displayContext /* context */)); - if (themeUpdated && mKeyboardView != null) { - mLatinIME.setInputView( - onCreateInputView(displayContext, mIsHardwareAcceleratedDrawingEnabled)); - } - } - - private boolean updateKeyboardThemeAndContextThemeWrapper(final Context context, - final KeyboardTheme keyboardTheme) { - if (mThemeContext == null || !keyboardTheme.equals(mKeyboardTheme) - || !mThemeContext.getResources().equals(context.getResources())) { - mKeyboardTheme = keyboardTheme; - mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId); - KeyboardLayoutSet.onKeyboardThemeChanged(); - return true; - } - return false; - } - - public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues, - final int currentAutoCapsState, final int currentRecapitalizeState) { - final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( - mThemeContext, editorInfo); - final Resources res = mThemeContext.getResources(); - final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(mThemeContext); - final int keyboardHeight = ResourceUtils.getKeyboardHeight(res, settingsValues); - builder.setKeyboardGeometry(keyboardWidth, keyboardHeight); - builder.setSubtype(mRichImm.getCurrentSubtype()); - builder.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey); - builder.setLanguageSwitchKeyEnabled(mLatinIME.shouldShowLanguageSwitchKey()); - builder.setSplitLayoutEnabledByUser(ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED - && settingsValues.mIsSplitKeyboardEnabled); - mKeyboardLayoutSet = builder.build(); - try { - mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState); - mKeyboardTextsSet.setLocale(mRichImm.getCurrentSubtypeLocale(), mThemeContext); - } catch (KeyboardLayoutSetException e) { - Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); - } - } - - public void saveKeyboardState() { - if (getKeyboard() != null || isShowingEmojiPalettes()) { - mState.onSaveKeyboardState(); - } - } - - public void onHideWindow() { - if (mKeyboardView != null) { - mKeyboardView.onHideWindow(); - } - } - - private void setKeyboard( - @Nonnull final int keyboardId, - @Nonnull final KeyboardSwitchState toggleState) { - // Make {@link MainKeyboardView} visible and hide {@link EmojiPalettesView}. - final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent(); - setMainKeyboardFrame(currentSettingsValues, toggleState); - // TODO: pass this object to setKeyboard instead of getting the current values. - final MainKeyboardView keyboardView = mKeyboardView; - final Keyboard oldKeyboard = keyboardView.getKeyboard(); - final Keyboard newKeyboard = mKeyboardLayoutSet.getKeyboard(keyboardId); - keyboardView.setKeyboard(newKeyboard); - mCurrentInputView.setKeyboardTopPadding(newKeyboard.mTopPadding); - keyboardView.setKeyPreviewPopupEnabled( - currentSettingsValues.mKeyPreviewPopupOn, - currentSettingsValues.mKeyPreviewPopupDismissDelay); - keyboardView.setKeyPreviewAnimationParams( - currentSettingsValues.mHasCustomKeyPreviewAnimationParams, - currentSettingsValues.mKeyPreviewShowUpStartXScale, - currentSettingsValues.mKeyPreviewShowUpStartYScale, - currentSettingsValues.mKeyPreviewShowUpDuration, - currentSettingsValues.mKeyPreviewDismissEndXScale, - currentSettingsValues.mKeyPreviewDismissEndYScale, - currentSettingsValues.mKeyPreviewDismissDuration); - keyboardView.updateShortcutKey(mRichImm.isShortcutImeReady()); - final boolean subtypeChanged = (oldKeyboard == null) - || !newKeyboard.mId.mSubtype.equals(oldKeyboard.mId.mSubtype); - final int languageOnSpacebarFormatType = LanguageOnSpacebarUtils - .getLanguageOnSpacebarFormatType(newKeyboard.mId.mSubtype); - final boolean hasMultipleEnabledIMEsOrSubtypes = mRichImm - .hasMultipleEnabledIMEsOrSubtypes(true /* shouldIncludeAuxiliarySubtypes */); - keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, languageOnSpacebarFormatType, - hasMultipleEnabledIMEsOrSubtypes); - } - - public Keyboard getKeyboard() { - if (mKeyboardView != null) { - return mKeyboardView.getKeyboard(); - } - return null; - } - - // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout - // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal(). - public void resetKeyboardStateToAlphabet(final int currentAutoCapsState, - final int currentRecapitalizeState) { - mState.onResetKeyboardStateToAlphabet(currentAutoCapsState, currentRecapitalizeState); - } - - public void onPressKey(final int code, final boolean isSinglePointer, - final int currentAutoCapsState, final int currentRecapitalizeState) { - mState.onPressKey(code, isSinglePointer, currentAutoCapsState, currentRecapitalizeState); - } - - public void onReleaseKey(final int code, final boolean withSliding, - final int currentAutoCapsState, final int currentRecapitalizeState) { - mState.onReleaseKey(code, withSliding, currentAutoCapsState, currentRecapitalizeState); - } - - public void onFinishSlidingInput(final int currentAutoCapsState, - final int currentRecapitalizeState) { - mState.onFinishSlidingInput(currentAutoCapsState, currentRecapitalizeState); - } - - // Implements {@link KeyboardState.SwitchActions}. - @Override - public void setAlphabetKeyboard() { - if (DEBUG_ACTION) { - Log.d(TAG, "setAlphabetKeyboard"); - } - setKeyboard(KeyboardId.ELEMENT_ALPHABET, KeyboardSwitchState.OTHER); - } - - // Implements {@link KeyboardState.SwitchActions}. - @Override - public void setAlphabetManualShiftedKeyboard() { - if (DEBUG_ACTION) { - Log.d(TAG, "setAlphabetManualShiftedKeyboard"); - } - setKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED, KeyboardSwitchState.OTHER); - } - - // Implements {@link KeyboardState.SwitchActions}. - @Override - public void setAlphabetAutomaticShiftedKeyboard() { - if (DEBUG_ACTION) { - Log.d(TAG, "setAlphabetAutomaticShiftedKeyboard"); - } - setKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardSwitchState.OTHER); - } - - // Implements {@link KeyboardState.SwitchActions}. - @Override - public void setAlphabetShiftLockedKeyboard() { - if (DEBUG_ACTION) { - Log.d(TAG, "setAlphabetShiftLockedKeyboard"); - } - setKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED, KeyboardSwitchState.OTHER); - } - - // Implements {@link KeyboardState.SwitchActions}. - @Override - public void setAlphabetShiftLockShiftedKeyboard() { - if (DEBUG_ACTION) { - Log.d(TAG, "setAlphabetShiftLockShiftedKeyboard"); - } - setKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED, KeyboardSwitchState.OTHER); - } - - // Implements {@link KeyboardState.SwitchActions}. - @Override - public void setSymbolsKeyboard() { - if (DEBUG_ACTION) { - Log.d(TAG, "setSymbolsKeyboard"); - } - setKeyboard(KeyboardId.ELEMENT_SYMBOLS, KeyboardSwitchState.OTHER); - } - - // Implements {@link KeyboardState.SwitchActions}. - @Override - public void setSymbolsShiftedKeyboard() { - if (DEBUG_ACTION) { - Log.d(TAG, "setSymbolsShiftedKeyboard"); - } - setKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED, KeyboardSwitchState.SYMBOLS_SHIFTED); - } - - public boolean isImeSuppressedByHardwareKeyboard( - @Nonnull final SettingsValues settingsValues, - @Nonnull final KeyboardSwitchState toggleState) { - return settingsValues.mHasHardwareKeyboard && toggleState == KeyboardSwitchState.HIDDEN; - } - - private void setMainKeyboardFrame( - @Nonnull final SettingsValues settingsValues, - @Nonnull final KeyboardSwitchState toggleState) { - final int visibility = isImeSuppressedByHardwareKeyboard(settingsValues, toggleState) - ? View.GONE : View.VISIBLE; - mKeyboardView.setVisibility(visibility); - // The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}. - // @see #getVisibleKeyboardView() and - // @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets) - mMainKeyboardFrame.setVisibility(visibility); - mEmojiPalettesView.setVisibility(View.GONE); - mEmojiPalettesView.stopEmojiPalettes(); - } - - // Implements {@link KeyboardState.SwitchActions}. - @Override - public void setEmojiKeyboard() { - if (DEBUG_ACTION) { - Log.d(TAG, "setEmojiKeyboard"); - } - final Keyboard keyboard = mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET); - mMainKeyboardFrame.setVisibility(View.GONE); - // The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}. - // @see #getVisibleKeyboardView() and - // @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets) - mKeyboardView.setVisibility(View.GONE); - mEmojiPalettesView.startEmojiPalettes( - mKeyboardTextsSet.getText(KeyboardTextsSet.SWITCH_TO_ALPHA_KEY_LABEL), - mKeyboardView.getKeyVisualAttribute(), keyboard.mIconsSet); - mEmojiPalettesView.setVisibility(View.VISIBLE); - } - - public enum KeyboardSwitchState { - HIDDEN(-1), - SYMBOLS_SHIFTED(KeyboardId.ELEMENT_SYMBOLS_SHIFTED), - EMOJI(KeyboardId.ELEMENT_EMOJI_RECENTS), - OTHER(-1); - - final int mKeyboardId; - - KeyboardSwitchState(int keyboardId) { - mKeyboardId = keyboardId; - } - } - - public KeyboardSwitchState getKeyboardSwitchState() { - boolean hidden = !isShowingEmojiPalettes() - && (mKeyboardLayoutSet == null - || mKeyboardView == null - || !mKeyboardView.isShown()); - KeyboardSwitchState state; - if (hidden) { - return KeyboardSwitchState.HIDDEN; - } else if (isShowingEmojiPalettes()) { - return KeyboardSwitchState.EMOJI; - } else if (isShowingKeyboardId(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)) { - return KeyboardSwitchState.SYMBOLS_SHIFTED; - } - return KeyboardSwitchState.OTHER; - } - - public void onToggleKeyboard(@Nonnull final KeyboardSwitchState toggleState) { - KeyboardSwitchState currentState = getKeyboardSwitchState(); - Log.w(TAG, "onToggleKeyboard() : Current = " + currentState + " : Toggle = " + toggleState); - if (currentState == toggleState) { - mLatinIME.stopShowingInputView(); - mLatinIME.hideWindow(); - setAlphabetKeyboard(); - } else { - mLatinIME.startShowingInputView(true); - if (toggleState == KeyboardSwitchState.EMOJI) { - setEmojiKeyboard(); - } else { - mEmojiPalettesView.stopEmojiPalettes(); - mEmojiPalettesView.setVisibility(View.GONE); - - mMainKeyboardFrame.setVisibility(View.VISIBLE); - mKeyboardView.setVisibility(View.VISIBLE); - setKeyboard(toggleState.mKeyboardId, toggleState); - } - } - } - - // Future method for requesting an updating to the shift state. - @Override - public void requestUpdatingShiftState(final int autoCapsFlags, final int recapitalizeMode) { - if (DEBUG_ACTION) { - Log.d(TAG, "requestUpdatingShiftState: " - + " autoCapsFlags=" + CapsModeUtils.flagsToString(autoCapsFlags) - + " recapitalizeMode=" + RecapitalizeStatus.modeToString(recapitalizeMode)); - } - mState.onUpdateShiftState(autoCapsFlags, recapitalizeMode); - } - - // Implements {@link KeyboardState.SwitchActions}. - @Override - public void startDoubleTapShiftKeyTimer() { - if (DEBUG_TIMER_ACTION) { - Log.d(TAG, "startDoubleTapShiftKeyTimer"); - } - final MainKeyboardView keyboardView = getMainKeyboardView(); - if (keyboardView != null) { - keyboardView.startDoubleTapShiftKeyTimer(); - } - } - - // Implements {@link KeyboardState.SwitchActions}. - @Override - public void cancelDoubleTapShiftKeyTimer() { - if (DEBUG_TIMER_ACTION) { - Log.d(TAG, "setAlphabetKeyboard"); - } - final MainKeyboardView keyboardView = getMainKeyboardView(); - if (keyboardView != null) { - keyboardView.cancelDoubleTapShiftKeyTimer(); - } - } - - // Implements {@link KeyboardState.SwitchActions}. - @Override - public boolean isInDoubleTapShiftKeyTimeout() { - if (DEBUG_TIMER_ACTION) { - Log.d(TAG, "isInDoubleTapShiftKeyTimeout"); - } - final MainKeyboardView keyboardView = getMainKeyboardView(); - return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout(); - } - - /** - * Updates state machine to figure out when to automatically switch back to the previous mode. - */ - public void onEvent(final Event event, final int currentAutoCapsState, - final int currentRecapitalizeState) { - mState.onEvent(event, currentAutoCapsState, currentRecapitalizeState); - } - - public boolean isShowingKeyboardId(@Nonnull int... keyboardIds) { - if (mKeyboardView == null || !mKeyboardView.isShown()) { - return false; - } - int activeKeyboardId = mKeyboardView.getKeyboard().mId.mElementId; - for (int keyboardId : keyboardIds) { - if (activeKeyboardId == keyboardId) { - return true; - } - } - return false; - } - - public boolean isShowingEmojiPalettes() { - return mEmojiPalettesView != null && mEmojiPalettesView.isShown(); - } - - public boolean isShowingMoreKeysPanel() { - if (isShowingEmojiPalettes()) { - return false; - } - return mKeyboardView.isShowingMoreKeysPanel(); - } - - public View getVisibleKeyboardView() { - if (isShowingEmojiPalettes()) { - return mEmojiPalettesView; - } - return mKeyboardView; - } - - public MainKeyboardView getMainKeyboardView() { - return mKeyboardView; - } - - public void deallocateMemory() { - if (mKeyboardView != null) { - mKeyboardView.cancelAllOngoingEvents(); - mKeyboardView.deallocateMemory(); - } - if (mEmojiPalettesView != null) { - mEmojiPalettesView.stopEmojiPalettes(); - } - } - - public View onCreateInputView(@NonNull Context displayContext, - final boolean isHardwareAcceleratedDrawingEnabled) { - if (mKeyboardView != null) { - mKeyboardView.closing(); - } - - updateKeyboardThemeAndContextThemeWrapper( - displayContext, KeyboardTheme.getKeyboardTheme(displayContext /* context */)); - mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate( - R.layout.input_view, null); - mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame); - mEmojiPalettesView = (EmojiPalettesView)mCurrentInputView.findViewById( - R.id.emoji_palettes_view); - - mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); - mKeyboardView.setHardwareAcceleratedDrawingEnabled(isHardwareAcceleratedDrawingEnabled); - mKeyboardView.setKeyboardActionListener(mLatinIME); - mEmojiPalettesView.setHardwareAcceleratedDrawingEnabled( - isHardwareAcceleratedDrawingEnabled); - mEmojiPalettesView.setKeyboardActionListener(mLatinIME); - return mCurrentInputView; - } - - public int getKeyboardShiftMode() { - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) { - return WordComposer.CAPS_MODE_OFF; - } - switch (keyboard.mId.mElementId) { - case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: - case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: - return WordComposer.CAPS_MODE_MANUAL_SHIFT_LOCKED; - case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: - return WordComposer.CAPS_MODE_MANUAL_SHIFTED; - case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: - return WordComposer.CAPS_MODE_AUTO_SHIFTED; - default: - return WordComposer.CAPS_MODE_OFF; - } - } - - public int getCurrentKeyboardScriptId() { - if (null == mKeyboardLayoutSet) { - return ScriptUtils.SCRIPT_UNKNOWN; - } - return mKeyboardLayoutSet.getScriptId(); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java deleted file mode 100644 index 006d08696..000000000 --- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.keyboard; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Build; -import android.os.Build.VERSION_CODES; -import android.preference.PreferenceManager; -import android.util.Log; - -import com.android.inputmethod.compat.BuildCompatUtils; -import com.android.inputmethod.latin.R; - -import java.util.ArrayList; -import java.util.Arrays; - -public final class KeyboardTheme implements Comparable<KeyboardTheme> { - private static final String TAG = KeyboardTheme.class.getSimpleName(); - - static final String KLP_KEYBOARD_THEME_KEY = "pref_keyboard_layout_20110916"; - static final String LXX_KEYBOARD_THEME_KEY = "pref_keyboard_theme_20140509"; - - // These should be aligned with Keyboard.themeId and Keyboard.Case.keyboardTheme - // attributes' values in attrs.xml. - public static final int THEME_ID_ICS = 0; - public static final int THEME_ID_KLP = 2; - public static final int THEME_ID_LXX_LIGHT = 3; - public static final int THEME_ID_LXX_DARK = 4; - public static final int DEFAULT_THEME_ID = THEME_ID_KLP; - - private static KeyboardTheme[] AVAILABLE_KEYBOARD_THEMES; - - /* package private for testing */ - static final KeyboardTheme[] KEYBOARD_THEMES = { - new KeyboardTheme(THEME_ID_ICS, "ICS", R.style.KeyboardTheme_ICS, - // This has never been selected because we support ICS or later. - VERSION_CODES.BASE), - new KeyboardTheme(THEME_ID_KLP, "KLP", R.style.KeyboardTheme_KLP, - // Default theme for ICS, JB, and KLP. - VERSION_CODES.ICE_CREAM_SANDWICH), - new KeyboardTheme(THEME_ID_LXX_LIGHT, "LXXLight", R.style.KeyboardTheme_LXX_Light, - // Default theme for LXX. - Build.VERSION_CODES.LOLLIPOP), - new KeyboardTheme(THEME_ID_LXX_DARK, "LXXDark", R.style.KeyboardTheme_LXX_Dark, - // This has never been selected as default theme. - VERSION_CODES.BASE), - }; - - static { - // Sort {@link #KEYBOARD_THEME} by descending order of {@link #mMinApiVersion}. - Arrays.sort(KEYBOARD_THEMES); - } - - public final int mThemeId; - public final int mStyleId; - public final String mThemeName; - public final int mMinApiVersion; - - // Note: The themeId should be aligned with "themeId" attribute of Keyboard style - // in values/themes-<style>.xml. - private KeyboardTheme(final int themeId, final String themeName, final int styleId, - final int minApiVersion) { - mThemeId = themeId; - mThemeName = themeName; - mStyleId = styleId; - mMinApiVersion = minApiVersion; - } - - @Override - public int compareTo(final KeyboardTheme rhs) { - if (mMinApiVersion > rhs.mMinApiVersion) return -1; - if (mMinApiVersion < rhs.mMinApiVersion) return 1; - return 0; - } - - @Override - public boolean equals(final Object o) { - if (o == this) return true; - return (o instanceof KeyboardTheme) && ((KeyboardTheme)o).mThemeId == mThemeId; - } - - @Override - public int hashCode() { - return mThemeId; - } - - /* package private for testing */ - static KeyboardTheme searchKeyboardThemeById(final int themeId, - final KeyboardTheme[] availableThemeIds) { - // TODO: This search algorithm isn't optimal if there are many themes. - for (final KeyboardTheme theme : availableThemeIds) { - if (theme.mThemeId == themeId) { - return theme; - } - } - return null; - } - - /* package private for testing */ - static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs, - final int sdkVersion, final KeyboardTheme[] availableThemeArray) { - final String klpThemeIdString = prefs.getString(KLP_KEYBOARD_THEME_KEY, null); - if (klpThemeIdString != null) { - if (sdkVersion <= VERSION_CODES.KITKAT) { - try { - final int themeId = Integer.parseInt(klpThemeIdString); - final KeyboardTheme theme = searchKeyboardThemeById(themeId, - availableThemeArray); - if (theme != null) { - return theme; - } - Log.w(TAG, "Unknown keyboard theme in KLP preference: " + klpThemeIdString); - } catch (final NumberFormatException e) { - Log.w(TAG, "Illegal keyboard theme in KLP preference: " + klpThemeIdString, e); - } - } - // Remove old preference. - Log.i(TAG, "Remove KLP keyboard theme preference: " + klpThemeIdString); - prefs.edit().remove(KLP_KEYBOARD_THEME_KEY).apply(); - } - // TODO: This search algorithm isn't optimal if there are many themes. - for (final KeyboardTheme theme : availableThemeArray) { - if (sdkVersion >= theme.mMinApiVersion) { - return theme; - } - } - return searchKeyboardThemeById(DEFAULT_THEME_ID, availableThemeArray); - } - - public static String getKeyboardThemeName(final int themeId) { - final KeyboardTheme theme = searchKeyboardThemeById(themeId, KEYBOARD_THEMES); - return theme.mThemeName; - } - - public static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs) { - saveKeyboardThemeId(themeId, prefs, BuildCompatUtils.EFFECTIVE_SDK_INT); - } - - /* package private for testing */ - static String getPreferenceKey(final int sdkVersion) { - if (sdkVersion <= VERSION_CODES.KITKAT) { - return KLP_KEYBOARD_THEME_KEY; - } - return LXX_KEYBOARD_THEME_KEY; - } - - /* package private for testing */ - static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs, - final int sdkVersion) { - final String prefKey = getPreferenceKey(sdkVersion); - prefs.edit().putString(prefKey, Integer.toString(themeId)).apply(); - } - - public static KeyboardTheme getKeyboardTheme(final Context context) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - final KeyboardTheme[] availableThemeArray = getAvailableThemeArray(context); - return getKeyboardTheme(prefs, BuildCompatUtils.EFFECTIVE_SDK_INT, availableThemeArray); - } - - /* package private for testing */ - static KeyboardTheme[] getAvailableThemeArray(final Context context) { - if (AVAILABLE_KEYBOARD_THEMES == null) { - final int[] availableThemeIdStringArray = context.getResources().getIntArray( - R.array.keyboard_theme_ids); - final ArrayList<KeyboardTheme> availableThemeList = new ArrayList<>(); - for (final int id : availableThemeIdStringArray) { - final KeyboardTheme theme = searchKeyboardThemeById(id, KEYBOARD_THEMES); - if (theme != null) { - availableThemeList.add(theme); - } - } - AVAILABLE_KEYBOARD_THEMES = availableThemeList.toArray( - new KeyboardTheme[availableThemeList.size()]); - Arrays.sort(AVAILABLE_KEYBOARD_THEMES); - } - return AVAILABLE_KEYBOARD_THEMES; - } - - /* package private for testing */ - static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion, - final KeyboardTheme[] availableThemeArray) { - final String lxxThemeIdString = prefs.getString(LXX_KEYBOARD_THEME_KEY, null); - if (lxxThemeIdString == null) { - return getDefaultKeyboardTheme(prefs, sdkVersion, availableThemeArray); - } - try { - final int themeId = Integer.parseInt(lxxThemeIdString); - final KeyboardTheme theme = searchKeyboardThemeById(themeId, availableThemeArray); - if (theme != null) { - return theme; - } - Log.w(TAG, "Unknown keyboard theme in LXX preference: " + lxxThemeIdString); - } catch (final NumberFormatException e) { - Log.w(TAG, "Illegal keyboard theme in LXX preference: " + lxxThemeIdString, e); - } - // Remove preference that contains unknown or illegal theme id. - prefs.edit().remove(LXX_KEYBOARD_THEME_KEY).apply(); - return getDefaultKeyboardTheme(prefs, sdkVersion, availableThemeArray); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java deleted file mode 100644 index a42108477..000000000 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ /dev/null @@ -1,590 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.NinePatchDrawable; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.View; - -import com.android.inputmethod.keyboard.internal.KeyDrawParams; -import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.utils.TypefaceUtils; - -import java.util.HashSet; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * A view that renders a virtual {@link Keyboard}. - * - * @attr ref android.R.styleable#KeyboardView_keyBackground - * @attr ref android.R.styleable#KeyboardView_functionalKeyBackground - * @attr ref android.R.styleable#KeyboardView_spacebarBackground - * @attr ref android.R.styleable#KeyboardView_spacebarIconWidthRatio - * @attr ref android.R.styleable#Keyboard_Key_keyLabelFlags - * @attr ref android.R.styleable#KeyboardView_keyHintLetterPadding - * @attr ref android.R.styleable#KeyboardView_keyPopupHintLetter - * @attr ref android.R.styleable#KeyboardView_keyPopupHintLetterPadding - * @attr ref android.R.styleable#KeyboardView_keyShiftedLetterHintPadding - * @attr ref android.R.styleable#KeyboardView_keyTextShadowRadius - * @attr ref android.R.styleable#KeyboardView_verticalCorrection - * @attr ref android.R.styleable#Keyboard_Key_keyTypeface - * @attr ref android.R.styleable#Keyboard_Key_keyLetterSize - * @attr ref android.R.styleable#Keyboard_Key_keyLabelSize - * @attr ref android.R.styleable#Keyboard_Key_keyLargeLetterRatio - * @attr ref android.R.styleable#Keyboard_Key_keyLargeLabelRatio - * @attr ref android.R.styleable#Keyboard_Key_keyHintLetterRatio - * @attr ref android.R.styleable#Keyboard_Key_keyShiftedLetterHintRatio - * @attr ref android.R.styleable#Keyboard_Key_keyHintLabelRatio - * @attr ref android.R.styleable#Keyboard_Key_keyLabelOffCenterRatio - * @attr ref android.R.styleable#Keyboard_Key_keyHintLabelOffCenterRatio - * @attr ref android.R.styleable#Keyboard_Key_keyPreviewTextRatio - * @attr ref android.R.styleable#Keyboard_Key_keyTextColor - * @attr ref android.R.styleable#Keyboard_Key_keyTextColorDisabled - * @attr ref android.R.styleable#Keyboard_Key_keyTextShadowColor - * @attr ref android.R.styleable#Keyboard_Key_keyHintLetterColor - * @attr ref android.R.styleable#Keyboard_Key_keyHintLabelColor - * @attr ref android.R.styleable#Keyboard_Key_keyShiftedLetterHintInactivatedColor - * @attr ref android.R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor - * @attr ref android.R.styleable#Keyboard_Key_keyPreviewTextColor - */ -public class KeyboardView extends View { - // XML attributes - private final KeyVisualAttributes mKeyVisualAttributes; - // Default keyLabelFlags from {@link KeyboardTheme}. - // Currently only "alignHintLabelToBottom" is supported. - private final int mDefaultKeyLabelFlags; - private final float mKeyHintLetterPadding; - private final String mKeyPopupHintLetter; - private final float mKeyPopupHintLetterPadding; - private final float mKeyShiftedLetterHintPadding; - private final float mKeyTextShadowRadius; - private final float mVerticalCorrection; - private final Drawable mKeyBackground; - private final Drawable mFunctionalKeyBackground; - private final Drawable mSpacebarBackground; - private final float mSpacebarIconWidthRatio; - private final Rect mKeyBackgroundPadding = new Rect(); - private static final float KET_TEXT_SHADOW_RADIUS_DISABLED = -1.0f; - - // The maximum key label width in the proportion to the key width. - private static final float MAX_LABEL_RATIO = 0.90f; - - // Main keyboard - // TODO: Consider having a base keyboard object to make this @Nonnull - @Nullable - private Keyboard mKeyboard; - @Nonnull - private final KeyDrawParams mKeyDrawParams = new KeyDrawParams(); - - // Drawing - /** True if all keys should be drawn */ - private boolean mInvalidateAllKeys; - /** The keys that should be drawn */ - private final HashSet<Key> mInvalidatedKeys = new HashSet<>(); - /** The working rectangle for clipping */ - private final Rect mClipRect = new Rect(); - /** The keyboard bitmap buffer for faster updates */ - private Bitmap mOffscreenBuffer; - /** The canvas for the above mutable keyboard bitmap */ - @Nonnull - private final Canvas mOffscreenCanvas = new Canvas(); - @Nonnull - private final Paint mPaint = new Paint(); - private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics(); - - public KeyboardView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.keyboardViewStyle); - } - - public KeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - - final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs, - R.styleable.KeyboardView, defStyle, R.style.KeyboardView); - mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground); - mKeyBackground.getPadding(mKeyBackgroundPadding); - final Drawable functionalKeyBackground = keyboardViewAttr.getDrawable( - R.styleable.KeyboardView_functionalKeyBackground); - mFunctionalKeyBackground = (functionalKeyBackground != null) ? functionalKeyBackground - : mKeyBackground; - final Drawable spacebarBackground = keyboardViewAttr.getDrawable( - R.styleable.KeyboardView_spacebarBackground); - mSpacebarBackground = (spacebarBackground != null) ? spacebarBackground : mKeyBackground; - mSpacebarIconWidthRatio = keyboardViewAttr.getFloat( - R.styleable.KeyboardView_spacebarIconWidthRatio, 1.0f); - mKeyHintLetterPadding = keyboardViewAttr.getDimension( - R.styleable.KeyboardView_keyHintLetterPadding, 0.0f); - mKeyPopupHintLetter = keyboardViewAttr.getString( - R.styleable.KeyboardView_keyPopupHintLetter); - mKeyPopupHintLetterPadding = keyboardViewAttr.getDimension( - R.styleable.KeyboardView_keyPopupHintLetterPadding, 0.0f); - mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension( - R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0.0f); - mKeyTextShadowRadius = keyboardViewAttr.getFloat( - R.styleable.KeyboardView_keyTextShadowRadius, KET_TEXT_SHADOW_RADIUS_DISABLED); - mVerticalCorrection = keyboardViewAttr.getDimension( - R.styleable.KeyboardView_verticalCorrection, 0.0f); - keyboardViewAttr.recycle(); - - final TypedArray keyAttr = context.obtainStyledAttributes(attrs, - R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView); - mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0); - mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); - keyAttr.recycle(); - - mPaint.setAntiAlias(true); - } - - @Nullable - public KeyVisualAttributes getKeyVisualAttribute() { - return mKeyVisualAttributes; - } - - private static void blendAlpha(@Nonnull final Paint paint, final int alpha) { - final int color = paint.getColor(); - paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE, - Color.red(color), Color.green(color), Color.blue(color)); - } - - public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { - if (!enabled) return; - // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? - setLayerType(LAYER_TYPE_HARDWARE, null); - } - - /** - * Attaches a keyboard to this view. The keyboard can be switched at any time and the - * view will re-layout itself to accommodate the keyboard. - * @see Keyboard - * @see #getKeyboard() - * @param keyboard the keyboard to display in this view - */ - public void setKeyboard(@Nonnull final Keyboard keyboard) { - mKeyboard = keyboard; - final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; - mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes); - mKeyDrawParams.updateParams(keyHeight, keyboard.mKeyVisualAttributes); - invalidateAllKeys(); - requestLayout(); - } - - /** - * Returns the current keyboard being displayed by this view. - * @return the currently attached keyboard - * @see #setKeyboard(Keyboard) - */ - @Nullable - public Keyboard getKeyboard() { - return mKeyboard; - } - - protected float getVerticalCorrection() { - return mVerticalCorrection; - } - - @Nonnull - protected KeyDrawParams getKeyDrawParams() { - return mKeyDrawParams; - } - - protected void updateKeyDrawParams(final int keyHeight) { - mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes); - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - return; - } - // The main keyboard expands to the entire this {@link KeyboardView}. - final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); - final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom(); - setMeasuredDimension(width, height); - } - - @Override - protected void onDraw(final Canvas canvas) { - super.onDraw(canvas); - if (canvas.isHardwareAccelerated()) { - onDrawKeyboard(canvas); - return; - } - - final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty(); - if (bufferNeedsUpdates || mOffscreenBuffer == null) { - if (maybeAllocateOffscreenBuffer()) { - mInvalidateAllKeys = true; - // TODO: Stop using the offscreen canvas even when in software rendering - mOffscreenCanvas.setBitmap(mOffscreenBuffer); - } - onDrawKeyboard(mOffscreenCanvas); - } - canvas.drawBitmap(mOffscreenBuffer, 0.0f, 0.0f, null); - } - - private boolean maybeAllocateOffscreenBuffer() { - final int width = getWidth(); - final int height = getHeight(); - if (width == 0 || height == 0) { - return false; - } - if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == width - && mOffscreenBuffer.getHeight() == height) { - return false; - } - freeOffscreenBuffer(); - mOffscreenBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - return true; - } - - private void freeOffscreenBuffer() { - mOffscreenCanvas.setBitmap(null); - mOffscreenCanvas.setMatrix(null); - if (mOffscreenBuffer != null) { - mOffscreenBuffer.recycle(); - mOffscreenBuffer = null; - } - } - - private void onDrawKeyboard(@Nonnull final Canvas canvas) { - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) { - return; - } - - final Paint paint = mPaint; - final Drawable background = getBackground(); - // Calculate clip region and set. - final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty(); - final boolean isHardwareAccelerated = canvas.isHardwareAccelerated(); - // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on. - if (drawAllKeys || isHardwareAccelerated) { - if (!isHardwareAccelerated && background != null) { - // Need to draw keyboard background on {@link #mOffscreenBuffer}. - canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); - background.draw(canvas); - } - // Draw all keys. - for (final Key key : keyboard.getSortedKeys()) { - onDrawKey(key, canvas, paint); - } - } else { - for (final Key key : mInvalidatedKeys) { - if (!keyboard.hasKey(key)) { - continue; - } - if (background != null) { - // Need to redraw key's background on {@link #mOffscreenBuffer}. - final int x = key.getX() + getPaddingLeft(); - final int y = key.getY() + getPaddingTop(); - mClipRect.set(x, y, x + key.getWidth(), y + key.getHeight()); - canvas.save(); - canvas.clipRect(mClipRect); - canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); - background.draw(canvas); - canvas.restore(); - } - onDrawKey(key, canvas, paint); - } - } - - mInvalidatedKeys.clear(); - mInvalidateAllKeys = false; - } - - private void onDrawKey(@Nonnull final Key key, @Nonnull final Canvas canvas, - @Nonnull final Paint paint) { - final int keyDrawX = key.getDrawX() + getPaddingLeft(); - final int keyDrawY = key.getY() + getPaddingTop(); - canvas.translate(keyDrawX, keyDrawY); - - final KeyVisualAttributes attr = key.getVisualAttributes(); - final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(key.getHeight(), attr); - params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE; - - if (!key.isSpacer()) { - final Drawable background = key.selectBackgroundDrawable( - mKeyBackground, mFunctionalKeyBackground, mSpacebarBackground); - if (background != null) { - onDrawKeyBackground(key, canvas, background); - } - } - onDrawKeyTopVisuals(key, canvas, paint, params); - - canvas.translate(-keyDrawX, -keyDrawY); - } - - // Draw key background. - protected void onDrawKeyBackground(@Nonnull final Key key, @Nonnull final Canvas canvas, - @Nonnull final Drawable background) { - final int keyWidth = key.getDrawWidth(); - final int keyHeight = key.getHeight(); - final int bgWidth, bgHeight, bgX, bgY; - if (key.needsToKeepBackgroundAspectRatio(mDefaultKeyLabelFlags) - // HACK: To disable expanding normal/functional key background. - && !key.hasCustomActionLabel()) { - final int intrinsicWidth = background.getIntrinsicWidth(); - final int intrinsicHeight = background.getIntrinsicHeight(); - final float minScale = Math.min( - keyWidth / (float)intrinsicWidth, keyHeight / (float)intrinsicHeight); - bgWidth = (int)(intrinsicWidth * minScale); - bgHeight = (int)(intrinsicHeight * minScale); - bgX = (keyWidth - bgWidth) / 2; - bgY = (keyHeight - bgHeight) / 2; - } else { - final Rect padding = mKeyBackgroundPadding; - bgWidth = keyWidth + padding.left + padding.right; - bgHeight = keyHeight + padding.top + padding.bottom; - bgX = -padding.left; - bgY = -padding.top; - } - final Rect bounds = background.getBounds(); - if (bgWidth != bounds.right || bgHeight != bounds.bottom) { - background.setBounds(0, 0, bgWidth, bgHeight); - } - canvas.translate(bgX, bgY); - background.draw(canvas); - canvas.translate(-bgX, -bgY); - } - - // Draw key top visuals. - protected void onDrawKeyTopVisuals(@Nonnull final Key key, @Nonnull final Canvas canvas, - @Nonnull final Paint paint, @Nonnull final KeyDrawParams params) { - final int keyWidth = key.getDrawWidth(); - final int keyHeight = key.getHeight(); - final float centerX = keyWidth * 0.5f; - final float centerY = keyHeight * 0.5f; - - // Draw key label. - final Keyboard keyboard = getKeyboard(); - final Drawable icon = (keyboard == null) ? null - : key.getIcon(keyboard.mIconsSet, params.mAnimAlpha); - float labelX = centerX; - float labelBaseline = centerY; - final String label = key.getLabel(); - if (label != null) { - paint.setTypeface(key.selectTypeface(params)); - paint.setTextSize(key.selectTextSize(params)); - final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint); - final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint); - - // Vertical label text alignment. - labelBaseline = centerY + labelCharHeight / 2.0f; - - // Horizontal label text alignment - if (key.isAlignLabelOffCenter()) { - // The label is placed off center of the key. Used mainly on "phone number" layout. - labelX = centerX + params.mLabelOffCenterRatio * labelCharWidth; - paint.setTextAlign(Align.LEFT); - } else { - labelX = centerX; - paint.setTextAlign(Align.CENTER); - } - if (key.needsAutoXScale()) { - final float ratio = Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / - TypefaceUtils.getStringWidth(label, paint)); - if (key.needsAutoScale()) { - final float autoSize = paint.getTextSize() * ratio; - paint.setTextSize(autoSize); - } else { - paint.setTextScaleX(ratio); - } - } - - if (key.isEnabled()) { - paint.setColor(key.selectTextColor(params)); - // Set a drop shadow for the text if the shadow radius is positive value. - if (mKeyTextShadowRadius > 0.0f) { - paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor); - } else { - paint.clearShadowLayer(); - } - } else { - // Make label invisible - paint.setColor(Color.TRANSPARENT); - paint.clearShadowLayer(); - } - blendAlpha(paint, params.mAnimAlpha); - canvas.drawText(label, 0, label.length(), labelX, labelBaseline, paint); - // Turn off drop shadow and reset x-scale. - paint.clearShadowLayer(); - paint.setTextScaleX(1.0f); - } - - // Draw hint label. - final String hintLabel = key.getHintLabel(); - if (hintLabel != null) { - paint.setTextSize(key.selectHintTextSize(params)); - paint.setColor(key.selectHintTextColor(params)); - // TODO: Should add a way to specify type face for hint letters - paint.setTypeface(Typeface.DEFAULT_BOLD); - blendAlpha(paint, params.mAnimAlpha); - final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint); - final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint); - final float hintX, hintBaseline; - if (key.hasHintLabel()) { - // The hint label is placed just right of the key label. Used mainly on - // "phone number" layout. - hintX = labelX + params.mHintLabelOffCenterRatio * labelCharWidth; - if (key.isAlignHintLabelToBottom(mDefaultKeyLabelFlags)) { - hintBaseline = labelBaseline; - } else { - hintBaseline = centerY + labelCharHeight / 2.0f; - } - paint.setTextAlign(Align.LEFT); - } else if (key.hasShiftedLetterHint()) { - // The hint label is placed at top-right corner of the key. Used mainly on tablet. - hintX = keyWidth - mKeyShiftedLetterHintPadding - labelCharWidth / 2.0f; - paint.getFontMetrics(mFontMetrics); - hintBaseline = -mFontMetrics.top; - paint.setTextAlign(Align.CENTER); - } else { // key.hasHintLetter() - // The hint letter is placed at top-right corner of the key. Used mainly on phone. - final float hintDigitWidth = TypefaceUtils.getReferenceDigitWidth(paint); - final float hintLabelWidth = TypefaceUtils.getStringWidth(hintLabel, paint); - hintX = keyWidth - mKeyHintLetterPadding - - Math.max(hintDigitWidth, hintLabelWidth) / 2.0f; - hintBaseline = -paint.ascent(); - paint.setTextAlign(Align.CENTER); - } - final float adjustmentY = params.mHintLabelVerticalAdjustment * labelCharHeight; - canvas.drawText( - hintLabel, 0, hintLabel.length(), hintX, hintBaseline + adjustmentY, paint); - } - - // Draw key icon. - if (label == null && icon != null) { - final int iconWidth; - if (key.getCode() == Constants.CODE_SPACE && icon instanceof NinePatchDrawable) { - iconWidth = (int)(keyWidth * mSpacebarIconWidthRatio); - } else { - iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth); - } - final int iconHeight = icon.getIntrinsicHeight(); - final int iconY; - if (key.isAlignIconToBottom()) { - iconY = keyHeight - iconHeight; - } else { - iconY = (keyHeight - iconHeight) / 2; // Align vertically center. - } - final int iconX = (keyWidth - iconWidth) / 2; // Align horizontally center. - drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight); - } - - if (key.hasPopupHint() && key.getMoreKeys() != null) { - drawKeyPopupHint(key, canvas, paint, params); - } - } - - // Draw popup hint "..." at the bottom right corner of the key. - protected void drawKeyPopupHint(@Nonnull final Key key, @Nonnull final Canvas canvas, - @Nonnull final Paint paint, @Nonnull final KeyDrawParams params) { - if (TextUtils.isEmpty(mKeyPopupHintLetter)) { - return; - } - final int keyWidth = key.getDrawWidth(); - final int keyHeight = key.getHeight(); - - paint.setTypeface(params.mTypeface); - paint.setTextSize(params.mHintLetterSize); - paint.setColor(params.mHintLabelColor); - paint.setTextAlign(Align.CENTER); - final float hintX = keyWidth - mKeyHintLetterPadding - - TypefaceUtils.getReferenceCharWidth(paint) / 2.0f; - final float hintY = keyHeight - mKeyPopupHintLetterPadding; - canvas.drawText(mKeyPopupHintLetter, hintX, hintY, paint); - } - - protected static void drawIcon(@Nonnull final Canvas canvas,@Nonnull final Drawable icon, - final int x, final int y, final int width, final int height) { - canvas.translate(x, y); - icon.setBounds(0, 0, width, height); - icon.draw(canvas); - canvas.translate(-x, -y); - } - - public Paint newLabelPaint(@Nullable final Key key) { - final Paint paint = new Paint(); - paint.setAntiAlias(true); - if (key == null) { - paint.setTypeface(mKeyDrawParams.mTypeface); - paint.setTextSize(mKeyDrawParams.mLabelSize); - } else { - paint.setColor(key.selectTextColor(mKeyDrawParams)); - paint.setTypeface(key.selectTypeface(mKeyDrawParams)); - paint.setTextSize(key.selectTextSize(mKeyDrawParams)); - } - return paint; - } - - /** - * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient - * because the keyboard renders the keys to an off-screen buffer and an invalidate() only - * draws the cached buffer. - * @see #invalidateKey(Key) - */ - public void invalidateAllKeys() { - mInvalidatedKeys.clear(); - mInvalidateAllKeys = true; - invalidate(); - } - - /** - * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only - * one key is changing it's content. Any changes that affect the position or size of the key - * may not be honored. - * @param key key in the attached {@link Keyboard}. - * @see #invalidateAllKeys - */ - public void invalidateKey(@Nullable final Key key) { - if (mInvalidateAllKeys || key == null) { - return; - } - mInvalidatedKeys.add(key); - final int x = key.getX() + getPaddingLeft(); - final int y = key.getY() + getPaddingTop(); - invalidate(x, y, x + key.getWidth(), y + key.getHeight()); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - freeOffscreenBuffer(); - } - - public void deallocateMemory() { - freeOffscreenBuffer(); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java deleted file mode 100644 index fc8744ec6..000000000 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ /dev/null @@ -1,895 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.keyboard; - -import android.animation.AnimatorInflater; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Typeface; -import android.preference.PreferenceManager; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; - -import com.android.inputmethod.accessibility.AccessibilityUtils; -import com.android.inputmethod.accessibility.MainKeyboardAccessibilityDelegate; -import com.android.inputmethod.annotations.ExternallyReferenced; -import com.android.inputmethod.keyboard.internal.DrawingPreviewPlacerView; -import com.android.inputmethod.keyboard.internal.DrawingProxy; -import com.android.inputmethod.keyboard.internal.GestureFloatingTextDrawingPreview; -import com.android.inputmethod.keyboard.internal.GestureTrailsDrawingPreview; -import com.android.inputmethod.keyboard.internal.KeyDrawParams; -import com.android.inputmethod.keyboard.internal.KeyPreviewChoreographer; -import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams; -import com.android.inputmethod.keyboard.internal.KeyPreviewView; -import com.android.inputmethod.keyboard.internal.MoreKeySpec; -import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper; -import com.android.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview; -import com.android.inputmethod.keyboard.internal.TimerHandler; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodSubtype; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.CoordinateUtils; -import com.android.inputmethod.latin.settings.DebugSettings; -import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils; -import com.android.inputmethod.latin.utils.TypefaceUtils; - -import java.util.Locale; -import java.util.WeakHashMap; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * A view that is responsible for detecting key presses and touch movements. - * - * @attr ref android.R.styleable#MainKeyboardView_languageOnSpacebarTextRatio - * @attr ref android.R.styleable#MainKeyboardView_languageOnSpacebarTextColor - * @attr ref android.R.styleable#MainKeyboardView_languageOnSpacebarTextShadowRadius - * @attr ref android.R.styleable#MainKeyboardView_languageOnSpacebarTextShadowColor - * @attr ref android.R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha - * @attr ref android.R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator - * @attr ref android.R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator - * @attr ref android.R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator - * @attr ref android.R.styleable#MainKeyboardView_keyHysteresisDistance - * @attr ref android.R.styleable#MainKeyboardView_touchNoiseThresholdTime - * @attr ref android.R.styleable#MainKeyboardView_touchNoiseThresholdDistance - * @attr ref android.R.styleable#MainKeyboardView_keySelectionByDraggingFinger - * @attr ref android.R.styleable#MainKeyboardView_keyRepeatStartTimeout - * @attr ref android.R.styleable#MainKeyboardView_keyRepeatInterval - * @attr ref android.R.styleable#MainKeyboardView_longPressKeyTimeout - * @attr ref android.R.styleable#MainKeyboardView_longPressShiftKeyTimeout - * @attr ref android.R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout - * @attr ref android.R.styleable#MainKeyboardView_keyPreviewLayout - * @attr ref android.R.styleable#MainKeyboardView_keyPreviewOffset - * @attr ref android.R.styleable#MainKeyboardView_keyPreviewHeight - * @attr ref android.R.styleable#MainKeyboardView_keyPreviewLingerTimeout - * @attr ref android.R.styleable#MainKeyboardView_keyPreviewShowUpAnimator - * @attr ref android.R.styleable#MainKeyboardView_keyPreviewDismissAnimator - * @attr ref android.R.styleable#MainKeyboardView_moreKeysKeyboardLayout - * @attr ref android.R.styleable#MainKeyboardView_moreKeysKeyboardForActionLayout - * @attr ref android.R.styleable#MainKeyboardView_backgroundDimAlpha - * @attr ref android.R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint - * @attr ref android.R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout - * @attr ref android.R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping - * @attr ref android.R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold - * @attr ref android.R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration - * @attr ref android.R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom - * @attr ref android.R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo - * @attr ref android.R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom - * @attr ref android.R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo - * @attr ref android.R.styleable#MainKeyboardView_gestureSamplingMinimumDistance - * @attr ref android.R.styleable#MainKeyboardView_gestureRecognitionMinimumTime - * @attr ref android.R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold - * @attr ref android.R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration - */ -public final class MainKeyboardView extends KeyboardView implements DrawingProxy, - MoreKeysPanel.Controller { - private static final String TAG = MainKeyboardView.class.getSimpleName(); - - /** Listener for {@link KeyboardActionListener}. */ - private KeyboardActionListener mKeyboardActionListener; - - /* Space key and its icon and background. */ - private Key mSpaceKey; - // Stuff to draw language name on spacebar. - private final int mLanguageOnSpacebarFinalAlpha; - private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator; - private int mLanguageOnSpacebarFormatType; - private boolean mHasMultipleEnabledIMEsOrSubtypes; - private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE; - private final float mLanguageOnSpacebarTextRatio; - private float mLanguageOnSpacebarTextSize; - private final int mLanguageOnSpacebarTextColor; - private final float mLanguageOnSpacebarTextShadowRadius; - private final int mLanguageOnSpacebarTextShadowColor; - private static final float LANGUAGE_ON_SPACEBAR_TEXT_SHADOW_RADIUS_DISABLED = -1.0f; - // The minimum x-scale to fit the language name on spacebar. - private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f; - - // Stuff to draw altCodeWhileTyping keys. - private final ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator; - private final ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator; - private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; - - // Drawing preview placer view - private final DrawingPreviewPlacerView mDrawingPreviewPlacerView; - private final int[] mOriginCoords = CoordinateUtils.newInstance(); - private final GestureFloatingTextDrawingPreview mGestureFloatingTextDrawingPreview; - private final GestureTrailsDrawingPreview mGestureTrailsDrawingPreview; - private final SlidingKeyInputDrawingPreview mSlidingKeyInputDrawingPreview; - - // Key preview - private final KeyPreviewDrawParams mKeyPreviewDrawParams; - private final KeyPreviewChoreographer mKeyPreviewChoreographer; - - // More keys keyboard - private final Paint mBackgroundDimAlphaPaint = new Paint(); - private final View mMoreKeysKeyboardContainer; - private final View mMoreKeysKeyboardForActionContainer; - private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = new WeakHashMap<>(); - private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; - // More keys panel (used by both more keys keyboard and more suggestions view) - // TODO: Consider extending to support multiple more keys panels - private MoreKeysPanel mMoreKeysPanel; - - // Gesture floating preview text - // TODO: Make this parameter customizable by user via settings. - private int mGestureFloatingPreviewTextLingerTimeout; - - private final KeyDetector mKeyDetector; - private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper; - - private final TimerHandler mTimerHandler; - private final int mLanguageOnSpacebarHorizontalMargin; - - private MainKeyboardAccessibilityDelegate mAccessibilityDelegate; - - public MainKeyboardView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.mainKeyboardViewStyle); - } - - public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - - final DrawingPreviewPlacerView drawingPreviewPlacerView = - new DrawingPreviewPlacerView(context, attrs); - - final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes( - attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView); - final int ignoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0); - final int gestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0); - mTimerHandler = new TimerHandler( - this, ignoreAltCodeKeyTimeout, gestureRecognitionUpdateTime); - - final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f); - final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f); - mKeyDetector = new KeyDetector( - keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier); - - PointerTracker.init(mainKeyboardViewAttr, mTimerHandler, this /* DrawingProxy */); - - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - final boolean forceNonDistinctMultitouch = prefs.getBoolean( - DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false); - final boolean hasDistinctMultitouch = context.getPackageManager() - .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT) - && !forceNonDistinctMultitouch; - mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null - : new NonDistinctMultitouchHelper(); - - final int backgroundDimAlpha = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_backgroundDimAlpha, 0); - mBackgroundDimAlphaPaint.setColor(Color.BLACK); - mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha); - mLanguageOnSpacebarTextRatio = mainKeyboardViewAttr.getFraction( - R.styleable.MainKeyboardView_languageOnSpacebarTextRatio, 1, 1, 1.0f); - mLanguageOnSpacebarTextColor = mainKeyboardViewAttr.getColor( - R.styleable.MainKeyboardView_languageOnSpacebarTextColor, 0); - mLanguageOnSpacebarTextShadowRadius = mainKeyboardViewAttr.getFloat( - R.styleable.MainKeyboardView_languageOnSpacebarTextShadowRadius, - LANGUAGE_ON_SPACEBAR_TEXT_SHADOW_RADIUS_DISABLED); - mLanguageOnSpacebarTextShadowColor = mainKeyboardViewAttr.getColor( - R.styleable.MainKeyboardView_languageOnSpacebarTextShadowColor, 0); - mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha, - Constants.Color.ALPHA_OPAQUE); - final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId( - R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0); - final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId( - R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0); - final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId( - R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); - - mKeyPreviewDrawParams = new KeyPreviewDrawParams(mainKeyboardViewAttr); - mKeyPreviewChoreographer = new KeyPreviewChoreographer(mKeyPreviewDrawParams); - - final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId( - R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0); - final int moreKeysKeyboardForActionLayoutId = mainKeyboardViewAttr.getResourceId( - R.styleable.MainKeyboardView_moreKeysKeyboardForActionLayout, - moreKeysKeyboardLayoutId); - mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean( - R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); - - mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0); - - mGestureFloatingTextDrawingPreview = new GestureFloatingTextDrawingPreview( - mainKeyboardViewAttr); - mGestureFloatingTextDrawingPreview.setDrawingView(drawingPreviewPlacerView); - - mGestureTrailsDrawingPreview = new GestureTrailsDrawingPreview(mainKeyboardViewAttr); - mGestureTrailsDrawingPreview.setDrawingView(drawingPreviewPlacerView); - - mSlidingKeyInputDrawingPreview = new SlidingKeyInputDrawingPreview(mainKeyboardViewAttr); - mSlidingKeyInputDrawingPreview.setDrawingView(drawingPreviewPlacerView); - mainKeyboardViewAttr.recycle(); - - mDrawingPreviewPlacerView = drawingPreviewPlacerView; - - final LayoutInflater inflater = LayoutInflater.from(getContext()); - mMoreKeysKeyboardContainer = inflater.inflate(moreKeysKeyboardLayoutId, null); - mMoreKeysKeyboardForActionContainer = inflater.inflate( - moreKeysKeyboardForActionLayoutId, null); - mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator( - languageOnSpacebarFadeoutAnimatorResId, this); - mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator( - altCodeKeyWhileTypingFadeoutAnimatorResId, this); - mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator( - altCodeKeyWhileTypingFadeinAnimatorResId, this); - - mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; - - mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension( - R.dimen.config_language_on_spacebar_horizontal_margin); - } - - @Override - public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { - super.setHardwareAcceleratedDrawingEnabled(enabled); - mDrawingPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled); - } - - private ObjectAnimator loadObjectAnimator(final int resId, final Object target) { - if (resId == 0) { - // TODO: Stop returning null. - return null; - } - final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator( - getContext(), resId); - if (animator != null) { - animator.setTarget(target); - } - return animator; - } - - private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel, - final ObjectAnimator animatorToStart) { - if (animatorToCancel == null || animatorToStart == null) { - // TODO: Stop using null as a no-operation animator. - return; - } - float startFraction = 0.0f; - if (animatorToCancel.isStarted()) { - animatorToCancel.cancel(); - startFraction = 1.0f - animatorToCancel.getAnimatedFraction(); - } - final long startTime = (long)(animatorToStart.getDuration() * startFraction); - animatorToStart.start(); - animatorToStart.setCurrentPlayTime(startTime); - } - - // Implements {@link DrawingProxy#startWhileTypingAnimation(int)}. - /** - * Called when a while-typing-animation should be started. - * @param fadeInOrOut {@link DrawingProxy#FADE_IN} starts while-typing-fade-in animation. - * {@link DrawingProxy#FADE_OUT} starts while-typing-fade-out animation. - */ - @Override - public void startWhileTypingAnimation(final int fadeInOrOut) { - switch (fadeInOrOut) { - case DrawingProxy.FADE_IN: - cancelAndStartAnimators( - mAltCodeKeyWhileTypingFadeoutAnimator, mAltCodeKeyWhileTypingFadeinAnimator); - break; - case DrawingProxy.FADE_OUT: - cancelAndStartAnimators( - mAltCodeKeyWhileTypingFadeinAnimator, mAltCodeKeyWhileTypingFadeoutAnimator); - break; - } - } - - @ExternallyReferenced - public int getLanguageOnSpacebarAnimAlpha() { - return mLanguageOnSpacebarAnimAlpha; - } - - @ExternallyReferenced - public void setLanguageOnSpacebarAnimAlpha(final int alpha) { - mLanguageOnSpacebarAnimAlpha = alpha; - invalidateKey(mSpaceKey); - } - - @ExternallyReferenced - public int getAltCodeKeyWhileTypingAnimAlpha() { - return mAltCodeKeyWhileTypingAnimAlpha; - } - - @ExternallyReferenced - public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) { - if (mAltCodeKeyWhileTypingAnimAlpha == alpha) { - return; - } - // Update the visual of alt-code-key-while-typing. - mAltCodeKeyWhileTypingAnimAlpha = alpha; - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) { - return; - } - for (final Key key : keyboard.mAltCodeKeysWhileTyping) { - invalidateKey(key); - } - } - - public void setKeyboardActionListener(final KeyboardActionListener listener) { - mKeyboardActionListener = listener; - PointerTracker.setKeyboardActionListener(listener); - } - - // TODO: We should reconsider which coordinate system should be used to represent keyboard - // event. - public int getKeyX(final int x) { - return Constants.isValidCoordinate(x) ? mKeyDetector.getTouchX(x) : x; - } - - // TODO: We should reconsider which coordinate system should be used to represent keyboard - // event. - public int getKeyY(final int y) { - return Constants.isValidCoordinate(y) ? mKeyDetector.getTouchY(y) : y; - } - - /** - * Attaches a keyboard to this view. The keyboard can be switched at any time and the - * view will re-layout itself to accommodate the keyboard. - * @see Keyboard - * @see #getKeyboard() - * @param keyboard the keyboard to display in this view - */ - @Override - public void setKeyboard(final Keyboard keyboard) { - // Remove any pending messages, except dismissing preview and key repeat. - mTimerHandler.cancelLongPressTimers(); - super.setKeyboard(keyboard); - mKeyDetector.setKeyboard( - keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection()); - PointerTracker.setKeyDetector(mKeyDetector); - mMoreKeysKeyboardCache.clear(); - - mSpaceKey = keyboard.getKey(Constants.CODE_SPACE); - final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; - mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio; - - if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { - if (mAccessibilityDelegate == null) { - mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this, mKeyDetector); - } - mAccessibilityDelegate.setKeyboard(keyboard); - } else { - mAccessibilityDelegate = null; - } - } - - /** - * Enables or disables the key preview popup. This is a popup that shows a magnified - * version of the depressed key. By default the preview is enabled. - * @param previewEnabled whether or not to enable the key feedback preview - * @param delay the delay after which the preview is dismissed - */ - public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) { - mKeyPreviewDrawParams.setPopupEnabled(previewEnabled, delay); - } - - /** - * Enables or disables the key preview popup animations and set animations' parameters. - * - * @param hasCustomAnimationParams false to use the default key preview popup animations - * specified by keyPreviewShowUpAnimator and keyPreviewDismissAnimator attributes. - * true to override the default animations with the specified parameters. - * @param showUpStartXScale from this x-scale the show up animation will start. - * @param showUpStartYScale from this y-scale the show up animation will start. - * @param showUpDuration the duration of the show up animation in milliseconds. - * @param dismissEndXScale to this x-scale the dismiss animation will end. - * @param dismissEndYScale to this y-scale the dismiss animation will end. - * @param dismissDuration the duration of the dismiss animation in milliseconds. - */ - public void setKeyPreviewAnimationParams(final boolean hasCustomAnimationParams, - final float showUpStartXScale, final float showUpStartYScale, final int showUpDuration, - final float dismissEndXScale, final float dismissEndYScale, final int dismissDuration) { - mKeyPreviewDrawParams.setAnimationParams(hasCustomAnimationParams, - showUpStartXScale, showUpStartYScale, showUpDuration, - dismissEndXScale, dismissEndYScale, dismissDuration); - } - - private void locatePreviewPlacerView() { - getLocationInWindow(mOriginCoords); - mDrawingPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, getWidth(), getHeight()); - } - - private void installPreviewPlacerView() { - final View rootView = getRootView(); - if (rootView == null) { - Log.w(TAG, "Cannot find root view"); - return; - } - final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content); - // Note: It'd be very weird if we get null by android.R.id.content. - if (windowContentView == null) { - Log.w(TAG, "Cannot find android.R.id.content view to add DrawingPreviewPlacerView"); - return; - } - windowContentView.addView(mDrawingPreviewPlacerView); - } - - // Implements {@link DrawingProxy#onKeyPressed(Key,boolean)}. - @Override - public void onKeyPressed(@Nonnull final Key key, final boolean withPreview) { - key.onPressed(); - invalidateKey(key); - if (withPreview && !key.noKeyPreview()) { - showKeyPreview(key); - } - } - - private void showKeyPreview(@Nonnull final Key key) { - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) { - return; - } - final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams; - if (!previewParams.isPopupEnabled()) { - previewParams.setVisibleOffset(-keyboard.mVerticalGap); - return; - } - - locatePreviewPlacerView(); - getLocationInWindow(mOriginCoords); - mKeyPreviewChoreographer.placeAndShowKeyPreview(key, keyboard.mIconsSet, getKeyDrawParams(), - getWidth(), mOriginCoords, mDrawingPreviewPlacerView, isHardwareAccelerated()); - } - - private void dismissKeyPreviewWithoutDelay(@Nonnull final Key key) { - mKeyPreviewChoreographer.dismissKeyPreview(key, false /* withAnimation */); - invalidateKey(key); - } - - // Implements {@link DrawingProxy#onKeyReleased(Key,boolean)}. - @Override - public void onKeyReleased(@Nonnull final Key key, final boolean withAnimation) { - key.onReleased(); - invalidateKey(key); - if (!key.noKeyPreview()) { - if (withAnimation) { - dismissKeyPreview(key); - } else { - dismissKeyPreviewWithoutDelay(key); - } - } - } - - private void dismissKeyPreview(@Nonnull final Key key) { - if (isHardwareAccelerated()) { - mKeyPreviewChoreographer.dismissKeyPreview(key, true /* withAnimation */); - return; - } - // TODO: Implement preference option to control key preview method and duration. - mTimerHandler.postDismissKeyPreview(key, mKeyPreviewDrawParams.getLingerTimeout()); - } - - public void setSlidingKeyInputPreviewEnabled(final boolean enabled) { - mSlidingKeyInputDrawingPreview.setPreviewEnabled(enabled); - } - - @Override - public void showSlidingKeyInputPreview(@Nullable final PointerTracker tracker) { - locatePreviewPlacerView(); - if (tracker != null) { - mSlidingKeyInputDrawingPreview.setPreviewPosition(tracker); - } else { - mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview(); - } - } - - private void setGesturePreviewMode(final boolean isGestureTrailEnabled, - final boolean isGestureFloatingPreviewTextEnabled) { - mGestureFloatingTextDrawingPreview.setPreviewEnabled(isGestureFloatingPreviewTextEnabled); - mGestureTrailsDrawingPreview.setPreviewEnabled(isGestureTrailEnabled); - } - - public void showGestureFloatingPreviewText(@Nonnull final SuggestedWords suggestedWords, - final boolean dismissDelayed) { - locatePreviewPlacerView(); - final GestureFloatingTextDrawingPreview gestureFloatingTextDrawingPreview = - mGestureFloatingTextDrawingPreview; - gestureFloatingTextDrawingPreview.setSuggetedWords(suggestedWords); - if (dismissDelayed) { - mTimerHandler.postDismissGestureFloatingPreviewText( - mGestureFloatingPreviewTextLingerTimeout); - } - } - - // Implements {@link DrawingProxy#dismissGestureFloatingPreviewTextWithoutDelay()}. - @Override - public void dismissGestureFloatingPreviewTextWithoutDelay() { - mGestureFloatingTextDrawingPreview.dismissGestureFloatingPreviewText(); - } - - @Override - public void showGestureTrail(@Nonnull final PointerTracker tracker, - final boolean showsFloatingPreviewText) { - locatePreviewPlacerView(); - if (showsFloatingPreviewText) { - mGestureFloatingTextDrawingPreview.setPreviewPosition(tracker); - } - mGestureTrailsDrawingPreview.setPreviewPosition(tracker); - } - - // Note that this method is called from a non-UI thread. - @SuppressWarnings("static-method") - public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { - PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable); - } - - public void setGestureHandlingEnabledByUser(final boolean isGestureHandlingEnabledByUser, - final boolean isGestureTrailEnabled, - final boolean isGestureFloatingPreviewTextEnabled) { - PointerTracker.setGestureHandlingEnabledByUser(isGestureHandlingEnabledByUser); - setGesturePreviewMode(isGestureHandlingEnabledByUser && isGestureTrailEnabled, - isGestureHandlingEnabledByUser && isGestureFloatingPreviewTextEnabled); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - installPreviewPlacerView(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mDrawingPreviewPlacerView.removeAllViews(); - } - - // Implements {@link DrawingProxy@showMoreKeysKeyboard(Key,PointerTracker)}. - @Override - @Nullable - public MoreKeysPanel showMoreKeysKeyboard(@Nonnull final Key key, - @Nonnull final PointerTracker tracker) { - final MoreKeySpec[] moreKeys = key.getMoreKeys(); - if (moreKeys == null) { - return null; - } - Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key); - if (moreKeysKeyboard == null) { - // {@link KeyPreviewDrawParams#mPreviewVisibleWidth} should have been set at - // {@link KeyPreviewChoreographer#placeKeyPreview(Key,TextView,KeyboardIconsSet,KeyDrawParams,int,int[]}, - // though there may be some chances that the value is zero. <code>width == 0</code> - // will cause zero-division error at - // {@link MoreKeysKeyboardParams#setParameters(int,int,int,int,int,int,boolean,int)}. - final boolean isSingleMoreKeyWithPreview = mKeyPreviewDrawParams.isPopupEnabled() - && !key.noKeyPreview() && moreKeys.length == 1 - && mKeyPreviewDrawParams.getVisibleWidth() > 0; - final MoreKeysKeyboard.Builder builder = new MoreKeysKeyboard.Builder( - getContext(), key, getKeyboard(), isSingleMoreKeyWithPreview, - mKeyPreviewDrawParams.getVisibleWidth(), - mKeyPreviewDrawParams.getVisibleHeight(), newLabelPaint(key)); - moreKeysKeyboard = builder.build(); - mMoreKeysKeyboardCache.put(key, moreKeysKeyboard); - } - - final View container = key.isActionKey() ? mMoreKeysKeyboardForActionContainer - : mMoreKeysKeyboardContainer; - final MoreKeysKeyboardView moreKeysKeyboardView = - (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); - moreKeysKeyboardView.setKeyboard(moreKeysKeyboard); - container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - final int[] lastCoords = CoordinateUtils.newInstance(); - tracker.getLastCoordinates(lastCoords); - final boolean keyPreviewEnabled = mKeyPreviewDrawParams.isPopupEnabled() - && !key.noKeyPreview(); - // The more keys keyboard is usually horizontally aligned with the center of the parent key. - // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more - // keys keyboard is placed at the touch point of the parent key. - final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled) - ? CoordinateUtils.x(lastCoords) - : key.getX() + key.getWidth() / 2; - // The more keys keyboard is usually vertically aligned with the top edge of the parent key - // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically - // aligned with the bottom edge of the visible part of the key preview. - // {@code mPreviewVisibleOffset} has been set appropriately in - // {@link KeyboardView#showKeyPreview(PointerTracker)}. - final int pointY = key.getY() + mKeyPreviewDrawParams.getVisibleOffset(); - moreKeysKeyboardView.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); - return moreKeysKeyboardView; - } - - public boolean isInDraggingFinger() { - if (isShowingMoreKeysPanel()) { - return true; - } - return PointerTracker.isAnyInDraggingFinger(); - } - - @Override - public void onShowMoreKeysPanel(final MoreKeysPanel panel) { - locatePreviewPlacerView(); - // Dismiss another {@link MoreKeysPanel} that may be being showed. - onDismissMoreKeysPanel(); - // Dismiss all key previews that may be being showed. - PointerTracker.setReleasedKeyGraphicsToAllKeys(); - // Dismiss sliding key input preview that may be being showed. - mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview(); - panel.showInParent(mDrawingPreviewPlacerView); - mMoreKeysPanel = panel; - } - - public boolean isShowingMoreKeysPanel() { - return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent(); - } - - @Override - public void onCancelMoreKeysPanel() { - PointerTracker.dismissAllMoreKeysPanels(); - } - - @Override - public void onDismissMoreKeysPanel() { - if (isShowingMoreKeysPanel()) { - mMoreKeysPanel.removeFromParent(); - mMoreKeysPanel = null; - } - } - - public void startDoubleTapShiftKeyTimer() { - mTimerHandler.startDoubleTapShiftKeyTimer(); - } - - public void cancelDoubleTapShiftKeyTimer() { - mTimerHandler.cancelDoubleTapShiftKeyTimer(); - } - - public boolean isInDoubleTapShiftKeyTimeout() { - return mTimerHandler.isInDoubleTapShiftKeyTimeout(); - } - - @Override - public boolean onTouchEvent(final MotionEvent event) { - if (getKeyboard() == null) { - return false; - } - if (mNonDistinctMultitouchHelper != null) { - if (event.getPointerCount() > 1 && mTimerHandler.isInKeyRepeat()) { - // Key repeating timer will be canceled if 2 or more keys are in action. - mTimerHandler.cancelKeyRepeatTimers(); - } - // Non distinct multitouch screen support - mNonDistinctMultitouchHelper.processMotionEvent(event, mKeyDetector); - return true; - } - return processMotionEvent(event); - } - - public boolean processMotionEvent(final MotionEvent event) { - final int index = event.getActionIndex(); - final int id = event.getPointerId(index); - final PointerTracker tracker = PointerTracker.getPointerTracker(id); - // When a more keys panel is showing, we should ignore other fingers' single touch events - // other than the finger that is showing the more keys panel. - if (isShowingMoreKeysPanel() && !tracker.isShowingMoreKeysPanel() - && PointerTracker.getActivePointerTrackerCount() == 1) { - return true; - } - tracker.processMotionEvent(event, mKeyDetector); - return true; - } - - public void cancelAllOngoingEvents() { - mTimerHandler.cancelAllMessages(); - PointerTracker.setReleasedKeyGraphicsToAllKeys(); - mGestureFloatingTextDrawingPreview.dismissGestureFloatingPreviewText(); - mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview(); - PointerTracker.dismissAllMoreKeysPanels(); - PointerTracker.cancelAllPointerTrackers(); - } - - public void closing() { - cancelAllOngoingEvents(); - mMoreKeysKeyboardCache.clear(); - } - - public void onHideWindow() { - onDismissMoreKeysPanel(); - final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; - if (accessibilityDelegate != null - && AccessibilityUtils.getInstance().isAccessibilityEnabled()) { - accessibilityDelegate.onHideWindow(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onHoverEvent(final MotionEvent event) { - final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; - if (accessibilityDelegate != null - && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { - return accessibilityDelegate.onHoverEvent(event); - } - return super.onHoverEvent(event); - } - - public void updateShortcutKey(final boolean available) { - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) { - return; - } - final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT); - if (shortcutKey == null) { - return; - } - shortcutKey.setEnabled(available); - invalidateKey(shortcutKey); - } - - public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged, - final int languageOnSpacebarFormatType, - final boolean hasMultipleEnabledIMEsOrSubtypes) { - if (subtypeChanged) { - KeyPreviewView.clearTextCache(); - } - mLanguageOnSpacebarFormatType = languageOnSpacebarFormatType; - mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes; - final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator; - if (animator == null) { - mLanguageOnSpacebarFormatType = LanguageOnSpacebarUtils.FORMAT_TYPE_NONE; - } else { - if (subtypeChanged - && languageOnSpacebarFormatType != LanguageOnSpacebarUtils.FORMAT_TYPE_NONE) { - setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE); - if (animator.isStarted()) { - animator.cancel(); - } - animator.start(); - } else { - if (!animator.isStarted()) { - mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha; - } - } - } - invalidateKey(mSpaceKey); - } - - @Override - protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, - final KeyDrawParams params) { - if (key.altCodeWhileTyping() && key.isEnabled()) { - params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; - } - super.onDrawKeyTopVisuals(key, canvas, paint, params); - final int code = key.getCode(); - if (code == Constants.CODE_SPACE) { - // If input language are explicitly selected. - if (mLanguageOnSpacebarFormatType != LanguageOnSpacebarUtils.FORMAT_TYPE_NONE) { - drawLanguageOnSpacebar(key, canvas, paint); - } - // Whether space key needs to show the "..." popup hint for special purposes - if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { - drawKeyPopupHint(key, canvas, paint, params); - } - } else if (code == Constants.CODE_LANGUAGE_SWITCH) { - drawKeyPopupHint(key, canvas, paint, params); - } - } - - private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) { - final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2; - paint.setTextScaleX(1.0f); - final float textWidth = TypefaceUtils.getStringWidth(text, paint); - if (textWidth < width) { - return true; - } - - final float scaleX = maxTextWidth / textWidth; - if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) { - return false; - } - - paint.setTextScaleX(scaleX); - return TypefaceUtils.getStringWidth(text, paint) < maxTextWidth; - } - - // Layout language name on spacebar. - private String layoutLanguageOnSpacebar(final Paint paint, - final RichInputMethodSubtype subtype, final int width) { - // Choose appropriate language name to fit into the width. - if (mLanguageOnSpacebarFormatType == LanguageOnSpacebarUtils.FORMAT_TYPE_FULL_LOCALE) { - final String fullText = subtype.getFullDisplayName(); - if (fitsTextIntoWidth(width, fullText, paint)) { - return fullText; - } - } - - final String middleText = subtype.getMiddleDisplayName(); - if (fitsTextIntoWidth(width, middleText, paint)) { - return middleText; - } - - return ""; - } - - private void drawLanguageOnSpacebar(final Key key, final Canvas canvas, final Paint paint) { - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) { - return; - } - final int width = key.getWidth(); - final int height = key.getHeight(); - paint.setTextAlign(Align.CENTER); - paint.setTypeface(Typeface.DEFAULT); - paint.setTextSize(mLanguageOnSpacebarTextSize); - final String language = layoutLanguageOnSpacebar(paint, keyboard.mId.mSubtype, width); - // Draw language text with shadow - final float descent = paint.descent(); - final float textHeight = -paint.ascent() + descent; - final float baseline = height / 2 + textHeight / 2; - if (mLanguageOnSpacebarTextShadowRadius > 0.0f) { - paint.setShadowLayer(mLanguageOnSpacebarTextShadowRadius, 0, 0, - mLanguageOnSpacebarTextShadowColor); - } else { - paint.clearShadowLayer(); - } - paint.setColor(mLanguageOnSpacebarTextColor); - paint.setAlpha(mLanguageOnSpacebarAnimAlpha); - canvas.drawText(language, width / 2, baseline - descent, paint); - paint.clearShadowLayer(); - paint.setTextScaleX(1.0f); - } - - @Override - public void deallocateMemory() { - super.deallocateMemory(); - mDrawingPreviewPlacerView.deallocateMemory(); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java deleted file mode 100644 index 5a9d4755f..000000000 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.keyboard; - -public final class MoreKeysDetector extends KeyDetector { - private final int mSlideAllowanceSquare; - private final int mSlideAllowanceSquareTop; - - public MoreKeysDetector(float slideAllowance) { - super(); - mSlideAllowanceSquare = (int)(slideAllowance * slideAllowance); - // Top slide allowance is slightly longer (sqrt(2) times) than other edges. - mSlideAllowanceSquareTop = mSlideAllowanceSquare * 2; - } - - @Override - public boolean alwaysAllowsKeySelectionByDraggingFinger() { - return true; - } - - @Override - public Key detectHitKey(final int x, final int y) { - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) { - return null; - } - final int touchX = getTouchX(x); - final int touchY = getTouchY(y); - - Key nearestKey = null; - int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; - for (final Key key : keyboard.getSortedKeys()) { - final int dist = key.squaredDistanceToEdge(touchX, touchY); - if (dist < nearestDist) { - nearestKey = key; - nearestDist = dist; - } - } - return nearestKey; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java deleted file mode 100644 index a021e5e2d..000000000 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.keyboard; - -import android.content.Context; -import android.graphics.Paint; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.keyboard.internal.KeyboardBuilder; -import com.android.inputmethod.keyboard.internal.KeyboardParams; -import com.android.inputmethod.keyboard.internal.MoreKeySpec; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.utils.TypefaceUtils; - -import javax.annotation.Nonnull; - -public final class MoreKeysKeyboard extends Keyboard { - private final int mDefaultKeyCoordX; - - MoreKeysKeyboard(final MoreKeysKeyboardParams params) { - super(params); - mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2; - } - - public int getDefaultCoordX() { - return mDefaultKeyCoordX; - } - - @UsedForTesting - static class MoreKeysKeyboardParams extends KeyboardParams { - public boolean mIsMoreKeysFixedOrder; - /* package */int mTopRowAdjustment; - public int mNumRows; - public int mNumColumns; - public int mTopKeys; - public int mLeftKeys; - public int mRightKeys; // includes default key. - public int mDividerWidth; - public int mColumnWidth; - - public MoreKeysKeyboardParams() { - super(); - } - - /** - * Set keyboard parameters of more keys keyboard. - * - * @param numKeys number of keys in this more keys keyboard. - * @param numColumn number of columns of this more keys keyboard. - * @param keyWidth more keys keyboard key width in pixel, including horizontal gap. - * @param rowHeight more keys keyboard row height in pixel, including vertical gap. - * @param coordXInParent coordinate x of the key preview in parent keyboard. - * @param parentKeyboardWidth parent keyboard width in pixel. - * @param isMoreKeysFixedColumn true if more keys keyboard should have - * <code>numColumn</code> columns. Otherwise more keys keyboard should have - * <code>numColumn</code> columns at most. - * @param isMoreKeysFixedOrder true if the order of more keys is determined by the order in - * the more keys' specification. Otherwise the order of more keys is automatically - * determined. - * @param dividerWidth width of divider, zero for no dividers. - */ - public void setParameters(final int numKeys, final int numColumn, final int keyWidth, - final int rowHeight, final int coordXInParent, final int parentKeyboardWidth, - final boolean isMoreKeysFixedColumn, final boolean isMoreKeysFixedOrder, - final int dividerWidth) { - mIsMoreKeysFixedOrder = isMoreKeysFixedOrder; - if (parentKeyboardWidth / keyWidth < Math.min(numKeys, numColumn)) { - throw new IllegalArgumentException("Keyboard is too small to hold more keys: " - + parentKeyboardWidth + " " + keyWidth + " " + numKeys + " " + numColumn); - } - mDefaultKeyWidth = keyWidth; - mDefaultRowHeight = rowHeight; - - final int numRows = (numKeys + numColumn - 1) / numColumn; - mNumRows = numRows; - final int numColumns = isMoreKeysFixedColumn ? Math.min(numKeys, numColumn) - : getOptimizedColumns(numKeys, numColumn); - mNumColumns = numColumns; - final int topKeys = numKeys % numColumns; - mTopKeys = topKeys == 0 ? numColumns : topKeys; - - final int numLeftKeys = (numColumns - 1) / 2; - final int numRightKeys = numColumns - numLeftKeys; // including default key. - // Maximum number of keys we can layout both side of the parent key - final int maxLeftKeys = coordXInParent / keyWidth; - final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth; - int leftKeys, rightKeys; - if (numLeftKeys > maxLeftKeys) { - leftKeys = maxLeftKeys; - rightKeys = numColumns - leftKeys; - } else if (numRightKeys > maxRightKeys + 1) { - rightKeys = maxRightKeys + 1; // include default key - leftKeys = numColumns - rightKeys; - } else { - leftKeys = numLeftKeys; - rightKeys = numRightKeys; - } - // If the left keys fill the left side of the parent key, entire more keys keyboard - // should be shifted to the right unless the parent key is on the left edge. - if (maxLeftKeys == leftKeys && leftKeys > 0) { - leftKeys--; - rightKeys++; - } - // If the right keys fill the right side of the parent key, entire more keys - // should be shifted to the left unless the parent key is on the right edge. - if (maxRightKeys == rightKeys - 1 && rightKeys > 1) { - leftKeys++; - rightKeys--; - } - mLeftKeys = leftKeys; - mRightKeys = rightKeys; - - // Adjustment of the top row. - mTopRowAdjustment = isMoreKeysFixedOrder ? getFixedOrderTopRowAdjustment() - : getAutoOrderTopRowAdjustment(); - mDividerWidth = dividerWidth; - mColumnWidth = mDefaultKeyWidth + mDividerWidth; - mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth; - // Need to subtract the bottom row's gutter only. - mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap - + mTopPadding + mBottomPadding; - } - - private int getFixedOrderTopRowAdjustment() { - if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns - || mLeftKeys == 0 || mRightKeys == 1) { - return 0; - } - return -1; - } - - private int getAutoOrderTopRowAdjustment() { - if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 - || mLeftKeys == 0 || mRightKeys == 1) { - return 0; - } - return -1; - } - - // Return key position according to column count (0 is default). - /* package */int getColumnPos(final int n) { - return mIsMoreKeysFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); - } - - private int getFixedOrderColumnPos(final int n) { - final int col = n % mNumColumns; - final int row = n / mNumColumns; - if (!isTopRow(row)) { - return col - mLeftKeys; - } - final int rightSideKeys = mTopKeys / 2; - final int leftSideKeys = mTopKeys - (rightSideKeys + 1); - final int pos = col - leftSideKeys; - final int numLeftKeys = mLeftKeys + mTopRowAdjustment; - final int numRightKeys = mRightKeys - 1; - if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) { - return pos; - } else if (numRightKeys < rightSideKeys) { - return pos - (rightSideKeys - numRightKeys); - } else { // numLeftKeys < leftSideKeys - return pos + (leftSideKeys - numLeftKeys); - } - } - - private int getAutomaticColumnPos(final int n) { - final int col = n % mNumColumns; - final int row = n / mNumColumns; - int leftKeys = mLeftKeys; - if (isTopRow(row)) { - leftKeys += mTopRowAdjustment; - } - if (col == 0) { - // default position. - return 0; - } - - int pos = 0; - int right = 1; // include default position key. - int left = 0; - int i = 0; - while (true) { - // Assign right key if available. - if (right < mRightKeys) { - pos = right; - right++; - i++; - } - if (i >= col) - break; - // Assign left key if available. - if (left < leftKeys) { - left++; - pos = -left; - i++; - } - if (i >= col) - break; - } - return pos; - } - - private static int getTopRowEmptySlots(final int numKeys, final int numColumns) { - final int remainings = numKeys % numColumns; - return remainings == 0 ? 0 : numColumns - remainings; - } - - private int getOptimizedColumns(final int numKeys, final int maxColumns) { - int numColumns = Math.min(numKeys, maxColumns); - while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { - numColumns--; - } - return numColumns; - } - - public int getDefaultKeyCoordX() { - return mLeftKeys * mColumnWidth + mLeftPadding; - } - - public int getX(final int n, final int row) { - final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX(); - if (isTopRow(row)) { - return x + mTopRowAdjustment * (mColumnWidth / 2); - } - return x; - } - - public int getY(final int row) { - return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; - } - - public void markAsEdgeKey(final Key key, final int row) { - if (row == 0) - key.markAsTopEdge(this); - if (isTopRow(row)) - key.markAsBottomEdge(this); - } - - private boolean isTopRow(final int rowCount) { - return mNumRows > 1 && rowCount == mNumRows - 1; - } - } - - public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> { - private final Key mParentKey; - - private static final float LABEL_PADDING_RATIO = 0.2f; - private static final float DIVIDER_RATIO = 0.2f; - - /** - * The builder of MoreKeysKeyboard. - * @param context the context of {@link MoreKeysKeyboardView}. - * @param key the {@link Key} that invokes more keys keyboard. - * @param keyboard the {@link Keyboard} that contains the parentKey. - * @param isSingleMoreKeyWithPreview true if the <code>key</code> has just a single - * "more key" and its key popup preview is enabled. - * @param keyPreviewVisibleWidth the width of visible part of key popup preview. - * @param keyPreviewVisibleHeight the height of visible part of key popup preview - * @param paintToMeasure the {@link Paint} object to measure a "more key" width - */ - public Builder(final Context context, final Key key, final Keyboard keyboard, - final boolean isSingleMoreKeyWithPreview, final int keyPreviewVisibleWidth, - final int keyPreviewVisibleHeight, final Paint paintToMeasure) { - super(context, new MoreKeysKeyboardParams()); - load(keyboard.mMoreKeysTemplate, keyboard.mId); - - // TODO: More keys keyboard's vertical gap is currently calculated heuristically. - // Should revise the algorithm. - mParams.mVerticalGap = keyboard.mVerticalGap / 2; - // This {@link MoreKeysKeyboard} is invoked from the <code>key</code>. - mParentKey = key; - - final int keyWidth, rowHeight; - if (isSingleMoreKeyWithPreview) { - // Use pre-computed width and height if this more keys keyboard has only one key to - // mitigate visual flicker between key preview and more keys keyboard. - // Caveats for the visual assets: To achieve this effect, both the key preview - // backgrounds and the more keys keyboard panel background have the exact same - // left/right/top paddings. The bottom paddings of both backgrounds don't need to - // be considered because the vertical positions of both backgrounds were already - // adjusted with their bottom paddings deducted. - keyWidth = keyPreviewVisibleWidth; - rowHeight = keyPreviewVisibleHeight + mParams.mVerticalGap; - } else { - final float padding = context.getResources().getDimension( - R.dimen.config_more_keys_keyboard_key_horizontal_padding) - + (key.hasLabelsInMoreKeys() - ? mParams.mDefaultKeyWidth * LABEL_PADDING_RATIO : 0.0f); - keyWidth = getMaxKeyWidth(key, mParams.mDefaultKeyWidth, padding, paintToMeasure); - rowHeight = keyboard.mMostCommonKeyHeight; - } - final int dividerWidth; - if (key.needsDividersInMoreKeys()) { - dividerWidth = (int)(keyWidth * DIVIDER_RATIO); - } else { - dividerWidth = 0; - } - final MoreKeySpec[] moreKeys = key.getMoreKeys(); - mParams.setParameters(moreKeys.length, key.getMoreKeysColumnNumber(), keyWidth, - rowHeight, key.getX() + key.getWidth() / 2, keyboard.mId.mWidth, - key.isMoreKeysFixedColumn(), key.isMoreKeysFixedOrder(), dividerWidth); - } - - private static int getMaxKeyWidth(final Key parentKey, final int minKeyWidth, - final float padding, final Paint paint) { - int maxWidth = minKeyWidth; - for (final MoreKeySpec spec : parentKey.getMoreKeys()) { - final String label = spec.mLabel; - // If the label is single letter, minKeyWidth is enough to hold the label. - if (label != null && StringUtils.codePointCount(label) > 1) { - maxWidth = Math.max(maxWidth, - (int)(TypefaceUtils.getStringWidth(label, paint) + padding)); - } - } - return maxWidth; - } - - @Override - @Nonnull - public MoreKeysKeyboard build() { - final MoreKeysKeyboardParams params = mParams; - final int moreKeyFlags = mParentKey.getMoreKeyLabelFlags(); - final MoreKeySpec[] moreKeys = mParentKey.getMoreKeys(); - for (int n = 0; n < moreKeys.length; n++) { - final MoreKeySpec moreKeySpec = moreKeys[n]; - final int row = n / params.mNumColumns; - final int x = params.getX(n, row); - final int y = params.getY(row); - final Key key = moreKeySpec.buildKey(x, y, moreKeyFlags, params); - params.markAsEdgeKey(key, row); - params.onAddKey(key); - - final int pos = params.getColumnPos(n); - // The "pos" value represents the offset from the default position. Negative means - // left of the default position. - if (params.mDividerWidth > 0 && pos != 0) { - final int dividerX = (pos > 0) ? x - params.mDividerWidth - : x + params.mDefaultKeyWidth; - final Key divider = new MoreKeyDivider( - params, dividerX, y, params.mDividerWidth, params.mDefaultRowHeight); - params.onAddKey(divider); - } - } - return new MoreKeysKeyboard(params); - } - } - - // Used as a divider maker. A divider is drawn by {@link MoreKeysKeyboardView}. - public static class MoreKeyDivider extends Key.Spacer { - public MoreKeyDivider(final KeyboardParams params, final int x, final int y, - final int width, final int height) { - super(params, x, y, width, height); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java deleted file mode 100644 index 3acc11b59..000000000 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.keyboard; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; - -import com.android.inputmethod.accessibility.AccessibilityUtils; -import com.android.inputmethod.accessibility.MoreKeysKeyboardAccessibilityDelegate; -import com.android.inputmethod.keyboard.internal.KeyDrawParams; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.CoordinateUtils; - -/** - * A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and - * detecting key presses and touch movements. - */ -public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel { - private final int[] mCoordinates = CoordinateUtils.newInstance(); - - private final Drawable mDivider; - protected final KeyDetector mKeyDetector; - private Controller mController = EMPTY_CONTROLLER; - protected KeyboardActionListener mListener; - private int mOriginX; - private int mOriginY; - private Key mCurrentKey; - - private int mActivePointerId; - - protected MoreKeysKeyboardAccessibilityDelegate mAccessibilityDelegate; - - public MoreKeysKeyboardView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.moreKeysKeyboardViewStyle); - } - - public MoreKeysKeyboardView(final Context context, final AttributeSet attrs, - final int defStyle) { - super(context, attrs, defStyle); - final TypedArray moreKeysKeyboardViewAttr = context.obtainStyledAttributes(attrs, - R.styleable.MoreKeysKeyboardView, defStyle, R.style.MoreKeysKeyboardView); - mDivider = moreKeysKeyboardViewAttr.getDrawable(R.styleable.MoreKeysKeyboardView_divider); - if (mDivider != null) { - // TODO: Drawable itself should have an alpha value. - mDivider.setAlpha(128); - } - moreKeysKeyboardViewAttr.recycle(); - mKeyDetector = new MoreKeysDetector(getResources().getDimension( - R.dimen.config_more_keys_keyboard_slide_allowance)); - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - final Keyboard keyboard = getKeyboard(); - if (keyboard != null) { - final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); - final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom(); - setMeasuredDimension(width, height); - } else { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - @Override - protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, - final KeyDrawParams params) { - if (!key.isSpacer() || !(key instanceof MoreKeysKeyboard.MoreKeyDivider) - || mDivider == null) { - super.onDrawKeyTopVisuals(key, canvas, paint, params); - return; - } - final int keyWidth = key.getDrawWidth(); - final int keyHeight = key.getHeight(); - final int iconWidth = Math.min(mDivider.getIntrinsicWidth(), keyWidth); - final int iconHeight = mDivider.getIntrinsicHeight(); - final int iconX = (keyWidth - iconWidth) / 2; // Align horizontally center - final int iconY = (keyHeight - iconHeight) / 2; // Align vertically center - drawIcon(canvas, mDivider, iconX, iconY, iconWidth, iconHeight); - } - - @Override - public void setKeyboard(final Keyboard keyboard) { - super.setKeyboard(keyboard); - mKeyDetector.setKeyboard( - keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection()); - if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { - if (mAccessibilityDelegate == null) { - mAccessibilityDelegate = new MoreKeysKeyboardAccessibilityDelegate( - this, mKeyDetector); - mAccessibilityDelegate.setOpenAnnounce(R.string.spoken_open_more_keys_keyboard); - mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_keys_keyboard); - } - mAccessibilityDelegate.setKeyboard(keyboard); - } else { - mAccessibilityDelegate = null; - } - } - - @Override - public void showMoreKeysPanel(final View parentView, final Controller controller, - final int pointX, final int pointY, final KeyboardActionListener listener) { - mController = controller; - mListener = listener; - final View container = getContainerView(); - // The coordinates of panel's left-top corner in parentView's coordinate system. - // We need to consider background drawable paddings. - final int x = pointX - getDefaultCoordX() - container.getPaddingLeft() - getPaddingLeft(); - final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom() - + getPaddingBottom(); - - parentView.getLocationInWindow(mCoordinates); - // Ensure the horizontal position of the panel does not extend past the parentView edges. - final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth(); - final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates); - final int panelY = y + CoordinateUtils.y(mCoordinates); - container.setX(panelX); - container.setY(panelY); - - mOriginX = x + container.getPaddingLeft(); - mOriginY = y + container.getPaddingTop(); - controller.onShowMoreKeysPanel(this); - final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; - if (accessibilityDelegate != null - && AccessibilityUtils.getInstance().isAccessibilityEnabled()) { - accessibilityDelegate.onShowMoreKeysKeyboard(); - } - } - - /** - * Returns the default x coordinate for showing this panel. - */ - protected int getDefaultCoordX() { - return ((MoreKeysKeyboard)getKeyboard()).getDefaultCoordX(); - } - - @Override - public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime) { - mActivePointerId = pointerId; - mCurrentKey = detectKey(x, y); - } - - @Override - public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime) { - if (mActivePointerId != pointerId) { - return; - } - final boolean hasOldKey = (mCurrentKey != null); - mCurrentKey = detectKey(x, y); - if (hasOldKey && mCurrentKey == null) { - // A more keys keyboard is canceled when detecting no key. - mController.onCancelMoreKeysPanel(); - } - } - - @Override - public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) { - if (mActivePointerId != pointerId) { - return; - } - // Calling {@link #detectKey(int,int,int)} here is harmless because the last move event and - // the following up event share the same coordinates. - mCurrentKey = detectKey(x, y); - if (mCurrentKey != null) { - updateReleaseKeyGraphics(mCurrentKey); - onKeyInput(mCurrentKey, x, y); - mCurrentKey = null; - } - } - - /** - * Performs the specific action for this panel when the user presses a key on the panel. - */ - protected void onKeyInput(final Key key, final int x, final int y) { - final int code = key.getCode(); - if (code == Constants.CODE_OUTPUT_TEXT) { - mListener.onTextInput(mCurrentKey.getOutputText()); - } else if (code != Constants.CODE_UNSPECIFIED) { - if (getKeyboard().hasProximityCharsCorrection(code)) { - mListener.onCodeInput(code, x, y, false /* isKeyRepeat */); - } else { - mListener.onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, - false /* isKeyRepeat */); - } - } - } - - private Key detectKey(int x, int y) { - final Key oldKey = mCurrentKey; - final Key newKey = mKeyDetector.detectHitKey(x, y); - if (newKey == oldKey) { - return newKey; - } - // A new key is detected. - if (oldKey != null) { - updateReleaseKeyGraphics(oldKey); - invalidateKey(oldKey); - } - if (newKey != null) { - updatePressKeyGraphics(newKey); - invalidateKey(newKey); - } - return newKey; - } - - private void updateReleaseKeyGraphics(final Key key) { - key.onReleased(); - invalidateKey(key); - } - - private void updatePressKeyGraphics(final Key key) { - key.onPressed(); - invalidateKey(key); - } - - @Override - public void dismissMoreKeysPanel() { - if (!isShowingInParent()) { - return; - } - final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; - if (accessibilityDelegate != null - && AccessibilityUtils.getInstance().isAccessibilityEnabled()) { - accessibilityDelegate.onDismissMoreKeysKeyboard(); - } - mController.onDismissMoreKeysPanel(); - } - - @Override - public int translateX(final int x) { - return x - mOriginX; - } - - @Override - public int translateY(final int y) { - return y - mOriginY; - } - - @Override - public boolean onTouchEvent(final MotionEvent me) { - final int action = me.getActionMasked(); - final long eventTime = me.getEventTime(); - final int index = me.getActionIndex(); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - final int pointerId = me.getPointerId(index); - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - onDownEvent(x, y, pointerId, eventTime); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - onUpEvent(x, y, pointerId, eventTime); - break; - case MotionEvent.ACTION_MOVE: - onMoveEvent(x, y, pointerId, eventTime); - break; - } - return true; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onHoverEvent(final MotionEvent event) { - final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; - if (accessibilityDelegate != null - && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { - return accessibilityDelegate.onHoverEvent(event); - } - return super.onHoverEvent(event); - } - - private View getContainerView() { - return (View)getParent(); - } - - @Override - public void showInParent(final ViewGroup parentView) { - removeFromParent(); - parentView.addView(getContainerView()); - } - - @Override - public void removeFromParent() { - final View containerView = getContainerView(); - final ViewGroup currentParent = (ViewGroup)containerView.getParent(); - if (currentParent != null) { - currentParent.removeView(containerView); - } - } - - @Override - public boolean isShowingInParent() { - return (getContainerView().getParent() != null); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java deleted file mode 100644 index 7bddd09f6..000000000 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.keyboard; - -import android.view.View; -import android.view.ViewGroup; - -public interface MoreKeysPanel { - public interface Controller { - /** - * Add the {@link MoreKeysPanel} to the target view. - * @param panel the panel to be shown. - */ - public void onShowMoreKeysPanel(final MoreKeysPanel panel); - - /** - * Remove the current {@link MoreKeysPanel} from the target view. - */ - public void onDismissMoreKeysPanel(); - - /** - * Instructs the parent to cancel the panel (e.g., when entering a different input mode). - */ - public void onCancelMoreKeysPanel(); - } - - public static final Controller EMPTY_CONTROLLER = new Controller() { - @Override - public void onShowMoreKeysPanel(final MoreKeysPanel panel) {} - @Override - public void onDismissMoreKeysPanel() {} - @Override - public void onCancelMoreKeysPanel() {} - }; - - /** - * Initializes the layout and event handling of this {@link MoreKeysPanel} and calls the - * controller's onShowMoreKeysPanel to add the panel's container view. - * - * @param parentView the parent view of this {@link MoreKeysPanel} - * @param controller the controller that can dismiss this {@link MoreKeysPanel} - * @param pointX x coordinate of this {@link MoreKeysPanel} - * @param pointY y coordinate of this {@link MoreKeysPanel} - * @param listener the listener that will receive keyboard action from this - * {@link MoreKeysPanel}. - */ - // TODO: Currently the MoreKeysPanel is inside a container view that is added to the parent. - // Consider the simpler approach of placing the MoreKeysPanel itself into the parent view. - public void showMoreKeysPanel(View parentView, Controller controller, int pointX, - int pointY, KeyboardActionListener listener); - - /** - * Dismisses the more keys panel and calls the controller's onDismissMoreKeysPanel to remove - * the panel's container view. - */ - public void dismissMoreKeysPanel(); - - /** - * Process a move event on the more keys panel. - * - * @param x translated x coordinate of the touch point - * @param y translated y coordinate of the touch point - * @param pointerId pointer id touch point - * @param eventTime timestamp of touch point - */ - public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime); - - /** - * Process a down event on the more keys panel. - * - * @param x translated x coordinate of the touch point - * @param y translated y coordinate of the touch point - * @param pointerId pointer id touch point - * @param eventTime timestamp of touch point - */ - public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime); - - /** - * Process an up event on the more keys panel. - * - * @param x translated x coordinate of the touch point - * @param y translated y coordinate of the touch point - * @param pointerId pointer id touch point - * @param eventTime timestamp of touch point - */ - public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime); - - /** - * Translate X-coordinate of touch event to the local X-coordinate of this - * {@link MoreKeysPanel}. - * - * @param x the global X-coordinate - * @return the local X-coordinate to this {@link MoreKeysPanel} - */ - public int translateX(int x); - - /** - * Translate Y-coordinate of touch event to the local Y-coordinate of this - * {@link MoreKeysPanel}. - * - * @param y the global Y-coordinate - * @return the local Y-coordinate to this {@link MoreKeysPanel} - */ - public int translateY(int y); - - /** - * Show this {@link MoreKeysPanel} in the parent view. - * - * @param parentView the {@link ViewGroup} that hosts this {@link MoreKeysPanel}. - */ - public void showInParent(ViewGroup parentView); - - /** - * Remove this {@link MoreKeysPanel} from the parent view. - */ - public void removeFromParent(); - - /** - * Return whether the panel is currently being shown. - */ - public boolean isShowingInParent(); -} diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java deleted file mode 100644 index c0ac1c054..000000000 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ /dev/null @@ -1,1198 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard; - -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.os.SystemClock; -import android.util.Log; -import android.view.MotionEvent; - -import com.android.inputmethod.keyboard.internal.BatchInputArbiter; -import com.android.inputmethod.keyboard.internal.BatchInputArbiter.BatchInputArbiterListener; -import com.android.inputmethod.keyboard.internal.BogusMoveEventDetector; -import com.android.inputmethod.keyboard.internal.DrawingProxy; -import com.android.inputmethod.keyboard.internal.GestureEnabler; -import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingParams; -import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingPoints; -import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionParams; -import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; -import com.android.inputmethod.keyboard.internal.TimerProxy; -import com.android.inputmethod.keyboard.internal.TypingTimeRecorder; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.CoordinateUtils; -import com.android.inputmethod.latin.common.InputPointers; -import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.utils.ResourceUtils; - -import java.util.ArrayList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public final class PointerTracker implements PointerTrackerQueue.Element, - BatchInputArbiterListener { - private static final String TAG = PointerTracker.class.getSimpleName(); - private static final boolean DEBUG_EVENT = false; - private static final boolean DEBUG_MOVE_EVENT = false; - private static final boolean DEBUG_LISTENER = false; - private static boolean DEBUG_MODE = DebugFlags.DEBUG_ENABLED || DEBUG_EVENT; - - static final class PointerTrackerParams { - public final boolean mKeySelectionByDraggingFinger; - public final int mTouchNoiseThresholdTime; - public final int mTouchNoiseThresholdDistance; - public final int mSuppressKeyPreviewAfterBatchInputDuration; - public final int mKeyRepeatStartTimeout; - public final int mKeyRepeatInterval; - public final int mLongPressShiftLockTimeout; - - public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) { - mKeySelectionByDraggingFinger = mainKeyboardViewAttr.getBoolean( - R.styleable.MainKeyboardView_keySelectionByDraggingFinger, false); - mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0); - mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimensionPixelSize( - R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0); - mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0); - mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0); - mKeyRepeatInterval = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_keyRepeatInterval, 0); - mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0); - } - } - - private static GestureEnabler sGestureEnabler = new GestureEnabler(); - - // Parameters for pointer handling. - private static PointerTrackerParams sParams; - private static GestureStrokeRecognitionParams sGestureStrokeRecognitionParams; - private static GestureStrokeDrawingParams sGestureStrokeDrawingParams; - private static boolean sNeedsPhantomSuddenMoveEventHack; - // Move this threshold to resource. - // TODO: Device specific parameter would be better for device specific hack? - private static final float PHANTOM_SUDDEN_MOVE_THRESHOLD = 0.25f; // in keyWidth - - private static final ArrayList<PointerTracker> sTrackers = new ArrayList<>(); - private static final PointerTrackerQueue sPointerTrackerQueue = new PointerTrackerQueue(); - - public final int mPointerId; - - private static DrawingProxy sDrawingProxy; - private static TimerProxy sTimerProxy; - private static KeyboardActionListener sListener = KeyboardActionListener.EMPTY_LISTENER; - - // The {@link KeyDetector} is set whenever the down event is processed. Also this is updated - // when new {@link Keyboard} is set by {@link #setKeyDetector(KeyDetector)}. - private KeyDetector mKeyDetector = new KeyDetector(); - private Keyboard mKeyboard; - private int mPhantomSuddenMoveThreshold; - private final BogusMoveEventDetector mBogusMoveEventDetector = new BogusMoveEventDetector(); - - private boolean mIsDetectingGesture = false; // per PointerTracker. - private static boolean sInGesture = false; - private static TypingTimeRecorder sTypingTimeRecorder; - - // The position and time at which first down event occurred. - private long mDownTime; - @Nonnull - private int[] mDownCoordinates = CoordinateUtils.newInstance(); - private long mUpTime; - - // The current key where this pointer is. - private Key mCurrentKey = null; - // The position where the current key was recognized for the first time. - private int mKeyX; - private int mKeyY; - - // Last pointer position. - private int mLastX; - private int mLastY; - - // true if keyboard layout has been changed. - private boolean mKeyboardLayoutHasBeenChanged; - - // true if this pointer is no longer triggering any action because it has been canceled. - private boolean mIsTrackingForActionDisabled; - - // the more keys panel currently being shown. equals null if no panel is active. - private MoreKeysPanel mMoreKeysPanel; - - private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3; - // true if this pointer is in the dragging finger mode. - boolean mIsInDraggingFinger; - // true if this pointer is sliding from a modifier key and in the sliding key input mode, - // so that further modifier keys should be ignored. - boolean mIsInSlidingKeyInput; - // if not a NOT_A_CODE, the key of this code is repeating - private int mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; - - // true if dragging finger is allowed. - private boolean mIsAllowedDraggingFinger; - - private final BatchInputArbiter mBatchInputArbiter; - private final GestureStrokeDrawingPoints mGestureStrokeDrawingPoints; - - // TODO: Add PointerTrackerFactory singleton and move some class static methods into it. - public static void init(final TypedArray mainKeyboardViewAttr, final TimerProxy timerProxy, - final DrawingProxy drawingProxy) { - sParams = new PointerTrackerParams(mainKeyboardViewAttr); - sGestureStrokeRecognitionParams = new GestureStrokeRecognitionParams(mainKeyboardViewAttr); - sGestureStrokeDrawingParams = new GestureStrokeDrawingParams(mainKeyboardViewAttr); - sTypingTimeRecorder = new TypingTimeRecorder( - sGestureStrokeRecognitionParams.mStaticTimeThresholdAfterFastTyping, - sParams.mSuppressKeyPreviewAfterBatchInputDuration); - - final Resources res = mainKeyboardViewAttr.getResources(); - sNeedsPhantomSuddenMoveEventHack = Boolean.parseBoolean( - ResourceUtils.getDeviceOverrideValue(res, - R.array.phantom_sudden_move_event_device_list, Boolean.FALSE.toString())); - BogusMoveEventDetector.init(res); - - sTimerProxy = timerProxy; - sDrawingProxy = drawingProxy; - } - - // Note that this method is called from a non-UI thread. - public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { - sGestureEnabler.setMainDictionaryAvailability(mainDictionaryAvailable); - } - - public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) { - sGestureEnabler.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser); - } - - public static PointerTracker getPointerTracker(final int id) { - final ArrayList<PointerTracker> trackers = sTrackers; - - // Create pointer trackers until we can get 'id+1'-th tracker, if needed. - for (int i = trackers.size(); i <= id; i++) { - final PointerTracker tracker = new PointerTracker(i); - trackers.add(tracker); - } - - return trackers.get(id); - } - - public static boolean isAnyInDraggingFinger() { - return sPointerTrackerQueue.isAnyInDraggingFinger(); - } - - public static void cancelAllPointerTrackers() { - sPointerTrackerQueue.cancelAllPointerTrackers(); - } - - public static void setKeyboardActionListener(final KeyboardActionListener listener) { - sListener = listener; - } - - public static void setKeyDetector(final KeyDetector keyDetector) { - final Keyboard keyboard = keyDetector.getKeyboard(); - if (keyboard == null) { - return; - } - final int trackersSize = sTrackers.size(); - for (int i = 0; i < trackersSize; ++i) { - final PointerTracker tracker = sTrackers.get(i); - tracker.setKeyDetectorInner(keyDetector); - } - sGestureEnabler.setPasswordMode(keyboard.mId.passwordInput()); - } - - public static void setReleasedKeyGraphicsToAllKeys() { - final int trackersSize = sTrackers.size(); - for (int i = 0; i < trackersSize; ++i) { - final PointerTracker tracker = sTrackers.get(i); - tracker.setReleasedKeyGraphics(tracker.getKey(), true /* withAnimation */); - } - } - - public static void dismissAllMoreKeysPanels() { - final int trackersSize = sTrackers.size(); - for (int i = 0; i < trackersSize; ++i) { - final PointerTracker tracker = sTrackers.get(i); - tracker.dismissMoreKeysPanel(); - } - } - - private PointerTracker(final int id) { - mPointerId = id; - mBatchInputArbiter = new BatchInputArbiter(id, sGestureStrokeRecognitionParams); - mGestureStrokeDrawingPoints = new GestureStrokeDrawingPoints(sGestureStrokeDrawingParams); - } - - // Returns true if keyboard has been changed by this callback. - private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key, - final int repeatCount) { - // While gesture input is going on, this method should be a no-operation. But when gesture - // input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code> - // are set to false. To keep this method is a no-operation, - // <code>mIsTrackingForActionDisabled</code> should also be taken account of. - if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) { - return false; - } - final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier(); - if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onPress : %s%s%s%s", mPointerId, - (key == null ? "none" : Constants.printableCode(key.getCode())), - ignoreModifierKey ? " ignoreModifier" : "", - key.isEnabled() ? "" : " disabled", - repeatCount > 0 ? " repeatCount=" + repeatCount : "")); - } - if (ignoreModifierKey) { - return false; - } - if (key.isEnabled()) { - sListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1); - final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; - mKeyboardLayoutHasBeenChanged = false; - sTimerProxy.startTypingStateTimer(key); - return keyboardLayoutHasBeenChanged; - } - return false; - } - - // Note that we need primaryCode argument because the keyboard may in shifted state and the - // primaryCode is different from {@link Key#mKeyCode}. - private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x, - final int y, final long eventTime, final boolean isKeyRepeat) { - final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier(); - final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState(); - final int code = altersCode ? key.getAltCode() : primaryCode; - if (DEBUG_LISTENER) { - final String output = code == Constants.CODE_OUTPUT_TEXT - ? key.getOutputText() : Constants.printableCode(code); - Log.d(TAG, String.format("[%d] onCodeInput: %4d %4d %s%s%s%s", mPointerId, x, y, - output, ignoreModifierKey ? " ignoreModifier" : "", - altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled")); - } - if (ignoreModifierKey) { - return; - } - // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. - if (key.isEnabled() || altersCode) { - sTypingTimeRecorder.onCodeInput(code, eventTime); - if (code == Constants.CODE_OUTPUT_TEXT) { - sListener.onTextInput(key.getOutputText()); - } else if (code != Constants.CODE_UNSPECIFIED) { - if (mKeyboard.hasProximityCharsCorrection(code)) { - sListener.onCodeInput(code, x, y, isKeyRepeat); - } else { - sListener.onCodeInput(code, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, isKeyRepeat); - } - } - } - } - - // Note that we need primaryCode argument because the keyboard may be in shifted state and the - // primaryCode is different from {@link Key#mKeyCode}. - private void callListenerOnRelease(final Key key, final int primaryCode, - final boolean withSliding) { - // See the comment at {@link #callListenerOnPressAndCheckKeyboardLayoutChange(Key}}. - if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) { - return; - } - final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier(); - if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId, - Constants.printableCode(primaryCode), - withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "", - key.isEnabled() ? "": " disabled")); - } - if (ignoreModifierKey) { - return; - } - if (key.isEnabled()) { - sListener.onReleaseKey(primaryCode, withSliding); - } - } - - private void callListenerOnFinishSlidingInput() { - if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onFinishSlidingInput", mPointerId)); - } - sListener.onFinishSlidingInput(); - } - - private void callListenerOnCancelInput() { - if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onCancelInput", mPointerId)); - } - sListener.onCancelInput(); - } - - private void setKeyDetectorInner(final KeyDetector keyDetector) { - final Keyboard keyboard = keyDetector.getKeyboard(); - if (keyboard == null) { - return; - } - if (keyDetector == mKeyDetector && keyboard == mKeyboard) { - return; - } - mKeyDetector = keyDetector; - mKeyboard = keyboard; - // Mark that keyboard layout has been changed. - mKeyboardLayoutHasBeenChanged = true; - final int keyWidth = mKeyboard.mMostCommonKeyWidth; - final int keyHeight = mKeyboard.mMostCommonKeyHeight; - mBatchInputArbiter.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight); - // Keep {@link #mCurrentKey} that comes from previous keyboard. The key preview of - // {@link #mCurrentKey} will be dismissed by {@setReleasedKeyGraphics(Key)} via - // {@link onMoveEventInternal(int,int,long)} or {@link #onUpEventInternal(int,int,long)}. - mPhantomSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD); - mBogusMoveEventDetector.setKeyboardGeometry(keyWidth, keyHeight); - } - - @Override - public boolean isInDraggingFinger() { - return mIsInDraggingFinger; - } - - @Nullable - public Key getKey() { - return mCurrentKey; - } - - @Override - public boolean isModifier() { - return mCurrentKey != null && mCurrentKey.isModifier(); - } - - public Key getKeyOn(final int x, final int y) { - return mKeyDetector.detectHitKey(x, y); - } - - private void setReleasedKeyGraphics(@Nullable final Key key, final boolean withAnimation) { - if (key == null) { - return; - } - - sDrawingProxy.onKeyReleased(key, withAnimation); - - if (key.isShift()) { - for (final Key shiftKey : mKeyboard.mShiftKeys) { - if (shiftKey != key) { - sDrawingProxy.onKeyReleased(shiftKey, false /* withAnimation */); - } - } - } - - if (key.altCodeWhileTyping()) { - final int altCode = key.getAltCode(); - final Key altKey = mKeyboard.getKey(altCode); - if (altKey != null) { - sDrawingProxy.onKeyReleased(altKey, false /* withAnimation */); - } - for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { - if (k != key && k.getAltCode() == altCode) { - sDrawingProxy.onKeyReleased(k, false /* withAnimation */); - } - } - } - } - - private static boolean needsToSuppressKeyPreviewPopup(final long eventTime) { - if (!sGestureEnabler.shouldHandleGesture()) return false; - return sTypingTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime); - } - - private void setPressedKeyGraphics(@Nullable final Key key, final long eventTime) { - if (key == null) { - return; - } - - // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. - final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState(); - final boolean needsToUpdateGraphics = key.isEnabled() || altersCode; - if (!needsToUpdateGraphics) { - return; - } - - final boolean noKeyPreview = sInGesture || needsToSuppressKeyPreviewPopup(eventTime); - sDrawingProxy.onKeyPressed(key, !noKeyPreview); - - if (key.isShift()) { - for (final Key shiftKey : mKeyboard.mShiftKeys) { - if (shiftKey != key) { - sDrawingProxy.onKeyPressed(shiftKey, false /* withPreview */); - } - } - } - - if (altersCode) { - final int altCode = key.getAltCode(); - final Key altKey = mKeyboard.getKey(altCode); - if (altKey != null) { - sDrawingProxy.onKeyPressed(altKey, false /* withPreview */); - } - for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { - if (k != key && k.getAltCode() == altCode) { - sDrawingProxy.onKeyPressed(k, false /* withPreview */); - } - } - } - } - - public GestureStrokeDrawingPoints getGestureStrokeDrawingPoints() { - return mGestureStrokeDrawingPoints; - } - - public void getLastCoordinates(@Nonnull final int[] outCoords) { - CoordinateUtils.set(outCoords, mLastX, mLastY); - } - - public long getDownTime() { - return mDownTime; - } - - public void getDownCoordinates(@Nonnull final int[] outCoords) { - CoordinateUtils.copy(outCoords, mDownCoordinates); - } - - private Key onDownKey(final int x, final int y, final long eventTime) { - mDownTime = eventTime; - CoordinateUtils.set(mDownCoordinates, x, y); - mBogusMoveEventDetector.onDownKey(); - return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); - } - - private static int getDistance(final int x1, final int y1, final int x2, final int y2) { - return (int)Math.hypot(x1 - x2, y1 - y2); - } - - private Key onMoveKeyInternal(final int x, final int y) { - mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY)); - mLastX = x; - mLastY = y; - return mKeyDetector.detectHitKey(x, y); - } - - private Key onMoveKey(final int x, final int y) { - return onMoveKeyInternal(x, y); - } - - private Key onMoveToNewKey(final Key newKey, final int x, final int y) { - mCurrentKey = newKey; - mKeyX = x; - mKeyY = y; - return newKey; - } - - /* package */ static int getActivePointerTrackerCount() { - return sPointerTrackerQueue.size(); - } - - private boolean isOldestTrackerInQueue() { - return sPointerTrackerQueue.getOldestElement() == this; - } - - // Implements {@link BatchInputArbiterListener}. - @Override - public void onStartBatchInput() { - if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId)); - } - sListener.onStartBatchInput(); - dismissAllMoreKeysPanels(); - sTimerProxy.cancelLongPressTimersOf(this); - } - - private void showGestureTrail() { - if (mIsTrackingForActionDisabled) { - return; - } - // A gesture floating preview text will be shown at the oldest pointer/finger on the screen. - sDrawingProxy.showGestureTrail( - this, isOldestTrackerInQueue() /* showsFloatingPreviewText */); - } - - public void updateBatchInputByTimer(final long syntheticMoveEventTime) { - mBatchInputArbiter.updateBatchInputByTimer(syntheticMoveEventTime, this); - } - - // Implements {@link BatchInputArbiterListener}. - @Override - public void onUpdateBatchInput(final InputPointers aggregatedPointers, final long eventTime) { - if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId, - aggregatedPointers.getPointerSize())); - } - sListener.onUpdateBatchInput(aggregatedPointers); - } - - // Implements {@link BatchInputArbiterListener}. - @Override - public void onStartUpdateBatchInputTimer() { - sTimerProxy.startUpdateBatchInputTimer(this); - } - - // Implements {@link BatchInputArbiterListener}. - @Override - public void onEndBatchInput(final InputPointers aggregatedPointers, final long eventTime) { - sTypingTimeRecorder.onEndBatchInput(eventTime); - sTimerProxy.cancelAllUpdateBatchInputTimers(); - if (mIsTrackingForActionDisabled) { - return; - } - if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d", - mPointerId, aggregatedPointers.getPointerSize())); - } - sListener.onEndBatchInput(aggregatedPointers); - } - - private void cancelBatchInput() { - cancelAllPointerTrackers(); - mIsDetectingGesture = false; - if (!sInGesture) { - return; - } - sInGesture = false; - if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onCancelBatchInput", mPointerId)); - } - sListener.onCancelBatchInput(); - } - - public void processMotionEvent(final MotionEvent me, final KeyDetector keyDetector) { - final int action = me.getActionMasked(); - final long eventTime = me.getEventTime(); - if (action == MotionEvent.ACTION_MOVE) { - // When this pointer is the only active pointer and is showing a more keys panel, - // we should ignore other pointers' motion event. - final boolean shouldIgnoreOtherPointers = - isShowingMoreKeysPanel() && getActivePointerTrackerCount() == 1; - final int pointerCount = me.getPointerCount(); - for (int index = 0; index < pointerCount; index++) { - final int id = me.getPointerId(index); - if (shouldIgnoreOtherPointers && id != mPointerId) { - continue; - } - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - final PointerTracker tracker = getPointerTracker(id); - tracker.onMoveEvent(x, y, eventTime, me); - } - return; - } - final int index = me.getActionIndex(); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - onDownEvent(x, y, eventTime, keyDetector); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - onUpEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_CANCEL: - onCancelEvent(x, y, eventTime); - break; - } - } - - private void onDownEvent(final int x, final int y, final long eventTime, - final KeyDetector keyDetector) { - setKeyDetectorInner(keyDetector); - if (DEBUG_EVENT) { - printTouchEvent("onDownEvent:", x, y, eventTime); - } - // Naive up-to-down noise filter. - final long deltaT = eventTime - mUpTime; - if (deltaT < sParams.mTouchNoiseThresholdTime) { - final int distance = getDistance(x, y, mLastX, mLastY); - if (distance < sParams.mTouchNoiseThresholdDistance) { - if (DEBUG_MODE) - Log.w(TAG, String.format("[%d] onDownEvent:" - + " ignore potential noise: time=%d distance=%d", - mPointerId, deltaT, distance)); - cancelTrackingForAction(); - return; - } - } - - final Key key = getKeyOn(x, y); - mBogusMoveEventDetector.onActualDownEvent(x, y); - if (key != null && key.isModifier()) { - // Before processing a down event of modifier key, all pointers already being - // tracked should be released. - sPointerTrackerQueue.releaseAllPointers(eventTime); - } - sPointerTrackerQueue.add(this); - onDownEventInternal(x, y, eventTime); - if (!sGestureEnabler.shouldHandleGesture()) { - return; - } - // A gesture should start only from a non-modifier key. Note that the gesture detection is - // disabled when the key is repeating. - mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard() - && key != null && !key.isModifier(); - if (mIsDetectingGesture) { - mBatchInputArbiter.addDownEventPoint(x, y, eventTime, - sTypingTimeRecorder.getLastLetterTypingTime(), getActivePointerTrackerCount()); - mGestureStrokeDrawingPoints.onDownEvent( - x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime)); - } - } - - /* package */ boolean isShowingMoreKeysPanel() { - return (mMoreKeysPanel != null); - } - - private void dismissMoreKeysPanel() { - if (isShowingMoreKeysPanel()) { - mMoreKeysPanel.dismissMoreKeysPanel(); - mMoreKeysPanel = null; - } - } - - private void onDownEventInternal(final int x, final int y, final long eventTime) { - Key key = onDownKey(x, y, eventTime); - // Key selection by dragging finger is allowed when 1) key selection by dragging finger is - // enabled by configuration, 2) this pointer starts dragging from modifier key, or 3) this - // pointer's KeyDetector always allows key selection by dragging finger, such as - // {@link MoreKeysKeyboard}. - mIsAllowedDraggingFinger = sParams.mKeySelectionByDraggingFinger - || (key != null && key.isModifier()) - || mKeyDetector.alwaysAllowsKeySelectionByDraggingFinger(); - mKeyboardLayoutHasBeenChanged = false; - mIsTrackingForActionDisabled = false; - resetKeySelectionByDraggingFinger(); - if (key != null) { - // This onPress call may have changed keyboard layout. Those cases are detected at - // {@link #setKeyboard}. In those cases, we should update key according to the new - // keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) { - key = onDownKey(x, y, eventTime); - } - - startRepeatKey(key); - startLongPressTimer(key); - setPressedKeyGraphics(key, eventTime); - } - } - - private void startKeySelectionByDraggingFinger(final Key key) { - if (!mIsInDraggingFinger) { - mIsInSlidingKeyInput = key.isModifier(); - } - mIsInDraggingFinger = true; - } - - private void resetKeySelectionByDraggingFinger() { - mIsInDraggingFinger = false; - mIsInSlidingKeyInput = false; - sDrawingProxy.showSlidingKeyInputPreview(null /* tracker */); - } - - private void onGestureMoveEvent(final int x, final int y, final long eventTime, - final boolean isMajorEvent, final Key key) { - if (!mIsDetectingGesture) { - return; - } - final boolean onValidArea = mBatchInputArbiter.addMoveEventPoint( - x, y, eventTime, isMajorEvent, this); - // If the move event goes out from valid batch input area, cancel batch input. - if (!onValidArea) { - cancelBatchInput(); - return; - } - mGestureStrokeDrawingPoints.onMoveEvent( - x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime)); - // If the MoreKeysPanel is showing then do not attempt to enter gesture mode. However, - // the gestured touch points are still being recorded in case the panel is dismissed. - if (isShowingMoreKeysPanel()) { - return; - } - if (!sInGesture && key != null && Character.isLetter(key.getCode()) - && mBatchInputArbiter.mayStartBatchInput(this)) { - sInGesture = true; - } - if (sInGesture) { - if (key != null) { - mBatchInputArbiter.updateBatchInput(eventTime, this); - } - showGestureTrail(); - } - } - - private void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) { - if (DEBUG_MOVE_EVENT) { - printTouchEvent("onMoveEvent:", x, y, eventTime); - } - if (mIsTrackingForActionDisabled) { - return; - } - - if (sGestureEnabler.shouldHandleGesture() && me != null) { - // Add historical points to gesture path. - final int pointerIndex = me.findPointerIndex(mPointerId); - final int historicalSize = me.getHistorySize(); - for (int h = 0; h < historicalSize; h++) { - final int historicalX = (int)me.getHistoricalX(pointerIndex, h); - final int historicalY = (int)me.getHistoricalY(pointerIndex, h); - final long historicalTime = me.getHistoricalEventTime(h); - onGestureMoveEvent(historicalX, historicalY, historicalTime, - false /* isMajorEvent */, null); - } - } - - if (isShowingMoreKeysPanel()) { - final int translatedX = mMoreKeysPanel.translateX(x); - final int translatedY = mMoreKeysPanel.translateY(y); - mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime); - onMoveKey(x, y); - if (mIsInSlidingKeyInput) { - sDrawingProxy.showSlidingKeyInputPreview(this); - } - return; - } - onMoveEventInternal(x, y, eventTime); - } - - private void processDraggingFingerInToNewKey(final Key newKey, final int x, final int y, - final long eventTime) { - // This onPress call may have changed keyboard layout. Those cases are detected - // at {@link #setKeyboard}. In those cases, we should update key according - // to the new keyboard layout. - Key key = newKey; - if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) { - key = onMoveKey(x, y); - } - onMoveToNewKey(key, x, y); - if (mIsTrackingForActionDisabled) { - return; - } - startLongPressTimer(key); - setPressedKeyGraphics(key, eventTime); - } - - private void processPhantomSuddenMoveHack(final Key key, final int x, final int y, - final long eventTime, final Key oldKey, final int lastX, final int lastY) { - if (DEBUG_MODE) { - Log.w(TAG, String.format("[%d] onMoveEvent:" - + " phantom sudden move event (distance=%d) is translated to " - + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId, - getDistance(x, y, lastX, lastY), - lastX, lastY, Constants.printableCode(oldKey.getCode()), - x, y, Constants.printableCode(key.getCode()))); - } - onUpEventInternal(x, y, eventTime); - onDownEventInternal(x, y, eventTime); - } - - private void processProximateBogusDownMoveUpEventHack(final Key key, final int x, final int y, - final long eventTime, final Key oldKey, final int lastX, final int lastY) { - if (DEBUG_MODE) { - final float keyDiagonal = (float)Math.hypot( - mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); - final float radiusRatio = - mBogusMoveEventDetector.getDistanceFromDownEvent(x, y) - / keyDiagonal; - Log.w(TAG, String.format("[%d] onMoveEvent:" - + " bogus down-move-up event (raidus=%.2f key diagonal) is " - + " translated to up[%d,%d,%s]/down[%d,%d,%s] events", - mPointerId, radiusRatio, - lastX, lastY, Constants.printableCode(oldKey.getCode()), - x, y, Constants.printableCode(key.getCode()))); - } - onUpEventInternal(x, y, eventTime); - onDownEventInternal(x, y, eventTime); - } - - private void processDraggingFingerOutFromOldKey(final Key oldKey) { - setReleasedKeyGraphics(oldKey, true /* withAnimation */); - callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */); - startKeySelectionByDraggingFinger(oldKey); - sTimerProxy.cancelKeyTimersOf(this); - } - - private void dragFingerFromOldKeyToNewKey(final Key key, final int x, final int y, - final long eventTime, final Key oldKey, final int lastX, final int lastY) { - // The pointer has been slid in to the new key from the previous key, we must call - // onRelease() first to notify that the previous key has been released, then call - // onPress() to notify that the new key is being pressed. - processDraggingFingerOutFromOldKey(oldKey); - startRepeatKey(key); - if (mIsAllowedDraggingFinger) { - processDraggingFingerInToNewKey(key, x, y, eventTime); - } - // HACK: On some devices, quick successive touches may be reported as a sudden move by - // touch panel firmware. This hack detects such cases and translates the move event to - // successive up and down events. - // TODO: Should find a way to balance gesture detection and this hack. - else if (sNeedsPhantomSuddenMoveEventHack - && getDistance(x, y, lastX, lastY) >= mPhantomSuddenMoveThreshold) { - processPhantomSuddenMoveHack(key, x, y, eventTime, oldKey, lastX, lastY); - } - // HACK: On some devices, quick successive proximate touches may be reported as a bogus - // down-move-up event by touch panel firmware. This hack detects such cases and breaks - // these events into separate up and down events. - else if (sTypingTimeRecorder.isInFastTyping(eventTime) - && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) { - processProximateBogusDownMoveUpEventHack(key, x, y, eventTime, oldKey, lastX, lastY); - } - // HACK: If there are currently multiple touches, register the key even if the finger - // slides off the key. This defends against noise from some touch panels when there are - // close multiple touches. - // Caveat: When in chording input mode with a modifier key, we don't use this hack. - else if (getActivePointerTrackerCount() > 1 - && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { - if (DEBUG_MODE) { - Log.w(TAG, String.format("[%d] onMoveEvent:" - + " detected sliding finger while multi touching", mPointerId)); - } - onUpEvent(x, y, eventTime); - cancelTrackingForAction(); - setReleasedKeyGraphics(oldKey, true /* withAnimation */); - } else { - if (!mIsDetectingGesture) { - cancelTrackingForAction(); - } - setReleasedKeyGraphics(oldKey, true /* withAnimation */); - } - } - - private void dragFingerOutFromOldKey(final Key oldKey, final int x, final int y) { - // The pointer has been slid out from the previous key, we must call onRelease() to - // notify that the previous key has been released. - processDraggingFingerOutFromOldKey(oldKey); - if (mIsAllowedDraggingFinger) { - onMoveToNewKey(null, x, y); - } else { - if (!mIsDetectingGesture) { - cancelTrackingForAction(); - } - } - } - - private void onMoveEventInternal(final int x, final int y, final long eventTime) { - final int lastX = mLastX; - final int lastY = mLastY; - final Key oldKey = mCurrentKey; - final Key newKey = onMoveKey(x, y); - - if (sGestureEnabler.shouldHandleGesture()) { - // Register move event on gesture tracker. - onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey); - if (sInGesture) { - mCurrentKey = null; - setReleasedKeyGraphics(oldKey, true /* withAnimation */); - return; - } - } - - if (newKey != null) { - if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { - dragFingerFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY); - } else if (oldKey == null) { - // The pointer has been slid in to the new key, but the finger was not on any keys. - // In this case, we must call onPress() to notify that the new key is being pressed. - processDraggingFingerInToNewKey(newKey, x, y, eventTime); - } - } else { // newKey == null - if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { - dragFingerOutFromOldKey(oldKey, x, y); - } - } - if (mIsInSlidingKeyInput) { - sDrawingProxy.showSlidingKeyInputPreview(this); - } - } - - private void onUpEvent(final int x, final int y, final long eventTime) { - if (DEBUG_EVENT) { - printTouchEvent("onUpEvent :", x, y, eventTime); - } - - sTimerProxy.cancelUpdateBatchInputTimer(this); - if (!sInGesture) { - if (mCurrentKey != null && mCurrentKey.isModifier()) { - // Before processing an up event of modifier key, all pointers already being - // tracked should be released. - sPointerTrackerQueue.releaseAllPointersExcept(this, eventTime); - } else { - sPointerTrackerQueue.releaseAllPointersOlderThan(this, eventTime); - } - } - onUpEventInternal(x, y, eventTime); - sPointerTrackerQueue.remove(this); - } - - // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. - // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a - // "virtual" up event. - @Override - public void onPhantomUpEvent(final long eventTime) { - if (DEBUG_EVENT) { - printTouchEvent("onPhntEvent:", mLastX, mLastY, eventTime); - } - onUpEventInternal(mLastX, mLastY, eventTime); - cancelTrackingForAction(); - } - - private void onUpEventInternal(final int x, final int y, final long eventTime) { - sTimerProxy.cancelKeyTimersOf(this); - final boolean isInDraggingFinger = mIsInDraggingFinger; - final boolean isInSlidingKeyInput = mIsInSlidingKeyInput; - resetKeySelectionByDraggingFinger(); - mIsDetectingGesture = false; - final Key currentKey = mCurrentKey; - mCurrentKey = null; - final int currentRepeatingKeyCode = mCurrentRepeatingKeyCode; - mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; - // Release the last pressed key. - setReleasedKeyGraphics(currentKey, true /* withAnimation */); - - if (isShowingMoreKeysPanel()) { - if (!mIsTrackingForActionDisabled) { - final int translatedX = mMoreKeysPanel.translateX(x); - final int translatedY = mMoreKeysPanel.translateY(y); - mMoreKeysPanel.onUpEvent(translatedX, translatedY, mPointerId, eventTime); - } - dismissMoreKeysPanel(); - return; - } - - if (sInGesture) { - if (currentKey != null) { - callListenerOnRelease(currentKey, currentKey.getCode(), true /* withSliding */); - } - if (mBatchInputArbiter.mayEndBatchInput( - eventTime, getActivePointerTrackerCount(), this)) { - sInGesture = false; - } - showGestureTrail(); - return; - } - - if (mIsTrackingForActionDisabled) { - return; - } - if (currentKey != null && currentKey.isRepeatable() - && (currentKey.getCode() == currentRepeatingKeyCode) && !isInDraggingFinger) { - return; - } - detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime); - if (isInSlidingKeyInput) { - callListenerOnFinishSlidingInput(); - } - } - - @Override - public void cancelTrackingForAction() { - if (isShowingMoreKeysPanel()) { - return; - } - mIsTrackingForActionDisabled = true; - } - - public boolean isInOperation() { - return !mIsTrackingForActionDisabled; - } - - public void onLongPressed() { - sTimerProxy.cancelLongPressTimersOf(this); - if (isShowingMoreKeysPanel()) { - return; - } - final Key key = getKey(); - if (key == null) { - return; - } - if (key.hasNoPanelAutoMoreKey()) { - cancelKeyTracking(); - final int moreKeyCode = key.getMoreKeys()[0].mCode; - sListener.onPressKey(moreKeyCode, 0 /* repeatCont */, true /* isSinglePointer */); - sListener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE, - Constants.NOT_A_COORDINATE, false /* isKeyRepeat */); - sListener.onReleaseKey(moreKeyCode, false /* withSliding */); - return; - } - final int code = key.getCode(); - if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) { - // Long pressing the space key invokes IME switcher dialog. - if (sListener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) { - cancelKeyTracking(); - sListener.onReleaseKey(code, false /* withSliding */); - return; - } - } - - setReleasedKeyGraphics(key, false /* withAnimation */); - final MoreKeysPanel moreKeysPanel = sDrawingProxy.showMoreKeysKeyboard(key, this); - if (moreKeysPanel == null) { - return; - } - final int translatedX = moreKeysPanel.translateX(mLastX); - final int translatedY = moreKeysPanel.translateY(mLastY); - moreKeysPanel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis()); - mMoreKeysPanel = moreKeysPanel; - } - - private void cancelKeyTracking() { - resetKeySelectionByDraggingFinger(); - cancelTrackingForAction(); - setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */); - sPointerTrackerQueue.remove(this); - } - - private void onCancelEvent(final int x, final int y, final long eventTime) { - if (DEBUG_EVENT) { - printTouchEvent("onCancelEvt:", x, y, eventTime); - } - - cancelBatchInput(); - cancelAllPointerTrackers(); - sPointerTrackerQueue.releaseAllPointers(eventTime); - onCancelEventInternal(); - } - - private void onCancelEventInternal() { - sTimerProxy.cancelKeyTimersOf(this); - setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */); - resetKeySelectionByDraggingFinger(); - dismissMoreKeysPanel(); - } - - private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime, - final Key newKey) { - final Key curKey = mCurrentKey; - if (newKey == curKey) { - return false; - } - if (curKey == null /* && newKey != null */) { - return true; - } - // Here curKey points to the different key from newKey. - final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared( - mIsInSlidingKeyInput); - final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y); - if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) { - if (DEBUG_MODE) { - final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared) - / mKeyboard.mMostCommonKeyWidth; - Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" - +" %.2f key width from key edge", mPointerId, distanceToEdgeRatio)); - } - return true; - } - if (!mIsAllowedDraggingFinger && sTypingTimeRecorder.isInFastTyping(eventTime) - && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) { - if (DEBUG_MODE) { - final float keyDiagonal = (float)Math.hypot( - mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); - final float lengthFromDownRatio = - mBogusMoveEventDetector.getAccumulatedDistanceFromDownKey() / keyDiagonal; - Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" - + " %.2f key diagonal from virtual down point", - mPointerId, lengthFromDownRatio)); - } - return true; - } - return false; - } - - private void startLongPressTimer(final Key key) { - // Note that we need to cancel all active long press shift key timers if any whenever we - // start a new long press timer for both non-shift and shift keys. - sTimerProxy.cancelLongPressShiftKeyTimer(); - if (sInGesture) return; - if (key == null) return; - if (!key.isLongPressEnabled()) return; - // Caveat: Please note that isLongPressEnabled() can be true even if the current key - // doesn't have its more keys. (e.g. spacebar, globe key) If we are in the dragging finger - // mode, we will disable long press timer of such key. - // We always need to start the long press timer if the key has its more keys regardless of - // whether or not we are in the dragging finger mode. - if (mIsInDraggingFinger && key.getMoreKeys() == null) return; - - final int delay = getLongPressTimeout(key.getCode()); - if (delay <= 0) return; - sTimerProxy.startLongPressTimerOf(this, delay); - } - - private int getLongPressTimeout(final int code) { - if (code == Constants.CODE_SHIFT) { - return sParams.mLongPressShiftLockTimeout; - } - final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout; - if (mIsInSlidingKeyInput) { - // We use longer timeout for sliding finger input started from the modifier key. - return longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT; - } - return longpressTimeout; - } - - private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) { - if (key == null) { - callListenerOnCancelInput(); - return; - } - - final int code = key.getCode(); - callListenerOnCodeInput(key, code, x, y, eventTime, false /* isKeyRepeat */); - callListenerOnRelease(key, code, false /* withSliding */); - } - - private void startRepeatKey(final Key key) { - if (sInGesture) return; - if (key == null) return; - if (!key.isRepeatable()) return; - // Don't start key repeat when we are in the dragging finger mode. - if (mIsInDraggingFinger) return; - final int startRepeatCount = 1; - startKeyRepeatTimer(startRepeatCount); - } - - public void onKeyRepeat(final int code, final int repeatCount) { - final Key key = getKey(); - if (key == null || key.getCode() != code) { - mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; - return; - } - mCurrentRepeatingKeyCode = code; - mIsDetectingGesture = false; - final int nextRepeatCount = repeatCount + 1; - startKeyRepeatTimer(nextRepeatCount); - callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount); - callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis(), - true /* isKeyRepeat */); - } - - private void startKeyRepeatTimer(final int repeatCount) { - final int delay = - (repeatCount == 1) ? sParams.mKeyRepeatStartTimeout : sParams.mKeyRepeatInterval; - sTimerProxy.startKeyRepeatTimerOf(this, repeatCount, delay); - } - - private void printTouchEvent(final String title, final int x, final int y, - final long eventTime) { - final Key key = mKeyDetector.detectHitKey(x, y); - final String code = (key == null ? "none" : Constants.printableCode(key.getCode())); - Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId, - (mIsTrackingForActionDisabled ? "-" : " "), title, x, y, eventTime, code)); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java deleted file mode 100644 index b9a5eaefb..000000000 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.keyboard; - -import android.graphics.Rect; -import android.util.Log; - -import com.android.inputmethod.keyboard.internal.TouchPositionCorrection; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.utils.JniUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import javax.annotation.Nonnull; - -public class ProximityInfo { - private static final String TAG = ProximityInfo.class.getSimpleName(); - private static final boolean DEBUG = false; - - // Must be equal to MAX_PROXIMITY_CHARS_SIZE in native/jni/src/defines.h - public static final int MAX_PROXIMITY_CHARS_SIZE = 16; - /** Number of key widths from current touch point to search for nearest keys. */ - private static final float SEARCH_DISTANCE = 1.2f; - @Nonnull - private static final List<Key> EMPTY_KEY_LIST = Collections.emptyList(); - private static final float DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS = 0.15f; - - private final int mGridWidth; - private final int mGridHeight; - private final int mGridSize; - private final int mCellWidth; - private final int mCellHeight; - // TODO: Find a proper name for mKeyboardMinWidth - private final int mKeyboardMinWidth; - private final int mKeyboardHeight; - private final int mMostCommonKeyWidth; - private final int mMostCommonKeyHeight; - @Nonnull - private final List<Key> mSortedKeys; - @Nonnull - private final List<Key>[] mGridNeighbors; - - @SuppressWarnings("unchecked") - ProximityInfo(final int gridWidth, final int gridHeight, final int minWidth, final int height, - final int mostCommonKeyWidth, final int mostCommonKeyHeight, - @Nonnull final List<Key> sortedKeys, - @Nonnull final TouchPositionCorrection touchPositionCorrection) { - mGridWidth = gridWidth; - mGridHeight = gridHeight; - mGridSize = mGridWidth * mGridHeight; - mCellWidth = (minWidth + mGridWidth - 1) / mGridWidth; - mCellHeight = (height + mGridHeight - 1) / mGridHeight; - mKeyboardMinWidth = minWidth; - mKeyboardHeight = height; - mMostCommonKeyHeight = mostCommonKeyHeight; - mMostCommonKeyWidth = mostCommonKeyWidth; - mSortedKeys = sortedKeys; - mGridNeighbors = new List[mGridSize]; - if (minWidth == 0 || height == 0) { - // No proximity required. Keyboard might be more keys keyboard. - return; - } - computeNearestNeighbors(); - mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection); - } - - private long mNativeProximityInfo; - static { - JniUtils.loadNativeLibrary(); - } - - // TODO: Stop passing proximityCharsArray - private static native long setProximityInfoNative(int displayWidth, int displayHeight, - int gridWidth, int gridHeight, int mostCommonKeyWidth, int mostCommonKeyHeight, - int[] proximityCharsArray, int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, - int[] keyWidths, int[] keyHeights, int[] keyCharCodes, float[] sweetSpotCenterXs, - float[] sweetSpotCenterYs, float[] sweetSpotRadii); - - private static native void releaseProximityInfoNative(long nativeProximityInfo); - - static boolean needsProximityInfo(final Key key) { - // Don't include special keys into ProximityInfo. - return key.getCode() >= Constants.CODE_SPACE; - } - - private static int getProximityInfoKeysCount(final List<Key> keys) { - int count = 0; - for (final Key key : keys) { - if (needsProximityInfo(key)) { - count++; - } - } - return count; - } - - private long createNativeProximityInfo( - @Nonnull final TouchPositionCorrection touchPositionCorrection) { - final List<Key>[] gridNeighborKeys = mGridNeighbors; - final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE]; - Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE); - for (int i = 0; i < mGridSize; ++i) { - final List<Key> neighborKeys = gridNeighborKeys[i]; - final int proximityCharsLength = neighborKeys.size(); - int infoIndex = i * MAX_PROXIMITY_CHARS_SIZE; - for (int j = 0; j < proximityCharsLength; ++j) { - final Key neighborKey = neighborKeys.get(j); - // Excluding from proximityCharsArray - if (!needsProximityInfo(neighborKey)) { - continue; - } - proximityCharsArray[infoIndex] = neighborKey.getCode(); - infoIndex++; - } - } - if (DEBUG) { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mGridSize; i++) { - sb.setLength(0); - for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE; j++) { - final int code = proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j]; - if (code == Constants.NOT_A_CODE) { - break; - } - if (sb.length() > 0) sb.append(" "); - sb.append(Constants.printableCode(code)); - } - Log.d(TAG, "proxmityChars["+i+"]: " + sb); - } - } - - final List<Key> sortedKeys = mSortedKeys; - final int keyCount = getProximityInfoKeysCount(sortedKeys); - final int[] keyXCoordinates = new int[keyCount]; - final int[] keyYCoordinates = new int[keyCount]; - final int[] keyWidths = new int[keyCount]; - final int[] keyHeights = new int[keyCount]; - final int[] keyCharCodes = new int[keyCount]; - final float[] sweetSpotCenterXs; - final float[] sweetSpotCenterYs; - final float[] sweetSpotRadii; - - for (int infoIndex = 0, keyIndex = 0; keyIndex < sortedKeys.size(); keyIndex++) { - final Key key = sortedKeys.get(keyIndex); - // Excluding from key coordinate arrays - if (!needsProximityInfo(key)) { - continue; - } - keyXCoordinates[infoIndex] = key.getX(); - keyYCoordinates[infoIndex] = key.getY(); - keyWidths[infoIndex] = key.getWidth(); - keyHeights[infoIndex] = key.getHeight(); - keyCharCodes[infoIndex] = key.getCode(); - infoIndex++; - } - - if (touchPositionCorrection.isValid()) { - if (DEBUG) { - Log.d(TAG, "touchPositionCorrection: ON"); - } - sweetSpotCenterXs = new float[keyCount]; - sweetSpotCenterYs = new float[keyCount]; - sweetSpotRadii = new float[keyCount]; - final int rows = touchPositionCorrection.getRows(); - final float defaultRadius = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS - * (float)Math.hypot(mMostCommonKeyWidth, mMostCommonKeyHeight); - for (int infoIndex = 0, keyIndex = 0; keyIndex < sortedKeys.size(); keyIndex++) { - final Key key = sortedKeys.get(keyIndex); - // Excluding from touch position correction arrays - if (!needsProximityInfo(key)) { - continue; - } - final Rect hitBox = key.getHitBox(); - sweetSpotCenterXs[infoIndex] = hitBox.exactCenterX(); - sweetSpotCenterYs[infoIndex] = hitBox.exactCenterY(); - sweetSpotRadii[infoIndex] = defaultRadius; - final int row = hitBox.top / mMostCommonKeyHeight; - if (row < rows) { - final int hitBoxWidth = hitBox.width(); - final int hitBoxHeight = hitBox.height(); - final float hitBoxDiagonal = (float)Math.hypot(hitBoxWidth, hitBoxHeight); - sweetSpotCenterXs[infoIndex] += - touchPositionCorrection.getX(row) * hitBoxWidth; - sweetSpotCenterYs[infoIndex] += - touchPositionCorrection.getY(row) * hitBoxHeight; - sweetSpotRadii[infoIndex] = - touchPositionCorrection.getRadius(row) * hitBoxDiagonal; - } - if (DEBUG) { - Log.d(TAG, String.format( - " [%2d] row=%d x/y/r=%7.2f/%7.2f/%5.2f %s code=%s", infoIndex, row, - sweetSpotCenterXs[infoIndex], sweetSpotCenterYs[infoIndex], - sweetSpotRadii[infoIndex], (row < rows ? "correct" : "default"), - Constants.printableCode(key.getCode()))); - } - infoIndex++; - } - } else { - sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null; - if (DEBUG) { - Log.d(TAG, "touchPositionCorrection: OFF"); - } - } - - // TODO: Stop passing proximityCharsArray - return setProximityInfoNative(mKeyboardMinWidth, mKeyboardHeight, mGridWidth, mGridHeight, - mMostCommonKeyWidth, mMostCommonKeyHeight, proximityCharsArray, keyCount, - keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes, - sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); - } - - public long getNativeProximityInfo() { - return mNativeProximityInfo; - } - - @Override - protected void finalize() throws Throwable { - try { - if (mNativeProximityInfo != 0) { - releaseProximityInfoNative(mNativeProximityInfo); - mNativeProximityInfo = 0; - } - } finally { - super.finalize(); - } - } - - private void computeNearestNeighbors() { - final int defaultWidth = mMostCommonKeyWidth; - final int keyCount = mSortedKeys.size(); - final int gridSize = mGridNeighbors.length; - final int threshold = (int) (defaultWidth * SEARCH_DISTANCE); - final int thresholdSquared = threshold * threshold; - // Round-up so we don't have any pixels outside the grid - final int lastPixelXCoordinate = mGridWidth * mCellWidth - 1; - final int lastPixelYCoordinate = mGridHeight * mCellHeight - 1; - - // For large layouts, 'neighborsFlatBuffer' is about 80k of memory: gridSize is usually 512, - // keycount is about 40 and a pointer to a Key is 4 bytes. This contains, for each cell, - // enough space for as many keys as there are on the keyboard. Hence, every - // keycount'th element is the start of a new cell, and each of these virtual subarrays - // start empty with keycount spaces available. This fills up gradually in the loop below. - // Since in the practice each cell does not have a lot of neighbors, most of this space is - // actually just empty padding in this fixed-size buffer. - final Key[] neighborsFlatBuffer = new Key[gridSize * keyCount]; - final int[] neighborCountPerCell = new int[gridSize]; - final int halfCellWidth = mCellWidth / 2; - final int halfCellHeight = mCellHeight / 2; - for (final Key key : mSortedKeys) { - if (key.isSpacer()) continue; - -/* HOW WE PRE-SELECT THE CELLS (iterate over only the relevant cells, instead of all of them) - - We want to compute the distance for keys that are in the cells that are close enough to the - key border, as this method is performance-critical. These keys are represented with 'star' - background on the diagram below. Let's consider the Y case first. - - We want to select the cells which center falls between the top of the key minus the threshold, - and the bottom of the key plus the threshold. - topPixelWithinThreshold is key.mY - threshold, and bottomPixelWithinThreshold is - key.mY + key.mHeight + threshold. - - Then we need to compute the center of the top row that we need to evaluate, as we'll iterate - from there. - -(0,0)----> x -| .-------------------------------------------. -| | | | | | | | | | | | | -| |---+---+---+---+---+---+---+---+---+---+---| .- top of top cell (aligned on the grid) -| | | | | | | | | | | | | | -| |-----------+---+---+---+---+---+---+---+---|---' v -| | | | |***|***|*_________________________ topPixelWithinThreshold | yDeltaToGrid -| |---+---+---+-----^-+-|-+---+---+---+---+---| ^ -| | | | |***|*|*|*|*|***|***| | | | ______________________________________ -v |---+---+--threshold--|-+---+---+---+---+---| | - | | | |***|*|*|*|*|***|***| | | | | Starting from key.mY, we substract -y |---+---+---+---+-v-+-|-+---+---+---+---+---| | thresholdBase and get the top pixel - | | | |***|**########------------------- key.mY | within the threshold. We align that on - |---+---+---+---+--#+---+-#-+---+---+---+---| | the grid by computing the delta to the - | | | |***|**#|***|*#*|***| | | | | grid, and get the top of the top cell. - |---+---+---+---+--#+---+-#-+---+---+---+---| | - | | | |***|**########*|***| | | | | Adding half the cell height to the top - |---+---+---+---+---+-|-+---+---+---+---+---| | of the top cell, we get the middle of - | | | |***|***|*|*|***|***| | | | | the top cell (yMiddleOfTopCell). - |---+---+---+---+---+-|-+---+---+---+---+---| | - | | | |***|***|*|*|***|***| | | | | - |---+---+---+---+---+-|________________________ yEnd | Since we only want to add the key to - | | | | | | | (bottomPixelWithinThreshold) | the proximity if it's close enough to - |---+---+---+---+---+---+---+---+---+---+---| | the center of the cell, we only need - | | | | | | | | | | | | | to compute for these cells where - '---'---'---'---'---'---'---'---'---'---'---' | topPixelWithinThreshold is above the - (positive x,y) | center of the cell. This is the case - | when yDeltaToGrid is less than half - [Zoomed in diagram] | the height of the cell. - +-------+-------+-------+-------+-------+ | - | | | | | | | On the zoomed in diagram, on the right - | | | | | | | the topPixelWithinThreshold (represented - | | | | | | top of | with an = sign) is below and we can skip - +-------+-------+-------+--v----+-------+ .. top cell | this cell, while on the left it's above - | | = topPixelWT | | yDeltaToGrid | and we need to compute for this cell. - |..yStart.|.....|.......|..|....|.......|... y middle | Thus, if yDeltaToGrid is more than half - | (left)| | | ^ = | | of top cell | the height of the cell, we start the - +-------+-|-----+-------+----|--+-------+ | iteration one cell below the top cell, - | | | | | | | | | else we start it on the top cell. This - |.......|.|.....|.......|....|..|.....yStart (right) | is stored in yStart. - - Since we only want to go up to bottomPixelWithinThreshold, and we only iterate on the center - of the keys, we can stop as soon as the y value exceeds bottomPixelThreshold, so we don't - have to align this on the center of the key. Hence, we don't need a separate value for - bottomPixelWithinThreshold and call this yEnd right away. -*/ - final int keyX = key.getX(); - final int keyY = key.getY(); - final int topPixelWithinThreshold = keyY - threshold; - final int yDeltaToGrid = topPixelWithinThreshold % mCellHeight; - final int yMiddleOfTopCell = topPixelWithinThreshold - yDeltaToGrid + halfCellHeight; - final int yStart = Math.max(halfCellHeight, - yMiddleOfTopCell + (yDeltaToGrid <= halfCellHeight ? 0 : mCellHeight)); - final int yEnd = Math.min(lastPixelYCoordinate, keyY + key.getHeight() + threshold); - - final int leftPixelWithinThreshold = keyX - threshold; - final int xDeltaToGrid = leftPixelWithinThreshold % mCellWidth; - final int xMiddleOfLeftCell = leftPixelWithinThreshold - xDeltaToGrid + halfCellWidth; - final int xStart = Math.max(halfCellWidth, - xMiddleOfLeftCell + (xDeltaToGrid <= halfCellWidth ? 0 : mCellWidth)); - final int xEnd = Math.min(lastPixelXCoordinate, keyX + key.getWidth() + threshold); - - int baseIndexOfCurrentRow = (yStart / mCellHeight) * mGridWidth + (xStart / mCellWidth); - for (int centerY = yStart; centerY <= yEnd; centerY += mCellHeight) { - int index = baseIndexOfCurrentRow; - for (int centerX = xStart; centerX <= xEnd; centerX += mCellWidth) { - if (key.squaredDistanceToEdge(centerX, centerY) < thresholdSquared) { - neighborsFlatBuffer[index * keyCount + neighborCountPerCell[index]] = key; - ++neighborCountPerCell[index]; - } - ++index; - } - baseIndexOfCurrentRow += mGridWidth; - } - } - - for (int i = 0; i < gridSize; ++i) { - final int indexStart = i * keyCount; - final int indexEnd = indexStart + neighborCountPerCell[i]; - final ArrayList<Key> neighbors = new ArrayList<>(indexEnd - indexStart); - for (int index = indexStart; index < indexEnd; index++) { - neighbors.add(neighborsFlatBuffer[index]); - } - mGridNeighbors[i] = Collections.unmodifiableList(neighbors); - } - } - - public void fillArrayWithNearestKeyCodes(final int x, final int y, final int primaryKeyCode, - final int[] dest) { - final int destLength = dest.length; - if (destLength < 1) { - return; - } - int index = 0; - if (primaryKeyCode > Constants.CODE_SPACE) { - dest[index++] = primaryKeyCode; - } - final List<Key> nearestKeys = getNearestKeys(x, y); - for (Key key : nearestKeys) { - if (index >= destLength) { - break; - } - final int code = key.getCode(); - if (code <= Constants.CODE_SPACE) { - break; - } - dest[index++] = code; - } - if (index < destLength) { - dest[index] = Constants.NOT_A_CODE; - } - } - - @Nonnull - public List<Key> getNearestKeys(final int x, final int y) { - if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) { - int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth); - if (index < mGridSize) { - return mGridNeighbors[index]; - } - } - return EMPTY_KEY_LIST; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java deleted file mode 100644 index daeb1f928..000000000 --- a/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.emoji; - -import android.content.SharedPreferences; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.utils.JsonUtils; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * This is a Keyboard class where you can add keys dynamically shown in a grid layout - */ -final class DynamicGridKeyboard extends Keyboard { - private static final String TAG = DynamicGridKeyboard.class.getSimpleName(); - private static final int TEMPLATE_KEY_CODE_0 = 0x30; - private static final int TEMPLATE_KEY_CODE_1 = 0x31; - private final Object mLock = new Object(); - - private final SharedPreferences mPrefs; - private final int mHorizontalStep; - private final int mVerticalStep; - private final int mColumnsNum; - private final int mMaxKeyCount; - private final boolean mIsRecents; - private final ArrayDeque<GridKey> mGridKeys = new ArrayDeque<>(); - private final ArrayDeque<Key> mPendingKeys = new ArrayDeque<>(); - - private List<Key> mCachedGridKeys; - - public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard, - final int maxKeyCount, final int categoryId) { - super(templateKeyboard); - final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0); - final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1); - mHorizontalStep = Math.abs(key1.getX() - key0.getX()); - mVerticalStep = key0.getHeight() + mVerticalGap; - mColumnsNum = mBaseWidth / mHorizontalStep; - mMaxKeyCount = maxKeyCount; - mIsRecents = categoryId == EmojiCategory.ID_RECENTS; - mPrefs = prefs; - } - - private Key getTemplateKey(final int code) { - for (final Key key : super.getSortedKeys()) { - if (key.getCode() == code) { - return key; - } - } - throw new RuntimeException("Can't find template key: code=" + code); - } - - public void addPendingKey(final Key usedKey) { - synchronized (mLock) { - mPendingKeys.addLast(usedKey); - } - } - - public void flushPendingRecentKeys() { - synchronized (mLock) { - while (!mPendingKeys.isEmpty()) { - addKey(mPendingKeys.pollFirst(), true); - } - saveRecentKeys(); - } - } - - public void addKeyFirst(final Key usedKey) { - addKey(usedKey, true); - if (mIsRecents) { - saveRecentKeys(); - } - } - - public void addKeyLast(final Key usedKey) { - addKey(usedKey, false); - } - - private void addKey(final Key usedKey, final boolean addFirst) { - if (usedKey == null) { - return; - } - synchronized (mLock) { - mCachedGridKeys = null; - final GridKey key = new GridKey(usedKey); - while (mGridKeys.remove(key)) { - // Remove duplicate keys. - } - if (addFirst) { - mGridKeys.addFirst(key); - } else { - mGridKeys.addLast(key); - } - while (mGridKeys.size() > mMaxKeyCount) { - mGridKeys.removeLast(); - } - int index = 0; - for (final GridKey gridKey : mGridKeys) { - final int keyX0 = getKeyX0(index); - final int keyY0 = getKeyY0(index); - final int keyX1 = getKeyX1(index); - final int keyY1 = getKeyY1(index); - gridKey.updateCoordinates(keyX0, keyY0, keyX1, keyY1); - index++; - } - } - } - - private void saveRecentKeys() { - final ArrayList<Object> keys = new ArrayList<>(); - for (final Key key : mGridKeys) { - if (key.getOutputText() != null) { - keys.add(key.getOutputText()); - } else { - keys.add(key.getCode()); - } - } - final String jsonStr = JsonUtils.listToJsonStr(keys); - Settings.writeEmojiRecentKeys(mPrefs, jsonStr); - } - - private static Key getKeyByCode(final Collection<DynamicGridKeyboard> keyboards, - final int code) { - for (final DynamicGridKeyboard keyboard : keyboards) { - for (final Key key : keyboard.getSortedKeys()) { - if (key.getCode() == code) { - return key; - } - } - } - return null; - } - - private static Key getKeyByOutputText(final Collection<DynamicGridKeyboard> keyboards, - final String outputText) { - for (final DynamicGridKeyboard keyboard : keyboards) { - for (final Key key : keyboard.getSortedKeys()) { - if (outputText.equals(key.getOutputText())) { - return key; - } - } - } - return null; - } - - public void loadRecentKeys(final Collection<DynamicGridKeyboard> keyboards) { - final String str = Settings.readEmojiRecentKeys(mPrefs); - final List<Object> keys = JsonUtils.jsonStrToList(str); - for (final Object o : keys) { - final Key key; - if (o instanceof Integer) { - final int code = (Integer)o; - key = getKeyByCode(keyboards, code); - } else if (o instanceof String) { - final String outputText = (String)o; - key = getKeyByOutputText(keyboards, outputText); - } else { - Log.w(TAG, "Invalid object: " + o); - continue; - } - addKeyLast(key); - } - } - - private int getKeyX0(final int index) { - final int column = index % mColumnsNum; - return column * mHorizontalStep; - } - - private int getKeyX1(final int index) { - final int column = index % mColumnsNum + 1; - return column * mHorizontalStep; - } - - private int getKeyY0(final int index) { - final int row = index / mColumnsNum; - return row * mVerticalStep + mVerticalGap / 2; - } - - private int getKeyY1(final int index) { - final int row = index / mColumnsNum + 1; - return row * mVerticalStep + mVerticalGap / 2; - } - - @Override - public List<Key> getSortedKeys() { - synchronized (mLock) { - if (mCachedGridKeys != null) { - return mCachedGridKeys; - } - final ArrayList<Key> cachedKeys = new ArrayList<Key>(mGridKeys); - mCachedGridKeys = Collections.unmodifiableList(cachedKeys); - return mCachedGridKeys; - } - } - - @Override - public List<Key> getNearestKeys(final int x, final int y) { - // TODO: Calculate the nearest key index in mGridKeys from x and y. - return getSortedKeys(); - } - - static final class GridKey extends Key { - private int mCurrentX; - private int mCurrentY; - - public GridKey(final Key originalKey) { - super(originalKey); - } - - public void updateCoordinates(final int x0, final int y0, final int x1, final int y1) { - mCurrentX = x0; - mCurrentY = y0; - getHitBox().set(x0, y0, x1, y1); - } - - @Override - public int getX() { - return mCurrentX; - } - - @Override - public int getY() { - return mCurrentY; - } - - @Override - public boolean equals(final Object o) { - if (!(o instanceof Key)) return false; - final Key key = (Key)o; - if (getCode() != key.getCode()) return false; - if (!TextUtils.equals(getLabel(), key.getLabel())) return false; - return TextUtils.equals(getOutputText(), key.getOutputText()); - } - - @Override - public String toString() { - return "GridKey: " + super.toString(); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java deleted file mode 100644 index b57e483d1..000000000 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java +++ /dev/null @@ -1,470 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.inputmethod.keyboard.emoji; - -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Paint; -import android.graphics.Rect; -import android.os.Build; -import android.util.Log; -import android.util.Pair; - -import com.android.inputmethod.compat.BuildCompatUtils; -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.keyboard.KeyboardLayoutSet; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.settings.Settings; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; - -final class EmojiCategory { - private final String TAG = EmojiCategory.class.getSimpleName(); - - private static final int ID_UNSPECIFIED = -1; - public static final int ID_RECENTS = 0; - private static final int ID_PEOPLE = 1; - private static final int ID_OBJECTS = 2; - private static final int ID_NATURE = 3; - private static final int ID_PLACES = 4; - private static final int ID_SYMBOLS = 5; - private static final int ID_EMOTICONS = 6; - private static final int ID_FLAGS = 7; - private static final int ID_EIGHT_SMILEY_PEOPLE = 8; - private static final int ID_EIGHT_ANIMALS_NATURE = 9; - private static final int ID_EIGHT_FOOD_DRINK = 10; - private static final int ID_EIGHT_TRAVEL_PLACES = 11; - private static final int ID_EIGHT_ACTIVITY = 12; - private static final int ID_EIGHT_OBJECTS = 13; - private static final int ID_EIGHT_SYMBOLS = 14; - private static final int ID_EIGHT_FLAGS = 15; - private static final int ID_EIGHT_SMILEY_PEOPLE_BORING = 16; - - public final class CategoryProperties { - public final int mCategoryId; - public final int mPageCount; - public CategoryProperties(final int categoryId, final int pageCount) { - mCategoryId = categoryId; - mPageCount = pageCount; - } - } - - private static final String[] sCategoryName = { - "recents", - "people", - "objects", - "nature", - "places", - "symbols", - "emoticons", - "flags", - "smiley & people", - "animals & nature", - "food & drink", - "travel & places", - "activity", - "objects2", - "symbols2", - "flags2", - "smiley & people2" }; - - private static final int[] sCategoryTabIconAttr = { - R.styleable.EmojiPalettesView_iconEmojiRecentsTab, - R.styleable.EmojiPalettesView_iconEmojiCategory1Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory2Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory3Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory4Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory5Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory6Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory7Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory8Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory9Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory10Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory11Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory12Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory13Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory14Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory15Tab, - R.styleable.EmojiPalettesView_iconEmojiCategory16Tab }; - - private static final int[] sAccessibilityDescriptionResourceIdsForCategories = { - R.string.spoken_descrption_emoji_category_recents, - R.string.spoken_descrption_emoji_category_people, - R.string.spoken_descrption_emoji_category_objects, - R.string.spoken_descrption_emoji_category_nature, - R.string.spoken_descrption_emoji_category_places, - R.string.spoken_descrption_emoji_category_symbols, - R.string.spoken_descrption_emoji_category_emoticons, - R.string.spoken_descrption_emoji_category_flags, - R.string.spoken_descrption_emoji_category_eight_smiley_people, - R.string.spoken_descrption_emoji_category_eight_animals_nature, - R.string.spoken_descrption_emoji_category_eight_food_drink, - R.string.spoken_descrption_emoji_category_eight_travel_places, - R.string.spoken_descrption_emoji_category_eight_activity, - R.string.spoken_descrption_emoji_category_objects, - R.string.spoken_descrption_emoji_category_symbols, - R.string.spoken_descrption_emoji_category_flags, - R.string.spoken_descrption_emoji_category_eight_smiley_people }; - - private static final int[] sCategoryElementId = { - KeyboardId.ELEMENT_EMOJI_RECENTS, - KeyboardId.ELEMENT_EMOJI_CATEGORY1, - KeyboardId.ELEMENT_EMOJI_CATEGORY2, - KeyboardId.ELEMENT_EMOJI_CATEGORY3, - KeyboardId.ELEMENT_EMOJI_CATEGORY4, - KeyboardId.ELEMENT_EMOJI_CATEGORY5, - KeyboardId.ELEMENT_EMOJI_CATEGORY6, - KeyboardId.ELEMENT_EMOJI_CATEGORY7, - KeyboardId.ELEMENT_EMOJI_CATEGORY8, - KeyboardId.ELEMENT_EMOJI_CATEGORY9, - KeyboardId.ELEMENT_EMOJI_CATEGORY10, - KeyboardId.ELEMENT_EMOJI_CATEGORY11, - KeyboardId.ELEMENT_EMOJI_CATEGORY12, - KeyboardId.ELEMENT_EMOJI_CATEGORY13, - KeyboardId.ELEMENT_EMOJI_CATEGORY14, - KeyboardId.ELEMENT_EMOJI_CATEGORY15, - KeyboardId.ELEMENT_EMOJI_CATEGORY16 }; - - private final SharedPreferences mPrefs; - private final Resources mRes; - private final int mMaxPageKeyCount; - private final KeyboardLayoutSet mLayoutSet; - private final HashMap<String, Integer> mCategoryNameToIdMap = new HashMap<>(); - private final int[] mCategoryTabIconId = new int[sCategoryName.length]; - private final ArrayList<CategoryProperties> mShownCategories = new ArrayList<>(); - private final ConcurrentHashMap<Long, DynamicGridKeyboard> mCategoryKeyboardMap = - new ConcurrentHashMap<>(); - - private int mCurrentCategoryId = EmojiCategory.ID_UNSPECIFIED; - private int mCurrentCategoryPageId = 0; - - public EmojiCategory(final SharedPreferences prefs, final Resources res, - final KeyboardLayoutSet layoutSet, final TypedArray emojiPaletteViewAttr) { - mPrefs = prefs; - mRes = res; - mMaxPageKeyCount = res.getInteger(R.integer.config_emoji_keyboard_max_page_key_count); - mLayoutSet = layoutSet; - for (int i = 0; i < sCategoryName.length; ++i) { - mCategoryNameToIdMap.put(sCategoryName[i], i); - mCategoryTabIconId[i] = emojiPaletteViewAttr.getResourceId( - sCategoryTabIconAttr[i], 0); - } - - int defaultCategoryId = EmojiCategory.ID_SYMBOLS; - addShownCategoryId(EmojiCategory.ID_RECENTS); - if (BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.KITKAT) { - if (canShowUnicodeEightEmoji()) { - defaultCategoryId = EmojiCategory.ID_EIGHT_SMILEY_PEOPLE; - addShownCategoryId(EmojiCategory.ID_EIGHT_SMILEY_PEOPLE); - addShownCategoryId(EmojiCategory.ID_EIGHT_ANIMALS_NATURE); - addShownCategoryId(EmojiCategory.ID_EIGHT_FOOD_DRINK); - addShownCategoryId(EmojiCategory.ID_EIGHT_TRAVEL_PLACES); - addShownCategoryId(EmojiCategory.ID_EIGHT_ACTIVITY); - addShownCategoryId(EmojiCategory.ID_EIGHT_OBJECTS); - addShownCategoryId(EmojiCategory.ID_EIGHT_SYMBOLS); - addShownCategoryId(EmojiCategory.ID_FLAGS); // Exclude combinations without glyphs. - } else { - defaultCategoryId = EmojiCategory.ID_PEOPLE; - addShownCategoryId(EmojiCategory.ID_PEOPLE); - addShownCategoryId(EmojiCategory.ID_OBJECTS); - addShownCategoryId(EmojiCategory.ID_NATURE); - addShownCategoryId(EmojiCategory.ID_PLACES); - addShownCategoryId(EmojiCategory.ID_SYMBOLS); - if (canShowFlagEmoji()) { - addShownCategoryId(EmojiCategory.ID_FLAGS); - } - } - } else { - addShownCategoryId(EmojiCategory.ID_SYMBOLS); - } - addShownCategoryId(EmojiCategory.ID_EMOTICONS); - - DynamicGridKeyboard recentsKbd = - getKeyboard(EmojiCategory.ID_RECENTS, 0 /* categoryPageId */); - recentsKbd.loadRecentKeys(mCategoryKeyboardMap.values()); - - mCurrentCategoryId = Settings.readLastShownEmojiCategoryId(mPrefs, defaultCategoryId); - Log.i(TAG, "Last Emoji category id is " + mCurrentCategoryId); - if (!isShownCategoryId(mCurrentCategoryId)) { - Log.i(TAG, "Last emoji category " + mCurrentCategoryId + - " is invalid, starting in " + defaultCategoryId); - mCurrentCategoryId = defaultCategoryId; - } else if (mCurrentCategoryId == EmojiCategory.ID_RECENTS && - recentsKbd.getSortedKeys().isEmpty()) { - Log.i(TAG, "No recent emojis found, starting in category " + defaultCategoryId); - mCurrentCategoryId = defaultCategoryId; - } - } - - private void addShownCategoryId(final int categoryId) { - // Load a keyboard of categoryId - getKeyboard(categoryId, 0 /* categoryPageId */); - final CategoryProperties properties = - new CategoryProperties(categoryId, getCategoryPageCount(categoryId)); - mShownCategories.add(properties); - } - - private boolean isShownCategoryId(final int categoryId) { - for (final CategoryProperties prop : mShownCategories) { - if (prop.mCategoryId == categoryId) { - return true; - } - } - return false; - } - - public static String getCategoryName(final int categoryId, final int categoryPageId) { - return sCategoryName[categoryId] + "-" + categoryPageId; - } - - public int getCategoryId(final String name) { - final String[] strings = name.split("-"); - return mCategoryNameToIdMap.get(strings[0]); - } - - public int getCategoryTabIcon(final int categoryId) { - return mCategoryTabIconId[categoryId]; - } - - public String getAccessibilityDescription(final int categoryId) { - return mRes.getString(sAccessibilityDescriptionResourceIdsForCategories[categoryId]); - } - - public ArrayList<CategoryProperties> getShownCategories() { - return mShownCategories; - } - - public int getCurrentCategoryId() { - return mCurrentCategoryId; - } - - public int getCurrentCategoryPageSize() { - return getCategoryPageSize(mCurrentCategoryId); - } - - public int getCategoryPageSize(final int categoryId) { - for (final CategoryProperties prop : mShownCategories) { - if (prop.mCategoryId == categoryId) { - return prop.mPageCount; - } - } - Log.w(TAG, "Invalid category id: " + categoryId); - // Should not reach here. - return 0; - } - - public void setCurrentCategoryId(final int categoryId) { - mCurrentCategoryId = categoryId; - Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId); - } - - public void setCurrentCategoryPageId(final int id) { - mCurrentCategoryPageId = id; - } - - public int getCurrentCategoryPageId() { - return mCurrentCategoryPageId; - } - - public void saveLastTypedCategoryPage() { - Settings.writeLastTypedEmojiCategoryPageId( - mPrefs, mCurrentCategoryId, mCurrentCategoryPageId); - } - - public boolean isInRecentTab() { - return mCurrentCategoryId == EmojiCategory.ID_RECENTS; - } - - public int getTabIdFromCategoryId(final int categoryId) { - for (int i = 0; i < mShownCategories.size(); ++i) { - if (mShownCategories.get(i).mCategoryId == categoryId) { - return i; - } - } - Log.w(TAG, "categoryId not found: " + categoryId); - return 0; - } - - // Returns the view pager's page position for the categoryId - public int getPageIdFromCategoryId(final int categoryId) { - final int lastSavedCategoryPageId = - Settings.readLastTypedEmojiCategoryPageId(mPrefs, categoryId); - int sum = 0; - for (int i = 0; i < mShownCategories.size(); ++i) { - final CategoryProperties props = mShownCategories.get(i); - if (props.mCategoryId == categoryId) { - return sum + lastSavedCategoryPageId; - } - sum += props.mPageCount; - } - Log.w(TAG, "categoryId not found: " + categoryId); - return 0; - } - - public int getRecentTabId() { - return getTabIdFromCategoryId(EmojiCategory.ID_RECENTS); - } - - private int getCategoryPageCount(final int categoryId) { - final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]); - return (keyboard.getSortedKeys().size() - 1) / mMaxPageKeyCount + 1; - } - - // Returns a pair of the category id and the category page id from the view pager's page - // position. The category page id is numbered in each category. And the view page position - // is the position of the current shown page in the view pager which contains all pages of - // all categories. - public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(final int position) { - int sum = 0; - for (final CategoryProperties properties : mShownCategories) { - final int temp = sum; - sum += properties.mPageCount; - if (sum > position) { - return new Pair<>(properties.mCategoryId, position - temp); - } - } - return null; - } - - // Returns a keyboard from the view pager's page position. - public DynamicGridKeyboard getKeyboardFromPagePosition(final int position) { - final Pair<Integer, Integer> categoryAndId = - getCategoryIdAndPageIdFromPagePosition(position); - if (categoryAndId != null) { - return getKeyboard(categoryAndId.first, categoryAndId.second); - } - return null; - } - - private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) { - return (((long) categoryId) << Integer.SIZE) | id; - } - - public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) { - synchronized (mCategoryKeyboardMap) { - final Long categoryKeyboardMapKey = getCategoryKeyboardMapKey(categoryId, id); - if (mCategoryKeyboardMap.containsKey(categoryKeyboardMapKey)) { - return mCategoryKeyboardMap.get(categoryKeyboardMapKey); - } - - if (categoryId == EmojiCategory.ID_RECENTS) { - final DynamicGridKeyboard kbd = new DynamicGridKeyboard(mPrefs, - mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS), - mMaxPageKeyCount, categoryId); - mCategoryKeyboardMap.put(categoryKeyboardMapKey, kbd); - return kbd; - } - - final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]); - final Key[][] sortedKeys = sortKeysIntoPages( - keyboard.getSortedKeys(), mMaxPageKeyCount); - for (int pageId = 0; pageId < sortedKeys.length; ++pageId) { - final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs, - mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS), - mMaxPageKeyCount, categoryId); - for (final Key emojiKey : sortedKeys[pageId]) { - if (emojiKey == null) { - break; - } - tempKeyboard.addKeyLast(emojiKey); - } - mCategoryKeyboardMap.put( - getCategoryKeyboardMapKey(categoryId, pageId), tempKeyboard); - } - return mCategoryKeyboardMap.get(categoryKeyboardMapKey); - } - } - - public int getTotalPageCountOfAllCategories() { - int sum = 0; - for (CategoryProperties properties : mShownCategories) { - sum += properties.mPageCount; - } - return sum; - } - - private static Comparator<Key> EMOJI_KEY_COMPARATOR = new Comparator<Key>() { - @Override - public int compare(final Key lhs, final Key rhs) { - final Rect lHitBox = lhs.getHitBox(); - final Rect rHitBox = rhs.getHitBox(); - if (lHitBox.top < rHitBox.top) { - return -1; - } else if (lHitBox.top > rHitBox.top) { - return 1; - } - if (lHitBox.left < rHitBox.left) { - return -1; - } else if (lHitBox.left > rHitBox.left) { - return 1; - } - if (lhs.getCode() == rhs.getCode()) { - return 0; - } - return lhs.getCode() < rhs.getCode() ? -1 : 1; - } - }; - - private static Key[][] sortKeysIntoPages(final List<Key> inKeys, final int maxPageCount) { - final ArrayList<Key> keys = new ArrayList<>(inKeys); - Collections.sort(keys, EMOJI_KEY_COMPARATOR); - final int pageCount = (keys.size() - 1) / maxPageCount + 1; - final Key[][] retval = new Key[pageCount][maxPageCount]; - for (int i = 0; i < keys.size(); ++i) { - retval[i / maxPageCount][i % maxPageCount] = keys.get(i); - } - return retval; - } - - private static boolean canShowFlagEmoji() { - Paint paint = new Paint(); - String switzerland = "\uD83C\uDDE8\uD83C\uDDED"; // U+1F1E8 U+1F1ED Flag for Switzerland - try { - return paint.hasGlyph(switzerland); - } catch (NoSuchMethodError e) { - // Compare display width of single-codepoint emoji to width of flag emoji to determine - // whether flag is rendered as single glyph or two adjacent regional indicator symbols. - float flagWidth = paint.measureText(switzerland); - float standardWidth = paint.measureText("\uD83D\uDC27"); // U+1F427 Penguin - return flagWidth < standardWidth * 1.25; - // This assumes that a valid glyph for the flag emoji must be less than 1.25 times - // the width of the penguin. - } - } - - private static boolean canShowUnicodeEightEmoji() { - Paint paint = new Paint(); - String cheese = "\uD83E\uDDC0"; // U+1F9C0 Cheese wedge - try { - return paint.hasGlyph(cheese); - } catch (NoSuchMethodError e) { - float cheeseWidth = paint.measureText(cheese); - float tofuWidth = paint.measureText("\uFFFE"); - return cheeseWidth > tofuWidth; - // This assumes that a valid glyph for the cheese wedge must be greater than the width - // of the noncharacter. - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java deleted file mode 100644 index 43d62c71a..000000000 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.emoji; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.view.View; - -public final class EmojiCategoryPageIndicatorView extends View { - private static final float BOTTOM_MARGIN_RATIO = 1.0f; - private final Paint mPaint = new Paint(); - private int mCategoryPageSize = 0; - private int mCurrentCategoryPageId = 0; - private float mOffset = 0.0f; - - public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs) { - this(context, attrs, 0); - } - - public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs, - final int defStyle) { - super(context, attrs, defStyle); - } - - public void setColors(final int foregroundColor, final int backgroundColor) { - mPaint.setColor(foregroundColor); - setBackgroundColor(backgroundColor); - } - - public void setCategoryPageId(final int size, final int id, final float offset) { - mCategoryPageSize = size; - mCurrentCategoryPageId = id; - mOffset = offset; - invalidate(); - } - - @Override - protected void onDraw(final Canvas canvas) { - if (mCategoryPageSize <= 1) { - // If the category is not set yet or contains only one category, - // just clear and return. - canvas.drawColor(0); - return; - } - final float height = getHeight(); - final float width = getWidth(); - final float unitWidth = width / mCategoryPageSize; - final float left = unitWidth * mCurrentCategoryPageId + mOffset * unitWidth; - final float top = 0.0f; - final float right = left + unitWidth; - final float bottom = height * BOTTOM_MARGIN_RATIO; - canvas.drawRect(left, top, right, bottom, mPaint); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java deleted file mode 100644 index a85c3a97f..000000000 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.emoji; - -import android.content.Context; -import android.content.res.Resources; -import androidx.viewpager.widget.ViewPager; -import android.view.View; -import android.widget.LinearLayout; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.ResourceUtils; - -final class EmojiLayoutParams { - private static final int DEFAULT_KEYBOARD_ROWS = 4; - - public final int mEmojiPagerHeight; - private final int mEmojiPagerBottomMargin; - public final int mEmojiKeyboardHeight; - private final int mEmojiCategoryPageIdViewHeight; - public final int mEmojiActionBarHeight; - public final int mKeyVerticalGap; - private final int mKeyHorizontalGap; - private final int mBottomPadding; - private final int mTopPadding; - - public EmojiLayoutParams(final Context context) { - final Resources res = context.getResources(); - final int defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res); - final int defaultKeyboardWidth = ResourceUtils.getDefaultKeyboardWidth(context); - mKeyVerticalGap = (int) res.getFraction(R.fraction.config_key_vertical_gap_holo, - defaultKeyboardHeight, defaultKeyboardHeight); - mBottomPadding = (int) res.getFraction(R.fraction.config_keyboard_bottom_padding_holo, - defaultKeyboardHeight, defaultKeyboardHeight); - mTopPadding = (int) res.getFraction(R.fraction.config_keyboard_top_padding_holo, - defaultKeyboardHeight, defaultKeyboardHeight); - mKeyHorizontalGap = (int) (res.getFraction(R.fraction.config_key_horizontal_gap_holo, - defaultKeyboardWidth, defaultKeyboardWidth)); - mEmojiCategoryPageIdViewHeight = - (int) (res.getDimension(R.dimen.config_emoji_category_page_id_height)); - final int baseheight = defaultKeyboardHeight - mBottomPadding - mTopPadding - + mKeyVerticalGap; - mEmojiActionBarHeight = baseheight / DEFAULT_KEYBOARD_ROWS - - (mKeyVerticalGap - mBottomPadding) / 2; - mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight - - mEmojiCategoryPageIdViewHeight; - mEmojiPagerBottomMargin = 0; - mEmojiKeyboardHeight = mEmojiPagerHeight - mEmojiPagerBottomMargin - 1; - } - - public void setPagerProperties(final ViewPager vp) { - final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) vp.getLayoutParams(); - lp.height = mEmojiKeyboardHeight; - lp.bottomMargin = mEmojiPagerBottomMargin; - vp.setLayoutParams(lp); - } - - public void setCategoryPageIdViewProperties(final View v) { - final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) v.getLayoutParams(); - lp.height = mEmojiCategoryPageIdViewHeight; - v.setLayoutParams(lp); - } - - public int getActionBarHeight() { - return mEmojiActionBarHeight - mBottomPadding; - } - - public void setActionBarProperties(final LinearLayout ll) { - final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams(); - lp.height = getActionBarHeight(); - ll.setLayoutParams(lp); - } - - public void setKeyProperties(final View v) { - final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) v.getLayoutParams(); - lp.leftMargin = mKeyHorizontalGap / 2; - lp.rightMargin = mKeyHorizontalGap / 2; - v.setLayoutParams(lp); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java deleted file mode 100644 index 09313f811..000000000 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.emoji; - -import android.content.Context; -import android.os.Handler; -import android.util.AttributeSet; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.accessibility.AccessibilityEvent; - -import com.android.inputmethod.accessibility.AccessibilityUtils; -import com.android.inputmethod.accessibility.KeyboardAccessibilityDelegate; -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.KeyDetector; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.latin.R; - -/** - * This is an extended {@link KeyboardView} class that hosts an emoji page keyboard. - * Multi-touch unsupported. No gesture support. - */ -// TODO: Implement key popup preview. -final class EmojiPageKeyboardView extends KeyboardView implements - GestureDetector.OnGestureListener { - private static final long KEY_PRESS_DELAY_TIME = 250; // msec - private static final long KEY_RELEASE_DELAY_TIME = 30; // msec - - public interface OnKeyEventListener { - public void onPressKey(Key key); - public void onReleaseKey(Key key); - } - - private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() { - @Override - public void onPressKey(final Key key) {} - @Override - public void onReleaseKey(final Key key) {} - }; - - private OnKeyEventListener mListener = EMPTY_LISTENER; - private final KeyDetector mKeyDetector = new KeyDetector(); - private final GestureDetector mGestureDetector; - private KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate; - - public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.keyboardViewStyle); - } - - public EmojiPageKeyboardView(final Context context, final AttributeSet attrs, - final int defStyle) { - super(context, attrs, defStyle); - mGestureDetector = new GestureDetector(context, this); - mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */); - mHandler = new Handler(); - } - - public void setOnKeyEventListener(final OnKeyEventListener listener) { - mListener = listener; - } - - /** - * {@inheritDoc} - */ - @Override - public void setKeyboard(final Keyboard keyboard) { - super.setKeyboard(keyboard); - mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */); - if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { - if (mAccessibilityDelegate == null) { - mAccessibilityDelegate = new KeyboardAccessibilityDelegate<>(this, mKeyDetector); - } - mAccessibilityDelegate.setKeyboard(keyboard); - } else { - mAccessibilityDelegate = null; - } - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) { - // Don't populate accessibility event with all Emoji keys. - return true; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onHoverEvent(final MotionEvent event) { - final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> accessibilityDelegate = - mAccessibilityDelegate; - if (accessibilityDelegate != null - && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { - return accessibilityDelegate.onHoverEvent(event); - } - return super.onHoverEvent(event); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onTouchEvent(final MotionEvent e) { - if (mGestureDetector.onTouchEvent(e)) { - return true; - } - final Key key = getKey(e); - if (key != null && key != mCurrentKey) { - releaseCurrentKey(false /* withKeyRegistering */); - } - return true; - } - - // {@link GestureEnabler#OnGestureListener} methods. - private Key mCurrentKey; - private Runnable mPendingKeyDown; - private final Handler mHandler; - - private Key getKey(final MotionEvent e) { - final int index = e.getActionIndex(); - final int x = (int)e.getX(index); - final int y = (int)e.getY(index); - return mKeyDetector.detectHitKey(x, y); - } - - void callListenerOnReleaseKey(final Key releasedKey, final boolean withKeyRegistering) { - releasedKey.onReleased(); - invalidateKey(releasedKey); - if (withKeyRegistering) { - mListener.onReleaseKey(releasedKey); - } - } - - void callListenerOnPressKey(final Key pressedKey) { - mPendingKeyDown = null; - pressedKey.onPressed(); - invalidateKey(pressedKey); - mListener.onPressKey(pressedKey); - } - - public void releaseCurrentKey(final boolean withKeyRegistering) { - mHandler.removeCallbacks(mPendingKeyDown); - mPendingKeyDown = null; - final Key currentKey = mCurrentKey; - if (currentKey == null) { - return; - } - callListenerOnReleaseKey(currentKey, withKeyRegistering); - mCurrentKey = null; - } - - @Override - public boolean onDown(final MotionEvent e) { - final Key key = getKey(e); - releaseCurrentKey(false /* withKeyRegistering */); - mCurrentKey = key; - if (key == null) { - return false; - } - // Do not trigger key-down effect right now in case this is actually a fling action. - mPendingKeyDown = new Runnable() { - @Override - public void run() { - callListenerOnPressKey(key); - } - }; - mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME); - return false; - } - - @Override - public void onShowPress(final MotionEvent e) { - // User feedback is done at {@link #onDown(MotionEvent)}. - } - - @Override - public boolean onSingleTapUp(final MotionEvent e) { - final Key key = getKey(e); - final Runnable pendingKeyDown = mPendingKeyDown; - final Key currentKey = mCurrentKey; - releaseCurrentKey(false /* withKeyRegistering */); - if (key == null) { - return false; - } - if (key == currentKey && pendingKeyDown != null) { - pendingKeyDown.run(); - // Trigger key-release event a little later so that a user can see visual feedback. - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - callListenerOnReleaseKey(key, true /* withRegistering */); - } - }, KEY_RELEASE_DELAY_TIME); - } else { - callListenerOnReleaseKey(key, true /* withRegistering */); - } - return true; - } - - @Override - public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, - final float distanceY) { - releaseCurrentKey(false /* withKeyRegistering */); - return false; - } - - @Override - public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, - final float velocityY) { - releaseCurrentKey(false /* withKeyRegistering */); - return false; - } - - @Override - public void onLongPress(final MotionEvent e) { - // Long press detection of {@link #mGestureDetector} is disabled and not used. - } -} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java deleted file mode 100644 index 18b9c7e36..000000000 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.keyboard.emoji; - -import androidx.viewpager.widget.PagerAdapter; -import android.util.Log; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.latin.R; - -final class EmojiPalettesAdapter extends PagerAdapter { - private static final String TAG = EmojiPalettesAdapter.class.getSimpleName(); - private static final boolean DEBUG_PAGER = false; - - private final EmojiPageKeyboardView.OnKeyEventListener mListener; - private final DynamicGridKeyboard mRecentsKeyboard; - private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews = new SparseArray<>(); - private final EmojiCategory mEmojiCategory; - private int mActivePosition = 0; - - public EmojiPalettesAdapter(final EmojiCategory emojiCategory, - final EmojiPageKeyboardView.OnKeyEventListener listener) { - mEmojiCategory = emojiCategory; - mListener = listener; - mRecentsKeyboard = mEmojiCategory.getKeyboard(EmojiCategory.ID_RECENTS, 0); - } - - public void flushPendingRecentKeys() { - mRecentsKeyboard.flushPendingRecentKeys(); - final KeyboardView recentKeyboardView = - mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId()); - if (recentKeyboardView != null) { - recentKeyboardView.invalidateAllKeys(); - } - } - - public void addRecentKey(final Key key) { - if (mEmojiCategory.isInRecentTab()) { - mRecentsKeyboard.addPendingKey(key); - return; - } - mRecentsKeyboard.addKeyFirst(key); - final KeyboardView recentKeyboardView = - mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId()); - if (recentKeyboardView != null) { - recentKeyboardView.invalidateAllKeys(); - } - } - - public void onPageScrolled() { - releaseCurrentKey(false /* withKeyRegistering */); - } - - public void releaseCurrentKey(final boolean withKeyRegistering) { - // Make sure the delayed key-down event (highlight effect and haptic feedback) will be - // canceled. - final EmojiPageKeyboardView currentKeyboardView = - mActiveKeyboardViews.get(mActivePosition); - if (currentKeyboardView == null) { - return; - } - currentKeyboardView.releaseCurrentKey(withKeyRegistering); - } - - @Override - public int getCount() { - return mEmojiCategory.getTotalPageCountOfAllCategories(); - } - - @Override - public void setPrimaryItem(final ViewGroup container, final int position, - final Object object) { - if (mActivePosition == position) { - return; - } - final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition); - if (oldKeyboardView != null) { - oldKeyboardView.releaseCurrentKey(false /* withKeyRegistering */); - oldKeyboardView.deallocateMemory(); - } - mActivePosition = position; - } - - @Override - public Object instantiateItem(final ViewGroup container, final int position) { - if (DEBUG_PAGER) { - Log.d(TAG, "instantiate item: " + position); - } - final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position); - if (oldKeyboardView != null) { - oldKeyboardView.deallocateMemory(); - // This may be redundant but wanted to be safer.. - mActiveKeyboardViews.remove(position); - } - final Keyboard keyboard = - mEmojiCategory.getKeyboardFromPagePosition(position); - final LayoutInflater inflater = LayoutInflater.from(container.getContext()); - final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate( - R.layout.emoji_keyboard_page, container, false /* attachToRoot */); - keyboardView.setKeyboard(keyboard); - keyboardView.setOnKeyEventListener(mListener); - container.addView(keyboardView); - mActiveKeyboardViews.put(position, keyboardView); - return keyboardView; - } - - @Override - public boolean isViewFromObject(final View view, final Object object) { - return view == object; - } - - @Override - public void destroyItem(final ViewGroup container, final int position, - final Object object) { - if (DEBUG_PAGER) { - Log.d(TAG, "destroy item: " + position + ", " + object.getClass().getSimpleName()); - } - final EmojiPageKeyboardView keyboardView = mActiveKeyboardViews.get(position); - if (keyboardView != null) { - keyboardView.deallocateMemory(); - mActiveKeyboardViews.remove(position); - } - if (object instanceof View) { - container.removeView((View)object); - } else { - Log.w(TAG, "Warning!!! Emoji palette may be leaking. " + object); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java deleted file mode 100644 index 898605019..000000000 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java +++ /dev/null @@ -1,486 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.emoji; - -import static com.android.inputmethod.latin.common.Constants.NOT_A_COORDINATE; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.preference.PreferenceManager; -import androidx.viewpager.widget.ViewPager; -import android.util.AttributeSet; -import android.util.Pair; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TabHost; -import android.widget.TabHost.OnTabChangeListener; -import android.widget.TabWidget; -import android.widget.TextView; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.keyboard.KeyboardLayoutSet; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.keyboard.internal.KeyDrawParams; -import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; -import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; -import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodSubtype; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.utils.ResourceUtils; - -/** - * View class to implement Emoji palettes. - * The Emoji keyboard consists of group of views layout/emoji_palettes_view. - * <ol> - * <li> Emoji category tabs. - * <li> Delete button. - * <li> Emoji keyboard pages that can be scrolled by swiping horizontally or by selecting a tab. - * <li> Back to main keyboard button and enter button. - * </ol> - * Because of the above reasons, this class doesn't extend {@link KeyboardView}. - */ -public final class EmojiPalettesView extends LinearLayout implements OnTabChangeListener, - ViewPager.OnPageChangeListener, View.OnClickListener, View.OnTouchListener, - EmojiPageKeyboardView.OnKeyEventListener { - private final int mFunctionalKeyBackgroundId; - private final int mSpacebarBackgroundId; - private final boolean mCategoryIndicatorEnabled; - private final int mCategoryIndicatorDrawableResId; - private final int mCategoryIndicatorBackgroundResId; - private final int mCategoryPageIndicatorColor; - private final int mCategoryPageIndicatorBackground; - private EmojiPalettesAdapter mEmojiPalettesAdapter; - private final EmojiLayoutParams mEmojiLayoutParams; - private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener; - - private ImageButton mDeleteKey; - private TextView mAlphabetKeyLeft; - private TextView mAlphabetKeyRight; - private View mSpacebar; - // TODO: Remove this workaround. - private View mSpacebarIcon; - private TabHost mTabHost; - private ViewPager mEmojiPager; - private int mCurrentPagerPosition = 0; - private EmojiCategoryPageIndicatorView mEmojiCategoryPageIndicatorView; - - private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; - - private final EmojiCategory mEmojiCategory; - - public EmojiPalettesView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.emojiPalettesViewStyle); - } - - public EmojiPalettesView(final Context context, final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs, - R.styleable.KeyboardView, defStyle, R.style.KeyboardView); - final int keyBackgroundId = keyboardViewAttr.getResourceId( - R.styleable.KeyboardView_keyBackground, 0); - mFunctionalKeyBackgroundId = keyboardViewAttr.getResourceId( - R.styleable.KeyboardView_functionalKeyBackground, keyBackgroundId); - mSpacebarBackgroundId = keyboardViewAttr.getResourceId( - R.styleable.KeyboardView_spacebarBackground, keyBackgroundId); - keyboardViewAttr.recycle(); - final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( - context, null /* editorInfo */); - final Resources res = context.getResources(); - mEmojiLayoutParams = new EmojiLayoutParams(context); - builder.setSubtype(RichInputMethodSubtype.getEmojiSubtype()); - builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(context), - mEmojiLayoutParams.mEmojiKeyboardHeight); - final KeyboardLayoutSet layoutSet = builder.build(); - final TypedArray emojiPalettesViewAttr = context.obtainStyledAttributes(attrs, - R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView); - mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context), - res, layoutSet, emojiPalettesViewAttr); - mCategoryIndicatorEnabled = emojiPalettesViewAttr.getBoolean( - R.styleable.EmojiPalettesView_categoryIndicatorEnabled, false); - mCategoryIndicatorDrawableResId = emojiPalettesViewAttr.getResourceId( - R.styleable.EmojiPalettesView_categoryIndicatorDrawable, 0); - mCategoryIndicatorBackgroundResId = emojiPalettesViewAttr.getResourceId( - R.styleable.EmojiPalettesView_categoryIndicatorBackground, 0); - mCategoryPageIndicatorColor = emojiPalettesViewAttr.getColor( - R.styleable.EmojiPalettesView_categoryPageIndicatorColor, 0); - mCategoryPageIndicatorBackground = emojiPalettesViewAttr.getColor( - R.styleable.EmojiPalettesView_categoryPageIndicatorBackground, 0); - emojiPalettesViewAttr.recycle(); - mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener(); - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - final Resources res = getContext().getResources(); - // The main keyboard expands to the entire this {@link KeyboardView}. - final int width = ResourceUtils.getDefaultKeyboardWidth(getContext()) - + getPaddingLeft() + getPaddingRight(); - final int height = ResourceUtils.getDefaultKeyboardHeight(res) - + res.getDimensionPixelSize(R.dimen.config_suggestions_strip_height) - + getPaddingTop() + getPaddingBottom(); - setMeasuredDimension(width, height); - } - - private void addTab(final TabHost host, final int categoryId) { - final String tabId = EmojiCategory.getCategoryName(categoryId, 0 /* categoryPageId */); - final TabHost.TabSpec tspec = host.newTabSpec(tabId); - tspec.setContent(R.id.emoji_keyboard_dummy); - final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate( - R.layout.emoji_keyboard_tab_icon, null); - // TODO: Replace background color with its own setting rather than using the - // category page indicator background as a workaround. - iconView.setBackgroundColor(mCategoryPageIndicatorBackground); - iconView.setImageResource(mEmojiCategory.getCategoryTabIcon(categoryId)); - iconView.setContentDescription(mEmojiCategory.getAccessibilityDescription(categoryId)); - tspec.setIndicator(iconView); - host.addTab(tspec); - } - - @Override - protected void onFinishInflate() { - mTabHost = (TabHost)findViewById(R.id.emoji_category_tabhost); - mTabHost.setup(); - for (final EmojiCategory.CategoryProperties properties - : mEmojiCategory.getShownCategories()) { - addTab(mTabHost, properties.mCategoryId); - } - mTabHost.setOnTabChangedListener(this); - final TabWidget tabWidget = mTabHost.getTabWidget(); - tabWidget.setStripEnabled(mCategoryIndicatorEnabled); - if (mCategoryIndicatorEnabled) { - // On TabWidget's strip, what looks like an indicator is actually a background. - // And what looks like a background are actually left and right drawables. - tabWidget.setBackgroundResource(mCategoryIndicatorDrawableResId); - tabWidget.setLeftStripDrawable(mCategoryIndicatorBackgroundResId); - tabWidget.setRightStripDrawable(mCategoryIndicatorBackgroundResId); - } - - mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, this); - - mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager); - mEmojiPager.setAdapter(mEmojiPalettesAdapter); - mEmojiPager.setOnPageChangeListener(this); - mEmojiPager.setOffscreenPageLimit(0); - mEmojiPager.setPersistentDrawingCache(PERSISTENT_NO_CACHE); - mEmojiLayoutParams.setPagerProperties(mEmojiPager); - - mEmojiCategoryPageIndicatorView = - (EmojiCategoryPageIndicatorView)findViewById(R.id.emoji_category_page_id_view); - mEmojiCategoryPageIndicatorView.setColors( - mCategoryPageIndicatorColor, mCategoryPageIndicatorBackground); - mEmojiLayoutParams.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView); - - setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */); - - final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar); - mEmojiLayoutParams.setActionBarProperties(actionBar); - - // deleteKey depends only on OnTouchListener. - mDeleteKey = (ImageButton)findViewById(R.id.emoji_keyboard_delete); - mDeleteKey.setBackgroundResource(mFunctionalKeyBackgroundId); - mDeleteKey.setTag(Constants.CODE_DELETE); - mDeleteKey.setOnTouchListener(mDeleteKeyOnTouchListener); - - // {@link #mAlphabetKeyLeft}, {@link #mAlphabetKeyRight, and spaceKey depend on - // {@link View.OnClickListener} as well as {@link View.OnTouchListener}. - // {@link View.OnTouchListener} is used as the trigger of key-press, while - // {@link View.OnClickListener} is used as the trigger of key-release which does not occur - // if the event is canceled by moving off the finger from the view. - // The text on alphabet keys are set at - // {@link #startEmojiPalettes(String,int,float,Typeface)}. - mAlphabetKeyLeft = (TextView)findViewById(R.id.emoji_keyboard_alphabet_left); - mAlphabetKeyLeft.setBackgroundResource(mFunctionalKeyBackgroundId); - mAlphabetKeyLeft.setTag(Constants.CODE_ALPHA_FROM_EMOJI); - mAlphabetKeyLeft.setOnTouchListener(this); - mAlphabetKeyLeft.setOnClickListener(this); - mAlphabetKeyRight = (TextView)findViewById(R.id.emoji_keyboard_alphabet_right); - mAlphabetKeyRight.setBackgroundResource(mFunctionalKeyBackgroundId); - mAlphabetKeyRight.setTag(Constants.CODE_ALPHA_FROM_EMOJI); - mAlphabetKeyRight.setOnTouchListener(this); - mAlphabetKeyRight.setOnClickListener(this); - mSpacebar = findViewById(R.id.emoji_keyboard_space); - mSpacebar.setBackgroundResource(mSpacebarBackgroundId); - mSpacebar.setTag(Constants.CODE_SPACE); - mSpacebar.setOnTouchListener(this); - mSpacebar.setOnClickListener(this); - mEmojiLayoutParams.setKeyProperties(mSpacebar); - mSpacebarIcon = findViewById(R.id.emoji_keyboard_space_icon); - } - - @Override - public boolean dispatchTouchEvent(final MotionEvent ev) { - // Add here to the stack trace to nail down the {@link IllegalArgumentException} exception - // in MotionEvent that sporadically happens. - // TODO: Remove this override method once the issue has been addressed. - return super.dispatchTouchEvent(ev); - } - - @Override - public void onTabChanged(final String tabId) { - AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback( - Constants.CODE_UNSPECIFIED, this); - final int categoryId = mEmojiCategory.getCategoryId(tabId); - setCurrentCategoryId(categoryId, false /* force */); - updateEmojiCategoryPageIdView(); - } - - @Override - public void onPageSelected(final int position) { - final Pair<Integer, Integer> newPos = - mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position); - setCurrentCategoryId(newPos.first /* categoryId */, false /* force */); - mEmojiCategory.setCurrentCategoryPageId(newPos.second /* categoryPageId */); - updateEmojiCategoryPageIdView(); - mCurrentPagerPosition = position; - } - - @Override - public void onPageScrollStateChanged(final int state) { - // Ignore this message. Only want the actual page selected. - } - - @Override - public void onPageScrolled(final int position, final float positionOffset, - final int positionOffsetPixels) { - mEmojiPalettesAdapter.onPageScrolled(); - final Pair<Integer, Integer> newPos = - mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position); - final int newCategoryId = newPos.first; - final int newCategorySize = mEmojiCategory.getCategoryPageSize(newCategoryId); - final int currentCategoryId = mEmojiCategory.getCurrentCategoryId(); - final int currentCategoryPageId = mEmojiCategory.getCurrentCategoryPageId(); - final int currentCategorySize = mEmojiCategory.getCurrentCategoryPageSize(); - if (newCategoryId == currentCategoryId) { - mEmojiCategoryPageIndicatorView.setCategoryPageId( - newCategorySize, newPos.second, positionOffset); - } else if (newCategoryId > currentCategoryId) { - mEmojiCategoryPageIndicatorView.setCategoryPageId( - currentCategorySize, currentCategoryPageId, positionOffset); - } else if (newCategoryId < currentCategoryId) { - mEmojiCategoryPageIndicatorView.setCategoryPageId( - currentCategorySize, currentCategoryPageId, positionOffset - 1); - } - } - - /** - * Called from {@link EmojiPageKeyboardView} through {@link android.view.View.OnTouchListener} - * interface to handle touch events from View-based elements such as the space bar. - * Note that this method is used only for observing {@link MotionEvent#ACTION_DOWN} to trigger - * {@link KeyboardActionListener#onPressKey}. {@link KeyboardActionListener#onReleaseKey} will - * be covered by {@link #onClick} as long as the event is not canceled. - */ - @Override - public boolean onTouch(final View v, final MotionEvent event) { - if (event.getActionMasked() != MotionEvent.ACTION_DOWN) { - return false; - } - final Object tag = v.getTag(); - if (!(tag instanceof Integer)) { - return false; - } - final int code = (Integer) tag; - mKeyboardActionListener.onPressKey( - code, 0 /* repeatCount */, true /* isSinglePointer */); - // It's important to return false here. Otherwise, {@link #onClick} and touch-down visual - // feedback stop working. - return false; - } - - /** - * Called from {@link EmojiPageKeyboardView} through {@link android.view.View.OnClickListener} - * interface to handle non-canceled touch-up events from View-based elements such as the space - * bar. - */ - @Override - public void onClick(View v) { - final Object tag = v.getTag(); - if (!(tag instanceof Integer)) { - return; - } - final int code = (Integer) tag; - mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE, - false /* isKeyRepeat */); - mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); - } - - /** - * Called from {@link EmojiPageKeyboardView} through - * {@link com.android.inputmethod.keyboard.emoji.EmojiPageKeyboardView.OnKeyEventListener} - * interface to handle touch events from non-View-based elements such as Emoji buttons. - */ - @Override - public void onPressKey(final Key key) { - final int code = key.getCode(); - mKeyboardActionListener.onPressKey(code, 0 /* repeatCount */, true /* isSinglePointer */); - } - - /** - * Called from {@link EmojiPageKeyboardView} through - * {@link com.android.inputmethod.keyboard.emoji.EmojiPageKeyboardView.OnKeyEventListener} - * interface to handle touch events from non-View-based elements such as Emoji buttons. - */ - @Override - public void onReleaseKey(final Key key) { - mEmojiPalettesAdapter.addRecentKey(key); - mEmojiCategory.saveLastTypedCategoryPage(); - final int code = key.getCode(); - if (code == Constants.CODE_OUTPUT_TEXT) { - mKeyboardActionListener.onTextInput(key.getOutputText()); - } else { - mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE, - false /* isKeyRepeat */); - } - mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); - } - - public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { - if (!enabled) return; - // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? - setLayerType(LAYER_TYPE_HARDWARE, null); - } - - private static void setupAlphabetKey(final TextView alphabetKey, final String label, - final KeyDrawParams params) { - alphabetKey.setText(label); - alphabetKey.setTextColor(params.mFunctionalTextColor); - alphabetKey.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mLabelSize); - alphabetKey.setTypeface(params.mTypeface); - } - - public void startEmojiPalettes(final String switchToAlphaLabel, - final KeyVisualAttributes keyVisualAttr, - final KeyboardIconsSet iconSet) { - final int deleteIconResId = iconSet.getIconResourceId(KeyboardIconsSet.NAME_DELETE_KEY); - if (deleteIconResId != 0) { - mDeleteKey.setImageResource(deleteIconResId); - } - final int spacebarResId = iconSet.getIconResourceId(KeyboardIconsSet.NAME_SPACE_KEY); - if (spacebarResId != 0) { - // TODO: Remove this workaround to place the spacebar icon. - mSpacebarIcon.setBackgroundResource(spacebarResId); - } - final KeyDrawParams params = new KeyDrawParams(); - params.updateParams(mEmojiLayoutParams.getActionBarHeight(), keyVisualAttr); - setupAlphabetKey(mAlphabetKeyLeft, switchToAlphaLabel, params); - setupAlphabetKey(mAlphabetKeyRight, switchToAlphaLabel, params); - mEmojiPager.setAdapter(mEmojiPalettesAdapter); - mEmojiPager.setCurrentItem(mCurrentPagerPosition); - } - - public void stopEmojiPalettes() { - mEmojiPalettesAdapter.releaseCurrentKey(true /* withKeyRegistering */); - mEmojiPalettesAdapter.flushPendingRecentKeys(); - mEmojiPager.setAdapter(null); - } - - public void setKeyboardActionListener(final KeyboardActionListener listener) { - mKeyboardActionListener = listener; - mDeleteKeyOnTouchListener.setKeyboardActionListener(listener); - } - - private void updateEmojiCategoryPageIdView() { - if (mEmojiCategoryPageIndicatorView == null) { - return; - } - mEmojiCategoryPageIndicatorView.setCategoryPageId( - mEmojiCategory.getCurrentCategoryPageSize(), - mEmojiCategory.getCurrentCategoryPageId(), 0.0f /* offset */); - } - - private void setCurrentCategoryId(final int categoryId, final boolean force) { - final int oldCategoryId = mEmojiCategory.getCurrentCategoryId(); - if (oldCategoryId == categoryId && !force) { - return; - } - - if (oldCategoryId == EmojiCategory.ID_RECENTS) { - // Needs to save pending updates for recent keys when we get out of the recents - // category because we don't want to move the recent emojis around while the user - // is in the recents category. - mEmojiPalettesAdapter.flushPendingRecentKeys(); - } - - mEmojiCategory.setCurrentCategoryId(categoryId); - final int newTabId = mEmojiCategory.getTabIdFromCategoryId(categoryId); - final int newCategoryPageId = mEmojiCategory.getPageIdFromCategoryId(categoryId); - if (force || mEmojiCategory.getCategoryIdAndPageIdFromPagePosition( - mEmojiPager.getCurrentItem()).first != categoryId) { - mEmojiPager.setCurrentItem(newCategoryPageId, false /* smoothScroll */); - } - if (force || mTabHost.getCurrentTab() != newTabId) { - mTabHost.setCurrentTab(newTabId); - } - } - - private static class DeleteKeyOnTouchListener implements OnTouchListener { - private KeyboardActionListener mKeyboardActionListener = - KeyboardActionListener.EMPTY_LISTENER; - - public void setKeyboardActionListener(final KeyboardActionListener listener) { - mKeyboardActionListener = listener; - } - - @Override - public boolean onTouch(final View v, final MotionEvent event) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - onTouchDown(v); - return true; - case MotionEvent.ACTION_MOVE: - final float x = event.getX(); - final float y = event.getY(); - if (x < 0.0f || v.getWidth() < x || y < 0.0f || v.getHeight() < y) { - // Stop generating key events once the finger moves away from the view area. - onTouchCanceled(v); - } - return true; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - onTouchUp(v); - return true; - } - return false; - } - - private void onTouchDown(final View v) { - mKeyboardActionListener.onPressKey(Constants.CODE_DELETE, - 0 /* repeatCount */, true /* isSinglePointer */); - v.setPressed(true /* pressed */); - } - - private void onTouchUp(final View v) { - mKeyboardActionListener.onCodeInput(Constants.CODE_DELETE, - NOT_A_COORDINATE, NOT_A_COORDINATE, false /* isKeyRepeat */); - mKeyboardActionListener.onReleaseKey(Constants.CODE_DELETE, false /* withSliding */); - v.setPressed(false /* pressed */); - } - - private void onTouchCanceled(final View v) { - v.setBackgroundColor(Color.TRANSPARENT); - } - } -}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java deleted file mode 100644 index c76a9aca4..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.graphics.Canvas; -import android.view.View; - -import com.android.inputmethod.keyboard.MainKeyboardView; -import com.android.inputmethod.keyboard.PointerTracker; - -import javax.annotation.Nonnull; - -/** - * Abstract base class for previews that are drawn on DrawingPreviewPlacerView, e.g., - * GestureFloatingTextDrawingPreview, GestureTrailsDrawingPreview, and - * SlidingKeyInputDrawingPreview. - */ -public abstract class AbstractDrawingPreview { - private View mDrawingView; - private boolean mPreviewEnabled; - private boolean mHasValidGeometry; - - public void setDrawingView(@Nonnull final DrawingPreviewPlacerView drawingView) { - mDrawingView = drawingView; - drawingView.addPreview(this); - } - - protected void invalidateDrawingView() { - if (mDrawingView != null) { - mDrawingView.invalidate(); - } - } - - protected final boolean isPreviewEnabled() { - return mPreviewEnabled && mHasValidGeometry; - } - - public final void setPreviewEnabled(final boolean enabled) { - mPreviewEnabled = enabled; - } - - /** - * Set {@link MainKeyboardView} geometry and position in the window of input method. - * The class that is overriding this method must call this super implementation. - * - * @param originCoords the top-left coordinates of the {@link MainKeyboardView} in - * the input method window coordinate-system. This is unused but has a point in an - * extended class, such as {@link GestureTrailsDrawingPreview}. - * @param width the width of {@link MainKeyboardView}. - * @param height the height of {@link MainKeyboardView}. - */ - public void setKeyboardViewGeometry(@Nonnull final int[] originCoords, final int width, - final int height) { - mHasValidGeometry = (width > 0 && height > 0); - } - - public abstract void onDeallocateMemory(); - - /** - * Draws the preview - * @param canvas The canvas where the preview is drawn. - */ - public abstract void drawPreview(@Nonnull final Canvas canvas); - - /** - * Set the position of the preview. - * @param tracker The new location of the preview is based on the points in PointerTracker. - */ - public abstract void setPreviewPosition(@Nonnull final PointerTracker tracker); -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java b/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java deleted file mode 100644 index 33f6b4965..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard.internal; - -import android.util.Log; - -public final class AlphabetShiftState { - private static final String TAG = AlphabetShiftState.class.getSimpleName(); - private static final boolean DEBUG = false; - - private static final int UNSHIFTED = 0; - private static final int MANUAL_SHIFTED = 1; - private static final int MANUAL_SHIFTED_FROM_AUTO = 2; - private static final int AUTOMATIC_SHIFTED = 3; - private static final int SHIFT_LOCKED = 4; - private static final int SHIFT_LOCK_SHIFTED = 5; - - private int mState = UNSHIFTED; - - public void setShifted(boolean newShiftState) { - final int oldState = mState; - if (newShiftState) { - switch (oldState) { - case UNSHIFTED: - mState = MANUAL_SHIFTED; - break; - case AUTOMATIC_SHIFTED: - mState = MANUAL_SHIFTED_FROM_AUTO; - break; - case SHIFT_LOCKED: - mState = SHIFT_LOCK_SHIFTED; - break; - } - } else { - switch (oldState) { - case MANUAL_SHIFTED: - case MANUAL_SHIFTED_FROM_AUTO: - case AUTOMATIC_SHIFTED: - mState = UNSHIFTED; - break; - case SHIFT_LOCK_SHIFTED: - mState = SHIFT_LOCKED; - break; - } - } - if (DEBUG) - Log.d(TAG, "setShifted(" + newShiftState + "): " + toString(oldState) + " > " + this); - } - - public void setShiftLocked(boolean newShiftLockState) { - final int oldState = mState; - if (newShiftLockState) { - switch (oldState) { - case UNSHIFTED: - case MANUAL_SHIFTED: - case MANUAL_SHIFTED_FROM_AUTO: - case AUTOMATIC_SHIFTED: - mState = SHIFT_LOCKED; - break; - } - } else { - mState = UNSHIFTED; - } - if (DEBUG) - Log.d(TAG, "setShiftLocked(" + newShiftLockState + "): " + toString(oldState) - + " > " + this); - } - - public void setAutomaticShifted() { - final int oldState = mState; - mState = AUTOMATIC_SHIFTED; - if (DEBUG) - Log.d(TAG, "setAutomaticShifted: " + toString(oldState) + " > " + this); - } - - public boolean isShiftedOrShiftLocked() { - return mState != UNSHIFTED; - } - - public boolean isShiftLocked() { - return mState == SHIFT_LOCKED || mState == SHIFT_LOCK_SHIFTED; - } - - public boolean isShiftLockShifted() { - return mState == SHIFT_LOCK_SHIFTED; - } - - public boolean isAutomaticShifted() { - return mState == AUTOMATIC_SHIFTED; - } - - public boolean isManualShifted() { - return mState == MANUAL_SHIFTED || mState == MANUAL_SHIFTED_FROM_AUTO - || mState == SHIFT_LOCK_SHIFTED; - } - - public boolean isManualShiftedFromAutomaticShifted() { - return mState == MANUAL_SHIFTED_FROM_AUTO; - } - - @Override - public String toString() { - return toString(mState); - } - - private static String toString(int state) { - switch (state) { - case UNSHIFTED: return "UNSHIFTED"; - case MANUAL_SHIFTED: return "MANUAL_SHIFTED"; - case MANUAL_SHIFTED_FROM_AUTO: return "MANUAL_SHIFTED_FROM_AUTO"; - case AUTOMATIC_SHIFTED: return "AUTOMATIC_SHIFTED"; - case SHIFT_LOCKED: return "SHIFT_LOCKED"; - case SHIFT_LOCK_SHIFTED: return "SHIFT_LOCK_SHIFTED"; - default: return "UNKNOWN"; - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java b/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java deleted file mode 100644 index 77d0e7a90..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.InputPointers; - -/** - * This class arbitrates batch input. - * An instance of this class holds a {@link GestureStrokeRecognitionPoints}. - * And it arbitrates multiple strokes gestured by multiple fingers and aggregates those gesture - * points into one batch input. - */ -public class BatchInputArbiter { - public interface BatchInputArbiterListener { - public void onStartBatchInput(); - public void onUpdateBatchInput( - final InputPointers aggregatedPointers, final long moveEventTime); - public void onStartUpdateBatchInputTimer(); - public void onEndBatchInput(final InputPointers aggregatedPointers, final long upEventTime); - } - - // The starting time of the first stroke of a gesture input. - private static long sGestureFirstDownTime; - // The {@link InputPointers} that includes all events of a gesture input. - private static final InputPointers sAggregatedPointers = new InputPointers( - Constants.DEFAULT_GESTURE_POINTS_CAPACITY); - private static int sLastRecognitionPointSize = 0; // synchronized using sAggregatedPointers - private static long sLastRecognitionTime = 0; // synchronized using sAggregatedPointers - - private final GestureStrokeRecognitionPoints mRecognitionPoints; - - public BatchInputArbiter(final int pointerId, final GestureStrokeRecognitionParams params) { - mRecognitionPoints = new GestureStrokeRecognitionPoints(pointerId, params); - } - - public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { - mRecognitionPoints.setKeyboardGeometry(keyWidth, keyboardHeight); - } - - /** - * Calculate elapsed time since the first gesture down. - * @param eventTime the time of this event. - * @return the elapsed time in millisecond from the first gesture down. - */ - public int getElapsedTimeSinceFirstDown(final long eventTime) { - return (int)(eventTime - sGestureFirstDownTime); - } - - /** - * Add a down event point. - * @param x the x-coordinate of this down event. - * @param y the y-coordinate of this down event. - * @param downEventTime the time of this down event. - * @param lastLetterTypingTime the last typing input time. - * @param activePointerCount the number of active pointers when this pointer down event occurs. - */ - public void addDownEventPoint(final int x, final int y, final long downEventTime, - final long lastLetterTypingTime, final int activePointerCount) { - if (activePointerCount == 1) { - sGestureFirstDownTime = downEventTime; - } - final int elapsedTimeSinceFirstDown = getElapsedTimeSinceFirstDown(downEventTime); - final int elapsedTimeSinceLastTyping = (int)(downEventTime - lastLetterTypingTime); - mRecognitionPoints.addDownEventPoint( - x, y, elapsedTimeSinceFirstDown, elapsedTimeSinceLastTyping); - } - - /** - * Add a move event point. - * @param x the x-coordinate of this move event. - * @param y the y-coordinate of this move event. - * @param moveEventTime the time of this move event. - * @param isMajorEvent false if this is a historical move event. - * @param listener {@link BatchInputArbiterListener#onStartUpdateBatchInputTimer()} of this - * <code>listener</code> may be called if enough move points have been added. - * @return true if this move event occurs on the valid gesture area. - */ - public boolean addMoveEventPoint(final int x, final int y, final long moveEventTime, - final boolean isMajorEvent, final BatchInputArbiterListener listener) { - final int beforeLength = mRecognitionPoints.getLength(); - final boolean onValidArea = mRecognitionPoints.addEventPoint( - x, y, getElapsedTimeSinceFirstDown(moveEventTime), isMajorEvent); - if (mRecognitionPoints.getLength() > beforeLength) { - listener.onStartUpdateBatchInputTimer(); - } - return onValidArea; - } - - /** - * Determine whether the batch input has started or not. - * @param listener {@link BatchInputArbiterListener#onStartBatchInput()} of this - * <code>listener</code> will be called when the batch input has started successfully. - * @return true if the batch input has started successfully. - */ - public boolean mayStartBatchInput(final BatchInputArbiterListener listener) { - if (!mRecognitionPoints.isStartOfAGesture()) { - return false; - } - synchronized (sAggregatedPointers) { - sAggregatedPointers.reset(); - sLastRecognitionPointSize = 0; - sLastRecognitionTime = 0; - listener.onStartBatchInput(); - } - return true; - } - - /** - * Add synthetic move event point. After adding the point, - * {@link #updateBatchInput(long,BatchInputArbiterListener)} will be called internally. - * @param syntheticMoveEventTime the synthetic move event time. - * @param listener the listener to be passed to - * {@link #updateBatchInput(long,BatchInputArbiterListener)}. - */ - public void updateBatchInputByTimer(final long syntheticMoveEventTime, - final BatchInputArbiterListener listener) { - mRecognitionPoints.duplicateLastPointWith( - getElapsedTimeSinceFirstDown(syntheticMoveEventTime)); - updateBatchInput(syntheticMoveEventTime, listener); - } - - /** - * Determine whether we have enough gesture points to lookup dictionary. - * @param moveEventTime the time of this move event. - * @param listener {@link BatchInputArbiterListener#onUpdateBatchInput(InputPointers,long)} of - * this <code>listener</code> will be called when enough event points we have. Also - * {@link BatchInputArbiterListener#onStartUpdateBatchInputTimer()} will be called to have - * possible future synthetic move event. - */ - public void updateBatchInput(final long moveEventTime, - final BatchInputArbiterListener listener) { - synchronized (sAggregatedPointers) { - mRecognitionPoints.appendIncrementalBatchPoints(sAggregatedPointers); - final int size = sAggregatedPointers.getPointerSize(); - if (size > sLastRecognitionPointSize && mRecognitionPoints.hasRecognitionTimePast( - moveEventTime, sLastRecognitionTime)) { - listener.onUpdateBatchInput(sAggregatedPointers, moveEventTime); - listener.onStartUpdateBatchInputTimer(); - // The listener may change the size of the pointers (when auto-committing - // for example), so we need to get the size from the pointers again. - sLastRecognitionPointSize = sAggregatedPointers.getPointerSize(); - sLastRecognitionTime = moveEventTime; - } - } - } - - /** - * Determine whether the batch input has ended successfully or continues. - * @param upEventTime the time of this up event. - * @param activePointerCount the number of active pointers when this pointer up event occurs. - * @param listener {@link BatchInputArbiterListener#onEndBatchInput(InputPointers,long)} of this - * <code>listener</code> will be called when the batch input has started successfully. - * @return true if the batch input has ended successfully. - */ - public boolean mayEndBatchInput(final long upEventTime, final int activePointerCount, - final BatchInputArbiterListener listener) { - synchronized (sAggregatedPointers) { - mRecognitionPoints.appendAllBatchPoints(sAggregatedPointers); - if (activePointerCount == 1) { - listener.onEndBatchInput(sAggregatedPointers, upEventTime); - return true; - } - } - return false; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java b/java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java deleted file mode 100644 index 4b355a4ab..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/BogusMoveEventDetector.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.Resources; -import android.util.DisplayMetrics; -import android.util.Log; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.define.DebugFlags; - -// This hack is applied to certain classes of tablets. -public final class BogusMoveEventDetector { - private static final String TAG = BogusMoveEventDetector.class.getSimpleName(); - private static final boolean DEBUG_MODE = DebugFlags.DEBUG_ENABLED; - - // Move these thresholds to resource. - // These thresholds' unit is a diagonal length of a key. - private static final float BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD = 0.53f; - private static final float BOGUS_MOVE_RADIUS_THRESHOLD = 1.14f; - - private static boolean sNeedsProximateBogusDownMoveUpEventHack; - - public static void init(final Resources res) { - // The proximate bogus down move up event hack is needed for a device such like, - // 1) is large tablet, or 2) is small tablet and the screen density is less than hdpi. - // Though it seems odd to use screen density as criteria of the quality of the touch - // screen, the small table that has a less density screen than hdpi most likely has been - // made with the touch screen that needs the hack. - final int screenMetrics = res.getInteger(R.integer.config_screen_metrics); - final boolean isLargeTablet = (screenMetrics == Constants.SCREEN_METRICS_LARGE_TABLET); - final boolean isSmallTablet = (screenMetrics == Constants.SCREEN_METRICS_SMALL_TABLET); - final int densityDpi = res.getDisplayMetrics().densityDpi; - final boolean hasLowDensityScreen = (densityDpi < DisplayMetrics.DENSITY_HIGH); - final boolean needsTheHack = isLargeTablet || (isSmallTablet && hasLowDensityScreen); - if (DEBUG_MODE) { - final int sw = res.getConfiguration().smallestScreenWidthDp; - Log.d(TAG, "needsProximateBogusDownMoveUpEventHack=" + needsTheHack - + " smallestScreenWidthDp=" + sw + " densityDpi=" + densityDpi - + " screenMetrics=" + screenMetrics); - } - sNeedsProximateBogusDownMoveUpEventHack = needsTheHack; - } - - private int mAccumulatedDistanceThreshold; - private int mRadiusThreshold; - - // Accumulated distance from actual and artificial down keys. - /* package */ int mAccumulatedDistanceFromDownKey; - private int mActualDownX; - private int mActualDownY; - - public void setKeyboardGeometry(final int keyWidth, final int keyHeight) { - final float keyDiagonal = (float)Math.hypot(keyWidth, keyHeight); - mAccumulatedDistanceThreshold = (int)( - keyDiagonal * BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD); - mRadiusThreshold = (int)(keyDiagonal * BOGUS_MOVE_RADIUS_THRESHOLD); - } - - public void onActualDownEvent(final int x, final int y) { - mActualDownX = x; - mActualDownY = y; - } - - public void onDownKey() { - mAccumulatedDistanceFromDownKey = 0; - } - - public void onMoveKey(final int distance) { - mAccumulatedDistanceFromDownKey += distance; - } - - public boolean hasTraveledLongDistance(final int x, final int y) { - if (!sNeedsProximateBogusDownMoveUpEventHack) { - return false; - } - final int dx = Math.abs(x - mActualDownX); - final int dy = Math.abs(y - mActualDownY); - // A bogus move event should be a horizontal movement. A vertical movement might be - // a sloppy typing and should be ignored. - return dx >= dy && mAccumulatedDistanceFromDownKey >= mAccumulatedDistanceThreshold; - } - - public int getAccumulatedDistanceFromDownKey() { - return mAccumulatedDistanceFromDownKey; - } - - public int getDistanceFromDownEvent(final int x, final int y) { - return getDistance(x, y, mActualDownX, mActualDownY); - } - - private static int getDistance(final int x1, final int y1, final int x2, final int y2) { - return (int)Math.hypot(x1 - x2, y1 - y2); - } - - public boolean isCloseToActualDownEvent(final int x, final int y) { - return sNeedsProximateBogusDownMoveUpEventHack - && getDistanceFromDownEvent(x, y) < mRadiusThreshold; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java deleted file mode 100644 index 2e2ed52dd..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; - -import android.text.TextUtils; - -/** - * The string parser of codesArray specification for <GridRows />. The attribute codesArray is an - * array of string. - * Each element of the array defines a key label by specifying a code point as a hexadecimal string. - * A key label may consist of multiple code points separated by comma. - * Each element of the array optionally can have an output text definition after vertical bar - * marker. An output text may consist of multiple code points separated by comma. - * The format of the codesArray element should be: - * <pre> - * label1[,label2]*(|outputText1[,outputText2]*(|minSupportSdkVersion)?)? - * </pre> - */ -// TODO: Write unit tests for this class. -public final class CodesArrayParser { - // Constants for parsing. - private static final char COMMA = Constants.CODE_COMMA; - private static final String COMMA_REGEX = StringUtils.newSingleCodePointString(COMMA); - private static final String VERTICAL_BAR_REGEX = // "\\|" - new String(new char[] { Constants.CODE_BACKSLASH, Constants.CODE_VERTICAL_BAR }); - private static final int BASE_HEX = 16; - - private CodesArrayParser() { - // This utility class is not publicly instantiable. - } - - private static String getLabelSpec(final String codesArraySpec) { - final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1); - if (strs.length <= 1) { - return codesArraySpec; - } - return strs[0]; - } - - public static String parseLabel(final String codesArraySpec) { - final String labelSpec = getLabelSpec(codesArraySpec); - final StringBuilder sb = new StringBuilder(); - for (final String codeInHex : labelSpec.split(COMMA_REGEX)) { - final int codePoint = Integer.parseInt(codeInHex, BASE_HEX); - sb.appendCodePoint(codePoint); - } - return sb.toString(); - } - - private static String getCodeSpec(final String codesArraySpec) { - final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1); - if (strs.length <= 1) { - return codesArraySpec; - } - return TextUtils.isEmpty(strs[1]) ? strs[0] : strs[1]; - } - - public static int getMinSupportSdkVersion(final String codesArraySpec) { - final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1); - if (strs.length <= 2) { - return 0; - } - try { - return Integer.parseInt(strs[2]); - } catch (NumberFormatException e) { - return 0; - } - } - - public static int parseCode(final String codesArraySpec) { - final String codeSpec = getCodeSpec(codesArraySpec); - if (codeSpec.indexOf(COMMA) < 0) { - return Integer.parseInt(codeSpec, BASE_HEX); - } - return Constants.CODE_OUTPUT_TEXT; - } - - public static String parseOutputText(final String codesArraySpec) { - final String codeSpec = getCodeSpec(codesArraySpec); - if (codeSpec.indexOf(COMMA) < 0) { - return null; - } - final StringBuilder sb = new StringBuilder(); - for (final String codeInHex : codeSpec.split(COMMA_REGEX)) { - final int codePoint = Integer.parseInt(codeInHex, BASE_HEX); - sb.appendCodePoint(codePoint); - } - return sb.toString(); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java deleted file mode 100644 index 9c0d7436b..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.util.AttributeSet; -import android.widget.RelativeLayout; - -import com.android.inputmethod.latin.common.CoordinateUtils; - -import java.util.ArrayList; - -public final class DrawingPreviewPlacerView extends RelativeLayout { - private final int[] mKeyboardViewOrigin = CoordinateUtils.newInstance(); - - private final ArrayList<AbstractDrawingPreview> mPreviews = new ArrayList<>(); - - public DrawingPreviewPlacerView(final Context context, final AttributeSet attrs) { - super(context, attrs); - setWillNotDraw(false); - } - - public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { - if (!enabled) return; - final Paint layerPaint = new Paint(); - layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); - setLayerType(LAYER_TYPE_HARDWARE, layerPaint); - } - - public void addPreview(final AbstractDrawingPreview preview) { - if (mPreviews.indexOf(preview) < 0) { - mPreviews.add(preview); - } - } - - public void setKeyboardViewGeometry(final int[] originCoords, final int width, - final int height) { - CoordinateUtils.copy(mKeyboardViewOrigin, originCoords); - final int count = mPreviews.size(); - for (int i = 0; i < count; i++) { - mPreviews.get(i).setKeyboardViewGeometry(originCoords, width, height); - } - } - - public void deallocateMemory() { - final int count = mPreviews.size(); - for (int i = 0; i < count; i++) { - mPreviews.get(i).onDeallocateMemory(); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - deallocateMemory(); - } - - @Override - public void onDraw(final Canvas canvas) { - super.onDraw(canvas); - final int originX = CoordinateUtils.x(mKeyboardViewOrigin); - final int originY = CoordinateUtils.y(mKeyboardViewOrigin); - canvas.translate(originX, originY); - final int count = mPreviews.size(); - for (int i = 0; i < count; i++) { - mPreviews.get(i).drawPreview(canvas); - } - canvas.translate(-originX, -originY); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java deleted file mode 100644 index 06bdfc41b..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.keyboard.internal; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.MoreKeysPanel; -import com.android.inputmethod.keyboard.PointerTracker; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public interface DrawingProxy { - /** - * Called when a key is being pressed. - * @param key the {@link Key} that is being pressed. - * @param withPreview true if key popup preview should be displayed. - */ - public void onKeyPressed(@Nonnull Key key, boolean withPreview); - - /** - * Called when a key is being released. - * @param key the {@link Key} that is being released. - * @param withAnimation when true, key popup preview should be dismissed with animation. - */ - public void onKeyReleased(@Nonnull Key key, boolean withAnimation); - - /** - * Start showing more keys keyboard of a key that is being long pressed. - * @param key the {@link Key} that is being long pressed and showing more keys keyboard. - * @param tracker the {@link PointerTracker} that detects this long pressing. - * @return {@link MoreKeysPanel} that is being shown. null if there is no need to show more keys - * keyboard. - */ - @Nullable - public MoreKeysPanel showMoreKeysKeyboard(@Nonnull Key key, @Nonnull PointerTracker tracker); - - /** - * Start a while-typing-animation. - * @param fadeInOrOut {@link #FADE_IN} starts while-typing-fade-in animation. - * {@link #FADE_OUT} starts while-typing-fade-out animation. - */ - public void startWhileTypingAnimation(int fadeInOrOut); - public static final int FADE_IN = 0; - public static final int FADE_OUT = 1; - - /** - * Show sliding-key input preview. - * @param tracker the {@link PointerTracker} that is currently doing the sliding-key input. - * null to dismiss the sliding-key input preview. - */ - public void showSlidingKeyInputPreview(@Nullable PointerTracker tracker); - - /** - * Show gesture trails. - * @param tracker the {@link PointerTracker} whose gesture trail will be shown. - * @param showsFloatingPreviewText when true, a gesture floating preview text will be shown - * with this <code>tracker</code>'s trail. - */ - public void showGestureTrail(@Nonnull PointerTracker tracker, boolean showsFloatingPreviewText); - - /** - * Dismiss a gesture floating preview text without delay. - */ - public void dismissGestureFloatingPreviewTextWithoutDelay(); -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureEnabler.java b/java/src/com/android/inputmethod/keyboard/internal/GestureEnabler.java deleted file mode 100644 index 7d14ae924..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureEnabler.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import com.android.inputmethod.accessibility.AccessibilityUtils; - -public final class GestureEnabler { - /** True if we should handle gesture events. */ - private boolean mShouldHandleGesture; - private boolean mMainDictionaryAvailable; - private boolean mGestureHandlingEnabledByInputField; - private boolean mGestureHandlingEnabledByUser; - - private void updateGestureHandlingMode() { - mShouldHandleGesture = mMainDictionaryAvailable - && mGestureHandlingEnabledByInputField - && mGestureHandlingEnabledByUser - && !AccessibilityUtils.getInstance().isTouchExplorationEnabled(); - } - - // Note that this method is called from a non-UI thread. - public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { - mMainDictionaryAvailable = mainDictionaryAvailable; - updateGestureHandlingMode(); - } - - public void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) { - mGestureHandlingEnabledByUser = gestureHandlingEnabledByUser; - updateGestureHandlingMode(); - } - - public void setPasswordMode(final boolean passwordMode) { - mGestureHandlingEnabledByInputField = !passwordMode; - updateGestureHandlingMode(); - } - - public boolean shouldHandleGesture() { - return mShouldHandleGesture; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java deleted file mode 100644 index cb50b76ae..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Rect; -import android.graphics.RectF; -import android.text.TextUtils; - -import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.common.CoordinateUtils; - -import javax.annotation.Nonnull; - -/** - * The class for single gesture preview text. The class for multiple gesture preview text will be - * derived from it. - * - * @attr ref android.R.styleable#KeyboardView_gestureFloatingPreviewTextSize - * @attr ref android.R.styleable#KeyboardView_gestureFloatingPreviewTextColor - * @attr ref android.R.styleable#KeyboardView_gestureFloatingPreviewTextOffset - * @attr ref android.R.styleable#KeyboardView_gestureFloatingPreviewColor - * @attr ref android.R.styleable#KeyboardView_gestureFloatingPreviewHorizontalPadding - * @attr ref android.R.styleable#KeyboardView_gestureFloatingPreviewVerticalPadding - * @attr ref android.R.styleable#KeyboardView_gestureFloatingPreviewRoundRadius - */ -public class GestureFloatingTextDrawingPreview extends AbstractDrawingPreview { - protected static final class GesturePreviewTextParams { - public final int mGesturePreviewTextOffset; - public final int mGesturePreviewTextHeight; - public final float mGesturePreviewHorizontalPadding; - public final float mGesturePreviewVerticalPadding; - public final float mGesturePreviewRoundRadius; - public final int mDisplayWidth; - - private final int mGesturePreviewTextSize; - private final int mGesturePreviewTextColor; - private final int mGesturePreviewColor; - private final Paint mPaint = new Paint(); - - private static final char[] TEXT_HEIGHT_REFERENCE_CHAR = { 'M' }; - - public GesturePreviewTextParams(final TypedArray mainKeyboardViewAttr) { - mGesturePreviewTextSize = mainKeyboardViewAttr.getDimensionPixelSize( - R.styleable.MainKeyboardView_gestureFloatingPreviewTextSize, 0); - mGesturePreviewTextColor = mainKeyboardViewAttr.getColor( - R.styleable.MainKeyboardView_gestureFloatingPreviewTextColor, 0); - mGesturePreviewTextOffset = mainKeyboardViewAttr.getDimensionPixelOffset( - R.styleable.MainKeyboardView_gestureFloatingPreviewTextOffset, 0); - mGesturePreviewColor = mainKeyboardViewAttr.getColor( - R.styleable.MainKeyboardView_gestureFloatingPreviewColor, 0); - mGesturePreviewHorizontalPadding = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gestureFloatingPreviewHorizontalPadding, 0.0f); - mGesturePreviewVerticalPadding = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gestureFloatingPreviewVerticalPadding, 0.0f); - mGesturePreviewRoundRadius = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gestureFloatingPreviewRoundRadius, 0.0f); - mDisplayWidth = mainKeyboardViewAttr.getResources().getDisplayMetrics().widthPixels; - - final Paint textPaint = getTextPaint(); - final Rect textRect = new Rect(); - textPaint.getTextBounds(TEXT_HEIGHT_REFERENCE_CHAR, 0, 1, textRect); - mGesturePreviewTextHeight = textRect.height(); - } - - public Paint getTextPaint() { - mPaint.setAntiAlias(true); - mPaint.setTextAlign(Align.CENTER); - mPaint.setTextSize(mGesturePreviewTextSize); - mPaint.setColor(mGesturePreviewTextColor); - return mPaint; - } - - public Paint getBackgroundPaint() { - mPaint.setColor(mGesturePreviewColor); - return mPaint; - } - } - - private final GesturePreviewTextParams mParams; - private final RectF mGesturePreviewRectangle = new RectF(); - private int mPreviewTextX; - private int mPreviewTextY; - private SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance(); - private final int[] mLastPointerCoords = CoordinateUtils.newInstance(); - - public GestureFloatingTextDrawingPreview(final TypedArray mainKeyboardViewAttr) { - mParams = new GesturePreviewTextParams(mainKeyboardViewAttr); - } - - @Override - public void onDeallocateMemory() { - // Nothing to do here. - } - - public void dismissGestureFloatingPreviewText() { - setSuggetedWords(SuggestedWords.getEmptyInstance()); - } - - public void setSuggetedWords(@Nonnull final SuggestedWords suggestedWords) { - if (!isPreviewEnabled()) { - return; - } - mSuggestedWords = suggestedWords; - updatePreviewPosition(); - } - - @Override - public void setPreviewPosition(final PointerTracker tracker) { - if (!isPreviewEnabled()) { - return; - } - tracker.getLastCoordinates(mLastPointerCoords); - updatePreviewPosition(); - } - - /** - * Draws gesture preview text - * @param canvas The canvas where preview text is drawn. - */ - @Override - public void drawPreview(final Canvas canvas) { - if (!isPreviewEnabled() || mSuggestedWords.isEmpty() - || TextUtils.isEmpty(mSuggestedWords.getWord(0))) { - return; - } - final float round = mParams.mGesturePreviewRoundRadius; - canvas.drawRoundRect( - mGesturePreviewRectangle, round, round, mParams.getBackgroundPaint()); - final String text = mSuggestedWords.getWord(0); - canvas.drawText(text, mPreviewTextX, mPreviewTextY, mParams.getTextPaint()); - } - - /** - * Updates gesture preview text position based on mLastPointerCoords. - */ - protected void updatePreviewPosition() { - if (mSuggestedWords.isEmpty() || TextUtils.isEmpty(mSuggestedWords.getWord(0))) { - invalidateDrawingView(); - return; - } - final String text = mSuggestedWords.getWord(0); - - final RectF rectangle = mGesturePreviewRectangle; - - final int textHeight = mParams.mGesturePreviewTextHeight; - final float textWidth = mParams.getTextPaint().measureText(text); - final float hPad = mParams.mGesturePreviewHorizontalPadding; - final float vPad = mParams.mGesturePreviewVerticalPadding; - final float rectWidth = textWidth + hPad * 2.0f; - final float rectHeight = textHeight + vPad * 2.0f; - - final float rectX = Math.min( - Math.max(CoordinateUtils.x(mLastPointerCoords) - rectWidth / 2.0f, 0.0f), - mParams.mDisplayWidth - rectWidth); - final float rectY = CoordinateUtils.y(mLastPointerCoords) - - mParams.mGesturePreviewTextOffset - rectHeight; - rectangle.set(rectX, rectY, rectX + rectWidth, rectY + rectHeight); - - mPreviewTextX = (int)(rectX + hPad + textWidth / 2.0f); - mPreviewTextY = (int)(rectY + vPad) + textHeight; - // TODO: Should narrow the invalidate region. - invalidateDrawingView(); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingParams.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingParams.java deleted file mode 100644 index eeba67892..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingParams.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.TypedArray; - -import com.android.inputmethod.latin.R; - -/** - * This class holds parameters to control how a gesture stroke is sampled and drawn on the screen. - * - * @attr ref android.R.styleable#MainKeyboardView_gestureTrailMinSamplingDistance - * @attr ref android.R.styleable#MainKeyboardView_gestureTrailMaxInterpolationAngularThreshold - * @attr ref android.R.styleable#MainKeyboardView_gestureTrailMaxInterpolationDistanceThreshold - * @attr ref android.R.styleable#MainKeyboardView_gestureTrailMaxInterpolationSegments - */ -public final class GestureStrokeDrawingParams { - public final double mMinSamplingDistance; // in pixel - public final double mMaxInterpolationAngularThreshold; // in radian - public final double mMaxInterpolationDistanceThreshold; // in pixel - public final int mMaxInterpolationSegments; - - private static final float DEFAULT_MIN_SAMPLING_DISTANCE = 0.0f; // dp - private static final int DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD = 15; // in degree - private static final float DEFAULT_MAX_INTERPOLATION_DISTANCE_THRESHOLD = 0.0f; // dp - private static final int DEFAULT_MAX_INTERPOLATION_SEGMENTS = 4; - - public GestureStrokeDrawingParams(final TypedArray mainKeyboardViewAttr) { - mMinSamplingDistance = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gestureTrailMinSamplingDistance, - DEFAULT_MIN_SAMPLING_DISTANCE); - final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable - .MainKeyboardView_gestureTrailMaxInterpolationAngularThreshold, 0); - mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0) - ? Math.toRadians(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD) - : Math.toRadians(interpolationAngularDegree); - mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable - .MainKeyboardView_gestureTrailMaxInterpolationDistanceThreshold, - DEFAULT_MAX_INTERPOLATION_DISTANCE_THRESHOLD); - mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger( - R.styleable.MainKeyboardView_gestureTrailMaxInterpolationSegments, - DEFAULT_MAX_INTERPOLATION_SEGMENTS); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java deleted file mode 100644 index 07ef00924..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import com.android.inputmethod.latin.common.ResizableIntArray; - -/** - * This class holds drawing points to represent a gesture stroke on the screen. - */ -public final class GestureStrokeDrawingPoints { - public static final int PREVIEW_CAPACITY = 256; - - private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY); - private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); - private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); - - private final GestureStrokeDrawingParams mDrawingParams; - - private int mStrokeId; - private int mLastPreviewSize; - private final HermiteInterpolator mInterpolator = new HermiteInterpolator(); - private int mLastInterpolatedPreviewIndex; - - private int mLastX; - private int mLastY; - private double mDistanceFromLastSample; - - public GestureStrokeDrawingPoints(final GestureStrokeDrawingParams drawingParams) { - mDrawingParams = drawingParams; - } - - private void reset() { - mStrokeId++; - mLastPreviewSize = 0; - mLastInterpolatedPreviewIndex = 0; - mPreviewEventTimes.setLength(0); - mPreviewXCoordinates.setLength(0); - mPreviewYCoordinates.setLength(0); - } - - public int getGestureStrokeId() { - return mStrokeId; - } - - public void onDownEvent(final int x, final int y, final int elapsedTimeSinceFirstDown) { - reset(); - onMoveEvent(x, y, elapsedTimeSinceFirstDown); - } - - private boolean needsSampling(final int x, final int y) { - mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY); - mLastX = x; - mLastY = y; - final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0); - if (mDistanceFromLastSample >= mDrawingParams.mMinSamplingDistance || isDownEvent) { - mDistanceFromLastSample = 0.0d; - return true; - } - return false; - } - - public void onMoveEvent(final int x, final int y, final int elapsedTimeSinceFirstDown) { - if (needsSampling(x, y)) { - mPreviewEventTimes.add(elapsedTimeSinceFirstDown); - mPreviewXCoordinates.add(x); - mPreviewYCoordinates.add(y); - } - } - - /** - * Append sampled preview points. - * - * @param eventTimes the event time array of gesture trail to be drawn. - * @param xCoords the x-coordinates array of gesture trail to be drawn. - * @param yCoords the y-coordinates array of gesture trail to be drawn. - * @param types the point types array of gesture trail. This is valid only when - * {@link GestureTrailDrawingPoints#DEBUG_SHOW_POINTS} is true. - */ - public void appendPreviewStroke(final ResizableIntArray eventTimes, - final ResizableIntArray xCoords, final ResizableIntArray yCoords, - final ResizableIntArray types) { - final int length = mPreviewEventTimes.getLength() - mLastPreviewSize; - if (length <= 0) { - return; - } - eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length); - xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length); - yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length); - if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) { - types.fill(GestureTrailDrawingPoints.POINT_TYPE_SAMPLED, types.getLength(), length); - } - mLastPreviewSize = mPreviewEventTimes.getLength(); - } - - /** - * Calculate interpolated points between the last interpolated point and the end of the trail. - * And return the start index of the last interpolated segment of input arrays because it - * may need to recalculate the interpolated points in the segment if further segments are - * added to this stroke. - * - * @param lastInterpolatedIndex the start index of the last interpolated segment of - * <code>eventTimes</code>, <code>xCoords</code>, and <code>yCoords</code>. - * @param eventTimes the event time array of gesture trail to be drawn. - * @param xCoords the x-coordinates array of gesture trail to be drawn. - * @param yCoords the y-coordinates array of gesture trail to be drawn. - * @param types the point types array of gesture trail. This is valid only when - * {@link GestureTrailDrawingPoints#DEBUG_SHOW_POINTS} is true. - * @return the start index of the last interpolated segment of input arrays. - */ - public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, - final ResizableIntArray eventTimes, final ResizableIntArray xCoords, - final ResizableIntArray yCoords, final ResizableIntArray types) { - final int size = mPreviewEventTimes.getLength(); - final int[] pt = mPreviewEventTimes.getPrimitiveArray(); - final int[] px = mPreviewXCoordinates.getPrimitiveArray(); - final int[] py = mPreviewYCoordinates.getPrimitiveArray(); - mInterpolator.reset(px, py, 0, size); - // The last segment of gesture stroke needs to be interpolated again because the slope of - // the tangent at the last point isn't determined. - int lastInterpolatedDrawIndex = lastInterpolatedIndex; - int d1 = lastInterpolatedIndex; - for (int p2 = mLastInterpolatedPreviewIndex + 1; p2 < size; p2++) { - final int p1 = p2 - 1; - final int p0 = p1 - 1; - final int p3 = p2 + 1; - mLastInterpolatedPreviewIndex = p1; - lastInterpolatedDrawIndex = d1; - mInterpolator.setInterval(p0, p1, p2, p3); - final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X); - final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X); - final double deltaAngle = Math.abs(angularDiff(m2, m1)); - final int segmentsByAngle = (int)Math.ceil( - deltaAngle / mDrawingParams.mMaxInterpolationAngularThreshold); - final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X, - mInterpolator.mP1Y - mInterpolator.mP2Y); - final int segmentsByDistance = (int)Math.ceil(deltaDistance - / mDrawingParams.mMaxInterpolationDistanceThreshold); - final int segments = Math.min(mDrawingParams.mMaxInterpolationSegments, - Math.max(segmentsByAngle, segmentsByDistance)); - final int t1 = eventTimes.get(d1); - final int dt = pt[p2] - pt[p1]; - d1++; - for (int i = 1; i < segments; i++) { - final float t = i / (float)segments; - mInterpolator.interpolate(t); - eventTimes.addAt(d1, (int)(dt * t) + t1); - xCoords.addAt(d1, (int)mInterpolator.mInterpolatedX); - yCoords.addAt(d1, (int)mInterpolator.mInterpolatedY); - if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) { - types.addAt(d1, GestureTrailDrawingPoints.POINT_TYPE_INTERPOLATED); - } - d1++; - } - eventTimes.addAt(d1, pt[p2]); - xCoords.addAt(d1, px[p2]); - yCoords.addAt(d1, py[p2]); - if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) { - types.addAt(d1, GestureTrailDrawingPoints.POINT_TYPE_SAMPLED); - } - } - return lastInterpolatedDrawIndex; - } - - private static final double TWO_PI = Math.PI * 2.0d; - - /** - * Calculate the angular of rotation from <code>a0</code> to <code>a1</code>. - * - * @param a1 the angular to which the rotation ends. - * @param a0 the angular from which the rotation starts. - * @return the angular rotation value from a0 to a1, normalized to [-PI, +PI]. - */ - private static double angularDiff(final double a1, final double a0) { - double deltaAngle = a1 - a0; - while (deltaAngle > Math.PI) { - deltaAngle -= TWO_PI; - } - while (deltaAngle < -Math.PI) { - deltaAngle += TWO_PI; - } - return deltaAngle; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionParams.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionParams.java deleted file mode 100644 index e98729d43..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionParams.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.TypedArray; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.ResourceUtils; - -/** - * This class holds parameters to control how a gesture stroke is sampled and recognized. - * This class also has parameters to distinguish gesture input events from fast typing events. - * - * @attr ref android.R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping - * @attr ref android.R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold - * @attr ref android.R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration - * @attr ref android.R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom - * @attr ref android.R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo - * @attr ref android.R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom - * @attr ref android.R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo - * @attr ref android.R.styleable#MainKeyboardView_gestureSamplingMinimumDistance - * @attr ref android.R.styleable#MainKeyboardView_gestureRecognitionMinimumTime - * @attr ref android.R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold - */ -public final class GestureStrokeRecognitionParams { - // Static threshold for gesture after fast typing - public final int mStaticTimeThresholdAfterFastTyping; // msec - // Static threshold for starting gesture detection - public final float mDetectFastMoveSpeedThreshold; // keyWidth/sec - // Dynamic threshold for gesture after fast typing - public final int mDynamicThresholdDecayDuration; // msec - // Time based threshold values - public final int mDynamicTimeThresholdFrom; // msec - public final int mDynamicTimeThresholdTo; // msec - // Distance based threshold values - public final float mDynamicDistanceThresholdFrom; // keyWidth - public final float mDynamicDistanceThresholdTo; // keyWidth - // Parameters for gesture sampling - public final float mSamplingMinimumDistance; // keyWidth - // Parameters for gesture recognition - public final int mRecognitionMinimumTime; // msec - public final float mRecognitionSpeedThreshold; // keyWidth/sec - - // Default GestureStrokeRecognitionPoints parameters. - public static final GestureStrokeRecognitionParams DEFAULT = - new GestureStrokeRecognitionParams(); - - private GestureStrokeRecognitionParams() { - // These parameter values are default and intended for testing. - mStaticTimeThresholdAfterFastTyping = 350; // msec - mDetectFastMoveSpeedThreshold = 1.5f; // keyWidth/sec - mDynamicThresholdDecayDuration = 450; // msec - mDynamicTimeThresholdFrom = 300; // msec - mDynamicTimeThresholdTo = 20; // msec - mDynamicDistanceThresholdFrom = 6.0f; // keyWidth - mDynamicDistanceThresholdTo = 0.35f; // keyWidth - // The following parameters' change will affect the result of regression test. - mSamplingMinimumDistance = 1.0f / 6.0f; // keyWidth - mRecognitionMinimumTime = 100; // msec - mRecognitionSpeedThreshold = 5.5f; // keyWidth/sec - } - - public GestureStrokeRecognitionParams(final TypedArray mainKeyboardViewAttr) { - mStaticTimeThresholdAfterFastTyping = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping, - DEFAULT.mStaticTimeThresholdAfterFastTyping); - mDetectFastMoveSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr, - R.styleable.MainKeyboardView_gestureDetectFastMoveSpeedThreshold, - DEFAULT.mDetectFastMoveSpeedThreshold); - mDynamicThresholdDecayDuration = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureDynamicThresholdDecayDuration, - DEFAULT.mDynamicThresholdDecayDuration); - mDynamicTimeThresholdFrom = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureDynamicTimeThresholdFrom, - DEFAULT.mDynamicTimeThresholdFrom); - mDynamicTimeThresholdTo = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureDynamicTimeThresholdTo, - DEFAULT.mDynamicTimeThresholdTo); - mDynamicDistanceThresholdFrom = ResourceUtils.getFraction(mainKeyboardViewAttr, - R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdFrom, - DEFAULT.mDynamicDistanceThresholdFrom); - mDynamicDistanceThresholdTo = ResourceUtils.getFraction(mainKeyboardViewAttr, - R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdTo, - DEFAULT.mDynamicDistanceThresholdTo); - mSamplingMinimumDistance = ResourceUtils.getFraction(mainKeyboardViewAttr, - R.styleable.MainKeyboardView_gestureSamplingMinimumDistance, - DEFAULT.mSamplingMinimumDistance); - mRecognitionMinimumTime = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureRecognitionMinimumTime, - DEFAULT.mRecognitionMinimumTime); - mRecognitionSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr, - R.styleable.MainKeyboardView_gestureRecognitionSpeedThreshold, - DEFAULT.mRecognitionSpeedThreshold); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java deleted file mode 100644 index 3e901114a..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.util.Log; - -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.InputPointers; -import com.android.inputmethod.latin.common.ResizableIntArray; - -/** - * This class holds event points to recognize a gesture stroke. - * TODO: Should be package private class. - */ -public final class GestureStrokeRecognitionPoints { - private static final String TAG = GestureStrokeRecognitionPoints.class.getSimpleName(); - private static final boolean DEBUG = false; - private static final boolean DEBUG_SPEED = false; - - // The height of extra area above the keyboard to draw gesture trails. - // Proportional to the keyboard height. - public static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f; - - private final int mPointerId; - private final ResizableIntArray mEventTimes = new ResizableIntArray( - Constants.DEFAULT_GESTURE_POINTS_CAPACITY); - private final ResizableIntArray mXCoordinates = new ResizableIntArray( - Constants.DEFAULT_GESTURE_POINTS_CAPACITY); - private final ResizableIntArray mYCoordinates = new ResizableIntArray( - Constants.DEFAULT_GESTURE_POINTS_CAPACITY); - - private final GestureStrokeRecognitionParams mRecognitionParams; - - private int mKeyWidth; // pixel - private int mMinYCoordinate; // pixel - private int mMaxYCoordinate; // pixel - // Static threshold for starting gesture detection - private int mDetectFastMoveSpeedThreshold; // pixel /sec - private int mDetectFastMoveTime; - private int mDetectFastMoveX; - private int mDetectFastMoveY; - // Dynamic threshold for gesture after fast typing - private boolean mAfterFastTyping; - private int mGestureDynamicDistanceThresholdFrom; // pixel - private int mGestureDynamicDistanceThresholdTo; // pixel - // Variables for gesture sampling - private int mGestureSamplingMinimumDistance; // pixel - private long mLastMajorEventTime; - private int mLastMajorEventX; - private int mLastMajorEventY; - // Variables for gesture recognition - private int mGestureRecognitionSpeedThreshold; // pixel / sec - private int mIncrementalRecognitionSize; - private int mLastIncrementalBatchSize; - - private static final int MSEC_PER_SEC = 1000; - - // TODO: Make this package private - public GestureStrokeRecognitionPoints(final int pointerId, - final GestureStrokeRecognitionParams recognitionParams) { - mPointerId = pointerId; - mRecognitionParams = recognitionParams; - } - - // TODO: Make this package private - public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { - mKeyWidth = keyWidth; - mMinYCoordinate = -(int)(keyboardHeight * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); - mMaxYCoordinate = keyboardHeight; - // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? - mDetectFastMoveSpeedThreshold = (int)( - keyWidth * mRecognitionParams.mDetectFastMoveSpeedThreshold); - mGestureDynamicDistanceThresholdFrom = (int)( - keyWidth * mRecognitionParams.mDynamicDistanceThresholdFrom); - mGestureDynamicDistanceThresholdTo = (int)( - keyWidth * mRecognitionParams.mDynamicDistanceThresholdTo); - mGestureSamplingMinimumDistance = (int)( - keyWidth * mRecognitionParams.mSamplingMinimumDistance); - mGestureRecognitionSpeedThreshold = (int)( - keyWidth * mRecognitionParams.mRecognitionSpeedThreshold); - if (DEBUG) { - Log.d(TAG, String.format( - "[%d] setKeyboardGeometry: keyWidth=%3d tT=%3d >> %3d tD=%3d >> %3d", - mPointerId, keyWidth, - mRecognitionParams.mDynamicTimeThresholdFrom, - mRecognitionParams.mDynamicTimeThresholdTo, - mGestureDynamicDistanceThresholdFrom, - mGestureDynamicDistanceThresholdTo)); - } - } - - // TODO: Make this package private - public int getLength() { - return mEventTimes.getLength(); - } - - // TODO: Make this package private - public void addDownEventPoint(final int x, final int y, final int elapsedTimeSinceFirstDown, - final int elapsedTimeSinceLastTyping) { - reset(); - if (elapsedTimeSinceLastTyping < mRecognitionParams.mStaticTimeThresholdAfterFastTyping) { - mAfterFastTyping = true; - } - if (DEBUG) { - Log.d(TAG, String.format("[%d] onDownEvent: dT=%3d%s", mPointerId, - elapsedTimeSinceLastTyping, mAfterFastTyping ? " afterFastTyping" : "")); - } - // Call {@link #addEventPoint(int,int,int,boolean)} to record this down event point as a - // major event point. - addEventPoint(x, y, elapsedTimeSinceFirstDown, true /* isMajorEvent */); - } - - private int getGestureDynamicDistanceThreshold(final int deltaTime) { - if (!mAfterFastTyping || deltaTime >= mRecognitionParams.mDynamicThresholdDecayDuration) { - return mGestureDynamicDistanceThresholdTo; - } - final int decayedThreshold = - (mGestureDynamicDistanceThresholdFrom - mGestureDynamicDistanceThresholdTo) - * deltaTime / mRecognitionParams.mDynamicThresholdDecayDuration; - return mGestureDynamicDistanceThresholdFrom - decayedThreshold; - } - - private int getGestureDynamicTimeThreshold(final int deltaTime) { - if (!mAfterFastTyping || deltaTime >= mRecognitionParams.mDynamicThresholdDecayDuration) { - return mRecognitionParams.mDynamicTimeThresholdTo; - } - final int decayedThreshold = - (mRecognitionParams.mDynamicTimeThresholdFrom - - mRecognitionParams.mDynamicTimeThresholdTo) - * deltaTime / mRecognitionParams.mDynamicThresholdDecayDuration; - return mRecognitionParams.mDynamicTimeThresholdFrom - decayedThreshold; - } - - // TODO: Make this package private - public final boolean isStartOfAGesture() { - if (!hasDetectedFastMove()) { - return false; - } - final int size = getLength(); - if (size <= 0) { - return false; - } - final int lastIndex = size - 1; - final int deltaTime = mEventTimes.get(lastIndex) - mDetectFastMoveTime; - if (deltaTime < 0) { - return false; - } - final int deltaDistance = getDistance( - mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex), - mDetectFastMoveX, mDetectFastMoveY); - final int distanceThreshold = getGestureDynamicDistanceThreshold(deltaTime); - final int timeThreshold = getGestureDynamicTimeThreshold(deltaTime); - final boolean isStartOfAGesture = deltaTime >= timeThreshold - && deltaDistance >= distanceThreshold; - if (DEBUG) { - Log.d(TAG, String.format("[%d] isStartOfAGesture: dT=%3d tT=%3d dD=%3d tD=%3d%s%s", - mPointerId, deltaTime, timeThreshold, - deltaDistance, distanceThreshold, - mAfterFastTyping ? " afterFastTyping" : "", - isStartOfAGesture ? " startOfAGesture" : "")); - } - return isStartOfAGesture; - } - - // TODO: Make this package private - public void duplicateLastPointWith(final int time) { - final int lastIndex = getLength() - 1; - if (lastIndex >= 0) { - final int x = mXCoordinates.get(lastIndex); - final int y = mYCoordinates.get(lastIndex); - if (DEBUG) { - Log.d(TAG, String.format("[%d] duplicateLastPointWith: %d,%d|%d", mPointerId, - x, y, time)); - } - // TODO: Have appendMajorPoint() - appendPoint(x, y, time); - updateIncrementalRecognitionSize(x, y, time); - } - } - - private void reset() { - mIncrementalRecognitionSize = 0; - mLastIncrementalBatchSize = 0; - mEventTimes.setLength(0); - mXCoordinates.setLength(0); - mYCoordinates.setLength(0); - mLastMajorEventTime = 0; - mDetectFastMoveTime = 0; - mAfterFastTyping = false; - } - - private void appendPoint(final int x, final int y, final int time) { - final int lastIndex = getLength() - 1; - // The point that is created by {@link duplicateLastPointWith(int)} may have later event - // time than the next {@link MotionEvent}. To maintain the monotonicity of the event time, - // drop the successive point here. - if (lastIndex >= 0 && mEventTimes.get(lastIndex) > time) { - Log.w(TAG, String.format("[%d] drop stale event: %d,%d|%d last: %d,%d|%d", mPointerId, - x, y, time, mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex), - mEventTimes.get(lastIndex))); - return; - } - mEventTimes.add(time); - mXCoordinates.add(x); - mYCoordinates.add(y); - } - - private void updateMajorEvent(final int x, final int y, final int time) { - mLastMajorEventTime = time; - mLastMajorEventX = x; - mLastMajorEventY = y; - } - - private final boolean hasDetectedFastMove() { - return mDetectFastMoveTime > 0; - } - - private int detectFastMove(final int x, final int y, final int time) { - final int size = getLength(); - final int lastIndex = size - 1; - final int lastX = mXCoordinates.get(lastIndex); - final int lastY = mYCoordinates.get(lastIndex); - final int dist = getDistance(lastX, lastY, x, y); - final int msecs = time - mEventTimes.get(lastIndex); - if (msecs > 0) { - final int pixels = getDistance(lastX, lastY, x, y); - final int pixelsPerSec = pixels * MSEC_PER_SEC; - if (DEBUG_SPEED) { - final float speed = (float)pixelsPerSec / msecs / mKeyWidth; - Log.d(TAG, String.format("[%d] detectFastMove: speed=%5.2f", mPointerId, speed)); - } - // Equivalent to (pixels / msecs < mStartSpeedThreshold / MSEC_PER_SEC) - if (!hasDetectedFastMove() && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) { - if (DEBUG) { - final float speed = (float)pixelsPerSec / msecs / mKeyWidth; - Log.d(TAG, String.format( - "[%d] detectFastMove: speed=%5.2f T=%3d points=%3d fastMove", - mPointerId, speed, time, size)); - } - mDetectFastMoveTime = time; - mDetectFastMoveX = x; - mDetectFastMoveY = y; - } - } - return dist; - } - - /** - * Add an event point to this gesture stroke recognition points. Returns true if the event - * point is on the valid gesture area. - * @param x the x-coordinate of the event point - * @param y the y-coordinate of the event point - * @param time the elapsed time in millisecond from the first gesture down - * @param isMajorEvent false if this is a historical move event - * @return true if the event point is on the valid gesture area - */ - // TODO: Make this package private - public boolean addEventPoint(final int x, final int y, final int time, - final boolean isMajorEvent) { - final int size = getLength(); - if (size <= 0) { - // The first event of this stroke (a.k.a. down event). - appendPoint(x, y, time); - updateMajorEvent(x, y, time); - } else { - final int distance = detectFastMove(x, y, time); - if (distance > mGestureSamplingMinimumDistance) { - appendPoint(x, y, time); - } - } - if (isMajorEvent) { - updateIncrementalRecognitionSize(x, y, time); - updateMajorEvent(x, y, time); - } - return y >= mMinYCoordinate && y < mMaxYCoordinate; - } - - private void updateIncrementalRecognitionSize(final int x, final int y, final int time) { - final int msecs = (int)(time - mLastMajorEventTime); - if (msecs <= 0) { - return; - } - final int pixels = getDistance(mLastMajorEventX, mLastMajorEventY, x, y); - final int pixelsPerSec = pixels * MSEC_PER_SEC; - // Equivalent to (pixels / msecs < mGestureRecognitionThreshold / MSEC_PER_SEC) - if (pixelsPerSec < mGestureRecognitionSpeedThreshold * msecs) { - mIncrementalRecognitionSize = getLength(); - } - } - - // TODO: Make this package private - public final boolean hasRecognitionTimePast( - final long currentTime, final long lastRecognitionTime) { - return currentTime > lastRecognitionTime + mRecognitionParams.mRecognitionMinimumTime; - } - - // TODO: Make this package private - public final void appendAllBatchPoints(final InputPointers out) { - appendBatchPoints(out, getLength()); - } - - // TODO: Make this package private - public final void appendIncrementalBatchPoints(final InputPointers out) { - appendBatchPoints(out, mIncrementalRecognitionSize); - } - - private void appendBatchPoints(final InputPointers out, final int size) { - final int length = size - mLastIncrementalBatchSize; - if (length <= 0) { - return; - } - out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates, - mLastIncrementalBatchSize, length); - mLastIncrementalBatchSize = size; - } - - private static int getDistance(final int x1, final int y1, final int x2, final int y2) { - return (int)Math.hypot(x1 - x2, y1 - y2); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingParams.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingParams.java deleted file mode 100644 index 074862a70..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingParams.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.TypedArray; - -import com.android.inputmethod.latin.R; - -/** - * This class holds parameters to control how a gesture trail is drawn and animated on the screen. - * - * On the other hand, {@link GestureStrokeDrawingParams} class controls how each gesture stroke is - * sampled and interpolated. This class controls how those gesture strokes are displayed as a - * gesture trail and animated on the screen. - * - * @attr ref android.R.styleable#MainKeyboardView_gestureTrailFadeoutStartDelay - * @attr ref android.R.styleable#MainKeyboardView_gestureTrailFadeoutDuration - * @attr ref android.R.styleable#MainKeyboardView_gestureTrailUpdateInterval - * @attr ref android.R.styleable#MainKeyboardView_gestureTrailColor - * @attr ref android.R.styleable#MainKeyboardView_gestureTrailWidth - */ -final class GestureTrailDrawingParams { - private static final int FADEOUT_START_DELAY_FOR_DEBUG = 2000; // millisecond - private static final int FADEOUT_DURATION_FOR_DEBUG = 200; // millisecond - - public final int mTrailColor; - public final float mTrailStartWidth; - public final float mTrailEndWidth; - public final float mTrailBodyRatio; - public boolean mTrailShadowEnabled; - public final float mTrailShadowRatio; - public final int mFadeoutStartDelay; - public final int mFadeoutDuration; - public final int mUpdateInterval; - - public final int mTrailLingerDuration; - - public GestureTrailDrawingParams(final TypedArray mainKeyboardViewAttr) { - mTrailColor = mainKeyboardViewAttr.getColor( - R.styleable.MainKeyboardView_gestureTrailColor, 0); - mTrailStartWidth = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gestureTrailStartWidth, 0.0f); - mTrailEndWidth = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gestureTrailEndWidth, 0.0f); - final int PERCENTAGE_INT = 100; - mTrailBodyRatio = (float)mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailBodyRatio, PERCENTAGE_INT) - / (float)PERCENTAGE_INT; - final int trailShadowRatioInt = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0); - mTrailShadowEnabled = (trailShadowRatioInt > 0); - mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT; - mFadeoutStartDelay = GestureTrailDrawingPoints.DEBUG_SHOW_POINTS - ? FADEOUT_START_DELAY_FOR_DEBUG - : mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0); - mFadeoutDuration = GestureTrailDrawingPoints.DEBUG_SHOW_POINTS - ? FADEOUT_DURATION_FOR_DEBUG - : mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0); - mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration; - mUpdateInterval = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java deleted file mode 100644 index 4d998e443..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Rect; -import android.os.SystemClock; - -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.ResizableIntArray; - -/** - * This class holds drawing points to represent a gesture trail. The gesture trail may contain - * multiple non-contiguous gesture strokes and will be animated asynchronously from gesture input. - * - * On the other hand, {@link GestureStrokeDrawingPoints} class holds drawing points of each gesture - * stroke. This class holds drawing points of those gesture strokes to draw as a gesture trail. - * Drawing points in this class will be asynchronously removed when fading out animation goes. - */ -final class GestureTrailDrawingPoints { - public static final boolean DEBUG_SHOW_POINTS = false; - public static final int POINT_TYPE_SAMPLED = 1; - public static final int POINT_TYPE_INTERPOLATED = 2; - - private static final int DEFAULT_CAPACITY = GestureStrokeDrawingPoints.PREVIEW_CAPACITY; - - // These three {@link ResizableIntArray}s should be synchronized by {@link #mEventTimes}. - private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); - private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); - private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY); - private final ResizableIntArray mPointTypes = new ResizableIntArray( - DEBUG_SHOW_POINTS ? DEFAULT_CAPACITY : 0); - private int mCurrentStrokeId = -1; - // The wall time of the zero value in {@link #mEventTimes} - private long mCurrentTimeBase; - private int mTrailStartIndex; - private int mLastInterpolatedDrawIndex; - - // Use this value as imaginary zero because x-coordinates may be zero. - private static final int DOWN_EVENT_MARKER = -128; - - private static int markAsDownEvent(final int xCoord) { - return DOWN_EVENT_MARKER - xCoord; - } - - private static boolean isDownEventXCoord(final int xCoordOrMark) { - return xCoordOrMark <= DOWN_EVENT_MARKER; - } - - private static int getXCoordValue(final int xCoordOrMark) { - return isDownEventXCoord(xCoordOrMark) - ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark; - } - - public void addStroke(final GestureStrokeDrawingPoints stroke, final long downTime) { - synchronized (mEventTimes) { - addStrokeLocked(stroke, downTime); - } - } - - private void addStrokeLocked(final GestureStrokeDrawingPoints stroke, final long downTime) { - final int trailSize = mEventTimes.getLength(); - stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates, mPointTypes); - if (mEventTimes.getLength() == trailSize) { - return; - } - final int[] eventTimes = mEventTimes.getPrimitiveArray(); - final int strokeId = stroke.getGestureStrokeId(); - // Because interpolation algorithm in {@link GestureStrokeDrawingPoints} can't determine - // the interpolated points in the last segment of gesture stroke, it may need recalculation - // of interpolation when new segments are added to the stroke. - // {@link #mLastInterpolatedDrawIndex} holds the start index of the last segment. It may - // be updated by the interpolation - // {@link GestureStrokeDrawingPoints#interpolatePreviewStroke} - // or by animation {@link #drawGestureTrail(Canvas,Paint,Rect,GestureTrailDrawingParams)} - // below. - final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId) - ? mLastInterpolatedDrawIndex : trailSize; - mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment( - lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates, mPointTypes); - if (strokeId != mCurrentStrokeId) { - final int elapsedTime = (int)(downTime - mCurrentTimeBase); - for (int i = mTrailStartIndex; i < trailSize; i++) { - // Decay the previous strokes' event times. - eventTimes[i] -= elapsedTime; - } - final int[] xCoords = mXCoordinates.getPrimitiveArray(); - final int downIndex = trailSize; - xCoords[downIndex] = markAsDownEvent(xCoords[downIndex]); - mCurrentTimeBase = downTime - eventTimes[downIndex]; - mCurrentStrokeId = strokeId; - } - } - - /** - * Calculate the alpha of a gesture trail. - * A gesture trail starts from fully opaque. After mFadeStartDelay has been passed, the alpha - * of a trail reduces in proportion to the elapsed time. Then after mFadeDuration has been - * passed, a trail becomes fully transparent. - * - * @param elapsedTime the elapsed time since a trail has been made. - * @param params gesture trail display parameters - * @return the width of a gesture trail - */ - private static int getAlpha(final int elapsedTime, final GestureTrailDrawingParams params) { - if (elapsedTime < params.mFadeoutStartDelay) { - return Constants.Color.ALPHA_OPAQUE; - } - final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE - * (elapsedTime - params.mFadeoutStartDelay) - / params.mFadeoutDuration; - return Constants.Color.ALPHA_OPAQUE - decreasingAlpha; - } - - /** - * Calculate the width of a gesture trail. - * A gesture trail starts from the width of mTrailStartWidth and reduces its width in proportion - * to the elapsed time. After mTrailEndWidth has been passed, the width becomes mTraiLEndWidth. - * - * @param elapsedTime the elapsed time since a trail has been made. - * @param params gesture trail display parameters - * @return the width of a gesture trail - */ - private static float getWidth(final int elapsedTime, final GestureTrailDrawingParams params) { - final float deltaWidth = params.mTrailStartWidth - params.mTrailEndWidth; - return params.mTrailStartWidth - (deltaWidth * elapsedTime) / params.mTrailLingerDuration; - } - - private final RoundedLine mRoundedLine = new RoundedLine(); - private final Rect mRoundedLineBounds = new Rect(); - - /** - * Draw gesture trail - * @param canvas The canvas to draw the gesture trail - * @param paint The paint object to be used to draw the gesture trail - * @param outBoundsRect the bounding box of this gesture trail drawing - * @param params The drawing parameters of gesture trail - * @return true if some gesture trails remain to be drawn - */ - public boolean drawGestureTrail(final Canvas canvas, final Paint paint, - final Rect outBoundsRect, final GestureTrailDrawingParams params) { - synchronized (mEventTimes) { - return drawGestureTrailLocked(canvas, paint, outBoundsRect, params); - } - } - - private boolean drawGestureTrailLocked(final Canvas canvas, final Paint paint, - final Rect outBoundsRect, final GestureTrailDrawingParams params) { - // Initialize bounds rectangle. - outBoundsRect.setEmpty(); - final int trailSize = mEventTimes.getLength(); - if (trailSize == 0) { - return false; - } - - final int[] eventTimes = mEventTimes.getPrimitiveArray(); - final int[] xCoords = mXCoordinates.getPrimitiveArray(); - final int[] yCoords = mYCoordinates.getPrimitiveArray(); - final int[] pointTypes = mPointTypes.getPrimitiveArray(); - final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentTimeBase); - int startIndex; - for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) { - final int elapsedTime = sinceDown - eventTimes[startIndex]; - // Skip too old trail points. - if (elapsedTime < params.mTrailLingerDuration) { - break; - } - } - mTrailStartIndex = startIndex; - - if (startIndex < trailSize) { - paint.setColor(params.mTrailColor); - paint.setStyle(Paint.Style.FILL); - final RoundedLine roundedLine = mRoundedLine; - int p1x = getXCoordValue(xCoords[startIndex]); - int p1y = yCoords[startIndex]; - final int lastTime = sinceDown - eventTimes[startIndex]; - float r1 = getWidth(lastTime, params) / 2.0f; - for (int i = startIndex + 1; i < trailSize; i++) { - final int elapsedTime = sinceDown - eventTimes[i]; - final int p2x = getXCoordValue(xCoords[i]); - final int p2y = yCoords[i]; - final float r2 = getWidth(elapsedTime, params) / 2.0f; - // Draw trail line only when the current point isn't a down point. - if (!isDownEventXCoord(xCoords[i])) { - final float body1 = r1 * params.mTrailBodyRatio; - final float body2 = r2 * params.mTrailBodyRatio; - final Path path = roundedLine.makePath(p1x, p1y, body1, p2x, p2y, body2); - if (!path.isEmpty()) { - roundedLine.getBounds(mRoundedLineBounds); - if (params.mTrailShadowEnabled) { - final float shadow2 = r2 * params.mTrailShadowRatio; - paint.setShadowLayer(shadow2, 0.0f, 0.0f, params.mTrailColor); - final int shadowInset = -(int)Math.ceil(shadow2); - mRoundedLineBounds.inset(shadowInset, shadowInset); - } - // Take union for the bounds. - outBoundsRect.union(mRoundedLineBounds); - final int alpha = getAlpha(elapsedTime, params); - paint.setAlpha(alpha); - canvas.drawPath(path, paint); - } - } - p1x = p2x; - p1y = p2y; - r1 = r2; - } - if (DEBUG_SHOW_POINTS) { - debugDrawPoints(canvas, startIndex, trailSize, paint); - } - } - - final int newSize = trailSize - startIndex; - if (newSize < startIndex) { - mTrailStartIndex = 0; - if (newSize > 0) { - System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize); - System.arraycopy(xCoords, startIndex, xCoords, 0, newSize); - System.arraycopy(yCoords, startIndex, yCoords, 0, newSize); - if (DEBUG_SHOW_POINTS) { - System.arraycopy(pointTypes, startIndex, pointTypes, 0, newSize); - } - } - mEventTimes.setLength(newSize); - mXCoordinates.setLength(newSize); - mYCoordinates.setLength(newSize); - if (DEBUG_SHOW_POINTS) { - mPointTypes.setLength(newSize); - } - // The start index of the last segment of the stroke - // {@link mLastInterpolatedDrawIndex} should also be updated because all array - // elements have just been shifted for compaction or been zeroed. - mLastInterpolatedDrawIndex = Math.max(mLastInterpolatedDrawIndex - startIndex, 0); - } - return newSize > 0; - } - - private void debugDrawPoints(final Canvas canvas, final int startIndex, final int endIndex, - final Paint paint) { - final int[] xCoords = mXCoordinates.getPrimitiveArray(); - final int[] yCoords = mYCoordinates.getPrimitiveArray(); - final int[] pointTypes = mPointTypes.getPrimitiveArray(); - // {@link Paint} that is zero width stroke and anti alias off draws exactly 1 pixel. - paint.setAntiAlias(false); - paint.setStrokeWidth(0); - for (int i = startIndex; i < endIndex; i++) { - final int pointType = pointTypes[i]; - if (pointType == POINT_TYPE_INTERPOLATED) { - paint.setColor(Color.RED); - } else if (pointType == POINT_TYPE_SAMPLED) { - paint.setColor(0xFFA000FF); - } else { - paint.setColor(Color.GREEN); - } - canvas.drawPoint(getXCoordValue(xCoords[i]), yCoords[i], paint); - } - paint.setAntiAlias(true); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java deleted file mode 100644 index f7bd7efe0..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.os.Handler; -import android.util.SparseArray; - -import com.android.inputmethod.keyboard.PointerTracker; - -/** - * Draw preview graphics of multiple gesture trails during gesture input. - */ -public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview implements Runnable { - private final SparseArray<GestureTrailDrawingPoints> mGestureTrails = new SparseArray<>(); - private final GestureTrailDrawingParams mDrawingParams; - private final Paint mGesturePaint; - private int mOffscreenWidth; - private int mOffscreenHeight; - private int mOffscreenOffsetY; - private Bitmap mOffscreenBuffer; - private final Canvas mOffscreenCanvas = new Canvas(); - private final Rect mOffscreenSrcRect = new Rect(); - private final Rect mDirtyRect = new Rect(); - private final Rect mGestureTrailBoundsRect = new Rect(); // per trail - - private final Handler mDrawingHandler = new Handler(); - - public GestureTrailsDrawingPreview(final TypedArray mainKeyboardViewAttr) { - mDrawingParams = new GestureTrailDrawingParams(mainKeyboardViewAttr); - final Paint gesturePaint = new Paint(); - gesturePaint.setAntiAlias(true); - gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); - mGesturePaint = gesturePaint; - } - - @Override - public void setKeyboardViewGeometry(final int[] originCoords, final int width, - final int height) { - super.setKeyboardViewGeometry(originCoords, width, height); - mOffscreenOffsetY = (int)(height - * GestureStrokeRecognitionPoints.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); - mOffscreenWidth = width; - mOffscreenHeight = mOffscreenOffsetY + height; - } - - @Override - public void onDeallocateMemory() { - freeOffscreenBuffer(); - } - - private void freeOffscreenBuffer() { - mOffscreenCanvas.setBitmap(null); - mOffscreenCanvas.setMatrix(null); - if (mOffscreenBuffer != null) { - mOffscreenBuffer.recycle(); - mOffscreenBuffer = null; - } - } - - private void mayAllocateOffscreenBuffer() { - if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == mOffscreenWidth - && mOffscreenBuffer.getHeight() == mOffscreenHeight) { - return; - } - freeOffscreenBuffer(); - mOffscreenBuffer = Bitmap.createBitmap( - mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888); - mOffscreenCanvas.setBitmap(mOffscreenBuffer); - mOffscreenCanvas.translate(0, mOffscreenOffsetY); - } - - private boolean drawGestureTrails(final Canvas offscreenCanvas, final Paint paint, - final Rect dirtyRect) { - // Clear previous dirty rectangle. - if (!dirtyRect.isEmpty()) { - paint.setColor(Color.TRANSPARENT); - paint.setStyle(Paint.Style.FILL); - offscreenCanvas.drawRect(dirtyRect, paint); - } - dirtyRect.setEmpty(); - boolean needsUpdatingGestureTrail = false; - // Draw gesture trails to offscreen buffer. - synchronized (mGestureTrails) { - // Trails count == fingers count that have ever been active. - final int trailsCount = mGestureTrails.size(); - for (int index = 0; index < trailsCount; index++) { - final GestureTrailDrawingPoints trail = mGestureTrails.valueAt(index); - needsUpdatingGestureTrail |= trail.drawGestureTrail(offscreenCanvas, paint, - mGestureTrailBoundsRect, mDrawingParams); - // {@link #mGestureTrailBoundsRect} has bounding box of the trail. - dirtyRect.union(mGestureTrailBoundsRect); - } - } - return needsUpdatingGestureTrail; - } - - @Override - public void run() { - // Update preview. - invalidateDrawingView(); - } - - /** - * Draws the preview - * @param canvas The canvas where the preview is drawn. - */ - @Override - public void drawPreview(final Canvas canvas) { - if (!isPreviewEnabled()) { - return; - } - mayAllocateOffscreenBuffer(); - // Draw gesture trails to offscreen buffer. - final boolean needsUpdatingGestureTrail = drawGestureTrails( - mOffscreenCanvas, mGesturePaint, mDirtyRect); - if (needsUpdatingGestureTrail) { - mDrawingHandler.removeCallbacks(this); - mDrawingHandler.postDelayed(this, mDrawingParams.mUpdateInterval); - } - // Transfer offscreen buffer to screen. - if (!mDirtyRect.isEmpty()) { - mOffscreenSrcRect.set(mDirtyRect); - mOffscreenSrcRect.offset(0, mOffscreenOffsetY); - canvas.drawBitmap(mOffscreenBuffer, mOffscreenSrcRect, mDirtyRect, null); - // Note: Defer clearing the dirty rectangle here because we will get cleared - // rectangle on the canvas. - } - } - - /** - * Set the position of the preview. - * @param tracker The new location of the preview is based on the points in PointerTracker. - */ - @Override - public void setPreviewPosition(final PointerTracker tracker) { - if (!isPreviewEnabled()) { - return; - } - GestureTrailDrawingPoints trail; - synchronized (mGestureTrails) { - trail = mGestureTrails.get(tracker.mPointerId); - if (trail == null) { - trail = new GestureTrailDrawingPoints(); - mGestureTrails.put(tracker.mPointerId, trail); - } - } - trail.addStroke(tracker.getGestureStrokeDrawingPoints(), tracker.getDownTime()); - - // TODO: Should narrow the invalidate region. - invalidateDrawingView(); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java deleted file mode 100644 index b526a942a..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -/** - * Interpolates XY-coordinates using Cubic Hermite Curve. - */ -public final class HermiteInterpolator { - private int[] mXCoords; - private int[] mYCoords; - private int mMinPos; - private int mMaxPos; - - // Working variable to calculate interpolated value. - /** The coordinates of the start point of the interval. */ - public int mP1X, mP1Y; - /** The coordinates of the end point of the interval. */ - public int mP2X, mP2Y; - /** The slope of the tangent at the start point. */ - public float mSlope1X, mSlope1Y; - /** The slope of the tangent at the end point. */ - public float mSlope2X, mSlope2Y; - /** The interpolated coordinates. - * The return variables of {@link #interpolate(float)} to avoid instantiations. - */ - public float mInterpolatedX, mInterpolatedY; - - public HermiteInterpolator() { - // Nothing to do with here. - } - - /** - * Reset this interpolator to point XY-coordinates data. - * @param xCoords the array of x-coordinates. Valid data are in left-open interval - * <code>[minPos, maxPos)</code>. - * @param yCoords the array of y-coordinates. Valid data are in left-open interval - * <code>[minPos, maxPos)</code>. - * @param minPos the minimum index of left-open interval of valid data. - * @param maxPos the maximum index of left-open interval of valid data. - */ - public void reset(final int[] xCoords, final int[] yCoords, final int minPos, - final int maxPos) { - mXCoords = xCoords; - mYCoords = yCoords; - mMinPos = minPos; - mMaxPos = maxPos; - } - - /** - * Set interpolation interval. - * <p> - * The start and end coordinates of the interval will be set in {@link #mP1X}, {@link #mP1Y}, - * {@link #mP2X}, and {@link #mP2Y}. The slope of the tangents at start and end points will be - * set in {@link #mSlope1X}, {@link #mSlope1Y}, {@link #mSlope2X}, and {@link #mSlope2Y}. - * - * @param p0 the index just before interpolation interval. If <code>p1</code> points the start - * of valid points, <code>p0</code> must be less than <code>minPos</code> of - * {@link #reset(int[],int[],int,int)}. - * @param p1 the start index of interpolation interval. - * @param p2 the end index of interpolation interval. - * @param p3 the index just after interpolation interval. If <code>p2</code> points the end of - * valid points, <code>p3</code> must be equal or greater than <code>maxPos</code> of - * {@link #reset(int[],int[],int,int)}. - */ - public void setInterval(final int p0, final int p1, final int p2, final int p3) { - mP1X = mXCoords[p1]; - mP1Y = mYCoords[p1]; - mP2X = mXCoords[p2]; - mP2Y = mYCoords[p2]; - // A(ax,ay) is the vector p1->p2. - final int ax = mP2X - mP1X; - final int ay = mP2Y - mP1Y; - - // Calculate the slope of the tangent at p1. - if (p0 >= mMinPos) { - // p1 has previous valid point p0. - // The slope of the tangent is half of the vector p0->p2. - mSlope1X = (mP2X - mXCoords[p0]) / 2.0f; - mSlope1Y = (mP2Y - mYCoords[p0]) / 2.0f; - } else if (p3 < mMaxPos) { - // p1 has no previous valid point, but p2 has next valid point p3. - // B(bx,by) is the slope vector of the tangent at p2. - final float bx = (mXCoords[p3] - mP1X) / 2.0f; - final float by = (mYCoords[p3] - mP1Y) / 2.0f; - final float crossProdAB = ax * by - ay * bx; - final float dotProdAB = ax * bx + ay * by; - final float normASquare = ax * ax + ay * ay; - final float invHalfNormASquare = 1.0f / normASquare / 2.0f; - // The slope of the tangent is the mirror image of vector B to vector A. - mSlope1X = invHalfNormASquare * (dotProdAB * ax + crossProdAB * ay); - mSlope1Y = invHalfNormASquare * (dotProdAB * ay - crossProdAB * ax); - } else { - // p1 and p2 have no previous valid point. (Interval has only point p1 and p2) - mSlope1X = ax; - mSlope1Y = ay; - } - - // Calculate the slope of the tangent at p2. - if (p3 < mMaxPos) { - // p2 has next valid point p3. - // The slope of the tangent is half of the vector p1->p3. - mSlope2X = (mXCoords[p3] - mP1X) / 2.0f; - mSlope2Y = (mYCoords[p3] - mP1Y) / 2.0f; - } else if (p0 >= mMinPos) { - // p2 has no next valid point, but p1 has previous valid point p0. - // B(bx,by) is the slope vector of the tangent at p1. - final float bx = (mP2X - mXCoords[p0]) / 2.0f; - final float by = (mP2Y - mYCoords[p0]) / 2.0f; - final float crossProdAB = ax * by - ay * bx; - final float dotProdAB = ax * bx + ay * by; - final float normASquare = ax * ax + ay * ay; - final float invHalfNormASquare = 1.0f / normASquare / 2.0f; - // The slope of the tangent is the mirror image of vector B to vector A. - mSlope2X = invHalfNormASquare * (dotProdAB * ax + crossProdAB * ay); - mSlope2Y = invHalfNormASquare * (dotProdAB * ay - crossProdAB * ax); - } else { - // p1 and p2 has no previous valid point. (Interval has only point p1 and p2) - mSlope2X = ax; - mSlope2Y = ay; - } - } - - /** - * Calculate interpolation value at <code>t</code> in unit interval <code>[0,1]</code>. - * <p> - * On the unit interval [0,1], given a starting point p1 at t=0 and an ending point p2 at t=1 - * with the slope of the tangent m1 at p1 and m2 at p2, the polynomial of cubic Hermite curve - * can be defined by - * p(t) = (1+2t)(1-t)(1-t)*p1 + t(1-t)(1-t)*m1 + (3-2t)t^2*p2 + (t-1)t^2*m2 - * where t is an element of [0,1]. - * <p> - * The interpolated XY-coordinates will be set in {@link #mInterpolatedX} and - * {@link #mInterpolatedY}. - * - * @param t the interpolation parameter. The value must be in close interval <code>[0,1]</code>. - */ - public void interpolate(final float t) { - final float omt = 1.0f - t; - final float tm2 = 2.0f * t; - final float k1 = 1.0f + tm2; - final float k2 = 3.0f - tm2; - final float omt2 = omt * omt; - final float t2 = t * t; - mInterpolatedX = (k1 * mP1X + t * mSlope1X) * omt2 + (k2 * mP2X - omt * mSlope2X) * t2; - mInterpolatedY = (k1 * mP1Y + t * mSlope1Y) * omt2 + (k2 * mP2Y - omt * mSlope2Y) * t2; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java deleted file mode 100644 index 3ef9ea1dc..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.graphics.Typeface; - -import com.android.inputmethod.latin.utils.ResourceUtils; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public final class KeyDrawParams { - @Nonnull - public Typeface mTypeface = Typeface.DEFAULT; - - public int mLetterSize; - public int mLabelSize; - public int mLargeLetterSize; - public int mHintLetterSize; - public int mShiftedLetterHintSize; - public int mHintLabelSize; - public int mPreviewTextSize; - - public int mTextColor; - public int mTextInactivatedColor; - public int mTextShadowColor; - public int mFunctionalTextColor; - public int mHintLetterColor; - public int mHintLabelColor; - public int mShiftedLetterHintInactivatedColor; - public int mShiftedLetterHintActivatedColor; - public int mPreviewTextColor; - - public float mHintLabelVerticalAdjustment; - public float mLabelOffCenterRatio; - public float mHintLabelOffCenterRatio; - - public int mAnimAlpha; - - public KeyDrawParams() {} - - private KeyDrawParams(@Nonnull final KeyDrawParams copyFrom) { - mTypeface = copyFrom.mTypeface; - - mLetterSize = copyFrom.mLetterSize; - mLabelSize = copyFrom.mLabelSize; - mLargeLetterSize = copyFrom.mLargeLetterSize; - mHintLetterSize = copyFrom.mHintLetterSize; - mShiftedLetterHintSize = copyFrom.mShiftedLetterHintSize; - mHintLabelSize = copyFrom.mHintLabelSize; - mPreviewTextSize = copyFrom.mPreviewTextSize; - - mTextColor = copyFrom.mTextColor; - mTextInactivatedColor = copyFrom.mTextInactivatedColor; - mTextShadowColor = copyFrom.mTextShadowColor; - mFunctionalTextColor = copyFrom.mFunctionalTextColor; - mHintLetterColor = copyFrom.mHintLetterColor; - mHintLabelColor = copyFrom.mHintLabelColor; - mShiftedLetterHintInactivatedColor = copyFrom.mShiftedLetterHintInactivatedColor; - mShiftedLetterHintActivatedColor = copyFrom.mShiftedLetterHintActivatedColor; - mPreviewTextColor = copyFrom.mPreviewTextColor; - - mHintLabelVerticalAdjustment = copyFrom.mHintLabelVerticalAdjustment; - mLabelOffCenterRatio = copyFrom.mLabelOffCenterRatio; - mHintLabelOffCenterRatio = copyFrom.mHintLabelOffCenterRatio; - - mAnimAlpha = copyFrom.mAnimAlpha; - } - - public void updateParams(final int keyHeight, @Nullable final KeyVisualAttributes attr) { - if (attr == null) { - return; - } - - if (attr.mTypeface != null) { - mTypeface = attr.mTypeface; - } - - mLetterSize = selectTextSizeFromDimensionOrRatio(keyHeight, - attr.mLetterSize, attr.mLetterRatio, mLetterSize); - mLabelSize = selectTextSizeFromDimensionOrRatio(keyHeight, - attr.mLabelSize, attr.mLabelRatio, mLabelSize); - mLargeLetterSize = selectTextSize(keyHeight, attr.mLargeLetterRatio, mLargeLetterSize); - mHintLetterSize = selectTextSize(keyHeight, attr.mHintLetterRatio, mHintLetterSize); - mShiftedLetterHintSize = selectTextSize(keyHeight, - attr.mShiftedLetterHintRatio, mShiftedLetterHintSize); - mHintLabelSize = selectTextSize(keyHeight, attr.mHintLabelRatio, mHintLabelSize); - mPreviewTextSize = selectTextSize(keyHeight, attr.mPreviewTextRatio, mPreviewTextSize); - - mTextColor = selectColor(attr.mTextColor, mTextColor); - mTextInactivatedColor = selectColor(attr.mTextInactivatedColor, mTextInactivatedColor); - mTextShadowColor = selectColor(attr.mTextShadowColor, mTextShadowColor); - mFunctionalTextColor = selectColor(attr.mFunctionalTextColor, mFunctionalTextColor); - mHintLetterColor = selectColor(attr.mHintLetterColor, mHintLetterColor); - mHintLabelColor = selectColor(attr.mHintLabelColor, mHintLabelColor); - mShiftedLetterHintInactivatedColor = selectColor( - attr.mShiftedLetterHintInactivatedColor, mShiftedLetterHintInactivatedColor); - mShiftedLetterHintActivatedColor = selectColor( - attr.mShiftedLetterHintActivatedColor, mShiftedLetterHintActivatedColor); - mPreviewTextColor = selectColor(attr.mPreviewTextColor, mPreviewTextColor); - - mHintLabelVerticalAdjustment = selectFloatIfNonZero( - attr.mHintLabelVerticalAdjustment, mHintLabelVerticalAdjustment); - mLabelOffCenterRatio = selectFloatIfNonZero( - attr.mLabelOffCenterRatio, mLabelOffCenterRatio); - mHintLabelOffCenterRatio = selectFloatIfNonZero( - attr.mHintLabelOffCenterRatio, mHintLabelOffCenterRatio); - } - - @Nonnull - public KeyDrawParams mayCloneAndUpdateParams(final int keyHeight, - @Nullable final KeyVisualAttributes attr) { - if (attr == null) { - return this; - } - final KeyDrawParams newParams = new KeyDrawParams(this); - newParams.updateParams(keyHeight, attr); - return newParams; - } - - private static int selectTextSizeFromDimensionOrRatio(final int keyHeight, - final int dimens, final float ratio, final int defaultDimens) { - if (ResourceUtils.isValidDimensionPixelSize(dimens)) { - return dimens; - } - if (ResourceUtils.isValidFraction(ratio)) { - return (int)(keyHeight * ratio); - } - return defaultDimens; - } - - private static int selectTextSize(final int keyHeight, final float ratio, - final int defaultSize) { - if (ResourceUtils.isValidFraction(ratio)) { - return (int)(keyHeight * ratio); - } - return defaultSize; - } - - private static int selectColor(final int attrColor, final int defaultColor) { - if (attrColor != 0) { - return attrColor; - } - return defaultColor; - } - - private static float selectFloatIfNonZero(final float attrFloat, final float defaultFloat) { - if (attrFloat != 0) { - return attrFloat; - } - return defaultFloat; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java deleted file mode 100644 index 448f1b4b1..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.keyboard.internal; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.latin.common.CoordinateUtils; -import com.android.inputmethod.latin.utils.ViewLayoutUtils; - -import java.util.ArrayDeque; -import java.util.HashMap; - -/** - * This class controls pop up key previews. This class decides: - * - what kind of key previews should be shown. - * - where key previews should be placed. - * - how key previews should be shown and dismissed. - */ -public final class KeyPreviewChoreographer { - // Free {@link KeyPreviewView} pool that can be used for key preview. - private final ArrayDeque<KeyPreviewView> mFreeKeyPreviewViews = new ArrayDeque<>(); - // Map from {@link Key} to {@link KeyPreviewView} that is currently being displayed as key - // preview. - private final HashMap<Key,KeyPreviewView> mShowingKeyPreviewViews = new HashMap<>(); - - private final KeyPreviewDrawParams mParams; - - public KeyPreviewChoreographer(final KeyPreviewDrawParams params) { - mParams = params; - } - - public KeyPreviewView getKeyPreviewView(final Key key, final ViewGroup placerView) { - KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.remove(key); - if (keyPreviewView != null) { - return keyPreviewView; - } - keyPreviewView = mFreeKeyPreviewViews.poll(); - if (keyPreviewView != null) { - return keyPreviewView; - } - final Context context = placerView.getContext(); - keyPreviewView = new KeyPreviewView(context, null /* attrs */); - keyPreviewView.setBackgroundResource(mParams.mPreviewBackgroundResId); - placerView.addView(keyPreviewView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0)); - return keyPreviewView; - } - - public boolean isShowingKeyPreview(final Key key) { - return mShowingKeyPreviewViews.containsKey(key); - } - - public void dismissKeyPreview(final Key key, final boolean withAnimation) { - if (key == null) { - return; - } - final KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.get(key); - if (keyPreviewView == null) { - return; - } - final Object tag = keyPreviewView.getTag(); - if (withAnimation) { - if (tag instanceof KeyPreviewAnimators) { - final KeyPreviewAnimators animators = (KeyPreviewAnimators)tag; - animators.startDismiss(); - return; - } - } - // Dismiss preview without animation. - mShowingKeyPreviewViews.remove(key); - if (tag instanceof Animator) { - ((Animator)tag).cancel(); - } - keyPreviewView.setTag(null); - keyPreviewView.setVisibility(View.INVISIBLE); - mFreeKeyPreviewViews.add(keyPreviewView); - } - - public void placeAndShowKeyPreview(final Key key, final KeyboardIconsSet iconsSet, - final KeyDrawParams drawParams, final int keyboardViewWidth, final int[] keyboardOrigin, - final ViewGroup placerView, final boolean withAnimation) { - final KeyPreviewView keyPreviewView = getKeyPreviewView(key, placerView); - placeKeyPreview( - key, keyPreviewView, iconsSet, drawParams, keyboardViewWidth, keyboardOrigin); - showKeyPreview(key, keyPreviewView, withAnimation); - } - - private void placeKeyPreview(final Key key, final KeyPreviewView keyPreviewView, - final KeyboardIconsSet iconsSet, final KeyDrawParams drawParams, - final int keyboardViewWidth, final int[] originCoords) { - keyPreviewView.setPreviewVisual(key, iconsSet, drawParams); - keyPreviewView.measure( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - mParams.setGeometry(keyPreviewView); - final int previewWidth = keyPreviewView.getMeasuredWidth(); - final int previewHeight = mParams.mPreviewHeight; - final int keyDrawWidth = key.getDrawWidth(); - // The key preview is horizontally aligned with the center of the visible part of the - // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and - // the left/right background is used if such background is specified. - final int keyPreviewPosition; - int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2 - + CoordinateUtils.x(originCoords); - if (previewX < 0) { - previewX = 0; - keyPreviewPosition = KeyPreviewView.POSITION_LEFT; - } else if (previewX > keyboardViewWidth - previewWidth) { - previewX = keyboardViewWidth - previewWidth; - keyPreviewPosition = KeyPreviewView.POSITION_RIGHT; - } else { - keyPreviewPosition = KeyPreviewView.POSITION_MIDDLE; - } - final boolean hasMoreKeys = (key.getMoreKeys() != null); - keyPreviewView.setPreviewBackground(hasMoreKeys, keyPreviewPosition); - // The key preview is placed vertically above the top edge of the parent key with an - // arbitrary offset. - final int previewY = key.getY() - previewHeight + mParams.mPreviewOffset - + CoordinateUtils.y(originCoords); - - ViewLayoutUtils.placeViewAt( - keyPreviewView, previewX, previewY, previewWidth, previewHeight); - keyPreviewView.setPivotX(previewWidth / 2.0f); - keyPreviewView.setPivotY(previewHeight); - } - - void showKeyPreview(final Key key, final KeyPreviewView keyPreviewView, - final boolean withAnimation) { - if (!withAnimation) { - keyPreviewView.setVisibility(View.VISIBLE); - mShowingKeyPreviewViews.put(key, keyPreviewView); - return; - } - - // Show preview with animation. - final Animator showUpAnimator = createShowUpAnimator(key, keyPreviewView); - final Animator dismissAnimator = createDismissAnimator(key, keyPreviewView); - final KeyPreviewAnimators animators = new KeyPreviewAnimators( - showUpAnimator, dismissAnimator); - keyPreviewView.setTag(animators); - animators.startShowUp(); - } - - public Animator createShowUpAnimator(final Key key, final KeyPreviewView keyPreviewView) { - final Animator showUpAnimator = mParams.createShowUpAnimator(keyPreviewView); - showUpAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(final Animator animator) { - showKeyPreview(key, keyPreviewView, false /* withAnimation */); - } - }); - return showUpAnimator; - } - - private Animator createDismissAnimator(final Key key, final KeyPreviewView keyPreviewView) { - final Animator dismissAnimator = mParams.createDismissAnimator(keyPreviewView); - dismissAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(final Animator animator) { - dismissKeyPreview(key, false /* withAnimation */); - } - }); - return dismissAnimator; - } - - private static class KeyPreviewAnimators extends AnimatorListenerAdapter { - private final Animator mShowUpAnimator; - private final Animator mDismissAnimator; - - public KeyPreviewAnimators(final Animator showUpAnimator, final Animator dismissAnimator) { - mShowUpAnimator = showUpAnimator; - mDismissAnimator = dismissAnimator; - } - - public void startShowUp() { - mShowUpAnimator.start(); - } - - public void startDismiss() { - if (mShowUpAnimator.isRunning()) { - mShowUpAnimator.addListener(this); - return; - } - mDismissAnimator.start(); - } - - @Override - public void onAnimationEnd(final Animator animator) { - mDismissAnimator.start(); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java deleted file mode 100644 index 5ed39f986..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.animation.Animator; -import android.animation.AnimatorInflater; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.content.res.TypedArray; -import android.view.View; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; - -import com.android.inputmethod.latin.R; - -public final class KeyPreviewDrawParams { - // XML attributes of {@link MainKeyboardView}. - public final int mPreviewOffset; - public final int mPreviewHeight; - public final int mPreviewBackgroundResId; - private final int mShowUpAnimatorResId; - private final int mDismissAnimatorResId; - private boolean mHasCustomAnimationParams; - private int mShowUpDuration; - private int mDismissDuration; - private float mShowUpStartXScale; - private float mShowUpStartYScale; - private float mDismissEndXScale; - private float mDismissEndYScale; - private int mLingerTimeout; - private boolean mShowPopup = true; - - // The graphical geometry of the key preview. - // <-width-> - // +-------+ ^ - // | | | - // |preview| height (visible) - // | | | - // + + ^ v - // \ / |offset - // +-\ /-+ v - // | +-+ | - // |parent | - // | key| - // +-------+ - // The background of a {@link TextView} being used for a key preview may have invisible - // paddings. To align the more keys keyboard panel's visible part with the visible part of - // the background, we need to record the width and height of key preview that don't include - // invisible paddings. - private int mVisibleWidth; - private int mVisibleHeight; - // The key preview may have an arbitrary offset and its background that may have a bottom - // padding. To align the more keys keyboard and the key preview we also need to record the - // offset between the top edge of parent key and the bottom of the visible part of key - // preview background. - private int mVisibleOffset; - - public KeyPreviewDrawParams(final TypedArray mainKeyboardViewAttr) { - mPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset( - R.styleable.MainKeyboardView_keyPreviewOffset, 0); - mPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize( - R.styleable.MainKeyboardView_keyPreviewHeight, 0); - mPreviewBackgroundResId = mainKeyboardViewAttr.getResourceId( - R.styleable.MainKeyboardView_keyPreviewBackground, 0); - mLingerTimeout = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0); - mShowUpAnimatorResId = mainKeyboardViewAttr.getResourceId( - R.styleable.MainKeyboardView_keyPreviewShowUpAnimator, 0); - mDismissAnimatorResId = mainKeyboardViewAttr.getResourceId( - R.styleable.MainKeyboardView_keyPreviewDismissAnimator, 0); - } - - public void setVisibleOffset(final int previewVisibleOffset) { - mVisibleOffset = previewVisibleOffset; - } - - public int getVisibleOffset() { - return mVisibleOffset; - } - - public void setGeometry(final View previewTextView) { - final int previewWidth = previewTextView.getMeasuredWidth(); - final int previewHeight = mPreviewHeight; - // The width and height of visible part of the key preview background. The content marker - // of the background 9-patch have to cover the visible part of the background. - mVisibleWidth = previewWidth - previewTextView.getPaddingLeft() - - previewTextView.getPaddingRight(); - mVisibleHeight = previewHeight - previewTextView.getPaddingTop() - - previewTextView.getPaddingBottom(); - // The distance between the top edge of the parent key and the bottom of the visible part - // of the key preview background. - setVisibleOffset(mPreviewOffset - previewTextView.getPaddingBottom()); - } - - public int getVisibleWidth() { - return mVisibleWidth; - } - - public int getVisibleHeight() { - return mVisibleHeight; - } - - public void setPopupEnabled(final boolean enabled, final int lingerTimeout) { - mShowPopup = enabled; - mLingerTimeout = lingerTimeout; - } - - public boolean isPopupEnabled() { - return mShowPopup; - } - - public int getLingerTimeout() { - return mLingerTimeout; - } - - public void setAnimationParams(final boolean hasCustomAnimationParams, - final float showUpStartXScale, final float showUpStartYScale, final int showUpDuration, - final float dismissEndXScale, final float dismissEndYScale, final int dismissDuration) { - mHasCustomAnimationParams = hasCustomAnimationParams; - mShowUpStartXScale = showUpStartXScale; - mShowUpStartYScale = showUpStartYScale; - mShowUpDuration = showUpDuration; - mDismissEndXScale = dismissEndXScale; - mDismissEndYScale = dismissEndYScale; - mDismissDuration = dismissDuration; - } - - private static final float KEY_PREVIEW_SHOW_UP_END_SCALE = 1.0f; - private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR = - new AccelerateInterpolator(); - private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = - new DecelerateInterpolator(); - - public Animator createShowUpAnimator(final View target) { - if (mHasCustomAnimationParams) { - final ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat( - target, View.SCALE_X, mShowUpStartXScale, - KEY_PREVIEW_SHOW_UP_END_SCALE); - final ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat( - target, View.SCALE_Y, mShowUpStartYScale, - KEY_PREVIEW_SHOW_UP_END_SCALE); - final AnimatorSet showUpAnimator = new AnimatorSet(); - showUpAnimator.play(scaleXAnimator).with(scaleYAnimator); - showUpAnimator.setDuration(mShowUpDuration); - showUpAnimator.setInterpolator(DECELERATE_INTERPOLATOR); - return showUpAnimator; - } - final Animator animator = AnimatorInflater.loadAnimator( - target.getContext(), mShowUpAnimatorResId); - animator.setTarget(target); - animator.setInterpolator(DECELERATE_INTERPOLATOR); - return animator; - } - - public Animator createDismissAnimator(final View target) { - if (mHasCustomAnimationParams) { - final ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat( - target, View.SCALE_X, mDismissEndXScale); - final ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat( - target, View.SCALE_Y, mDismissEndYScale); - final AnimatorSet dismissAnimator = new AnimatorSet(); - dismissAnimator.play(scaleXAnimator).with(scaleYAnimator); - final int dismissDuration = Math.min(mDismissDuration, mLingerTimeout); - dismissAnimator.setDuration(dismissDuration); - dismissAnimator.setInterpolator(ACCELERATE_INTERPOLATOR); - return dismissAnimator; - } - final Animator animator = AnimatorInflater.loadAnimator( - target.getContext(), mDismissAnimatorResId); - animator.setTarget(target); - animator.setInterpolator(ACCELERATE_INTERPOLATOR); - return animator; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java deleted file mode 100644 index 24538605a..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.keyboard.internal; - -import android.content.Context; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.text.TextPaint; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.Gravity; -import android.widget.TextView; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.latin.R; - -import java.util.HashSet; - -/** - * The pop up key preview view. - */ -public class KeyPreviewView extends TextView { - public static final int POSITION_MIDDLE = 0; - public static final int POSITION_LEFT = 1; - public static final int POSITION_RIGHT = 2; - - private final Rect mBackgroundPadding = new Rect(); - private static final HashSet<String> sNoScaleXTextSet = new HashSet<>(); - - public KeyPreviewView(final Context context, final AttributeSet attrs) { - this(context, attrs, 0); - } - - public KeyPreviewView(final Context context, final AttributeSet attrs, final int defStyleAttr) { - super(context, attrs, defStyleAttr); - setGravity(Gravity.CENTER); - } - - public void setPreviewVisual(final Key key, final KeyboardIconsSet iconsSet, - final KeyDrawParams drawParams) { - // What we show as preview should match what we show on a key top in onDraw(). - final int iconId = key.getIconId(); - if (iconId != KeyboardIconsSet.ICON_UNDEFINED) { - setCompoundDrawables(null, null, null, key.getPreviewIcon(iconsSet)); - setText(null); - return; - } - - setCompoundDrawables(null, null, null, null); - setTextColor(drawParams.mPreviewTextColor); - setTextSize(TypedValue.COMPLEX_UNIT_PX, key.selectPreviewTextSize(drawParams)); - setTypeface(key.selectPreviewTypeface(drawParams)); - // TODO Should take care of temporaryShiftLabel here. - setTextAndScaleX(key.getPreviewLabel()); - } - - private void setTextAndScaleX(final String text) { - setTextScaleX(1.0f); - setText(text); - if (sNoScaleXTextSet.contains(text)) { - return; - } - // TODO: Override {@link #setBackground(Drawable)} that is supported from API 16 and - // calculate maximum text width. - final Drawable background = getBackground(); - if (background == null) { - return; - } - background.getPadding(mBackgroundPadding); - final int maxWidth = background.getIntrinsicWidth() - mBackgroundPadding.left - - mBackgroundPadding.right; - final float width = getTextWidth(text, getPaint()); - if (width <= maxWidth) { - sNoScaleXTextSet.add(text); - return; - } - setTextScaleX(maxWidth / width); - } - - public static void clearTextCache() { - sNoScaleXTextSet.clear(); - } - - private static float getTextWidth(final String text, final TextPaint paint) { - if (TextUtils.isEmpty(text)) { - return 0.0f; - } - final int len = text.length(); - final float[] widths = new float[len]; - final int count = paint.getTextWidths(text, 0, len, widths); - float width = 0; - for (int i = 0; i < count; i++) { - width += widths[i]; - } - return width; - } - - // Background state set - private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = { - { // POSITION_MIDDLE - {}, - { R.attr.state_has_morekeys } - }, - { // POSITION_LEFT - { R.attr.state_left_edge }, - { R.attr.state_left_edge, R.attr.state_has_morekeys } - }, - { // POSITION_RIGHT - { R.attr.state_right_edge }, - { R.attr.state_right_edge, R.attr.state_has_morekeys } - } - }; - private static final int STATE_NORMAL = 0; - private static final int STATE_HAS_MOREKEYS = 1; - - public void setPreviewBackground(final boolean hasMoreKeys, final int position) { - final Drawable background = getBackground(); - if (background == null) { - return; - } - final int hasMoreKeysState = hasMoreKeys ? STATE_HAS_MOREKEYS : STATE_NORMAL; - background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[position][hasMoreKeysState]); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java deleted file mode 100644 index 3eb62e7a6..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard.internal; - -import static com.android.inputmethod.latin.common.Constants.CODE_OUTPUT_TEXT; -import static com.android.inputmethod.latin.common.Constants.CODE_UNSPECIFIED; - -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * The string parser of the key specification. - * - * Each key specification is one of the following: - * - Label optionally followed by keyOutputText (keyLabel|keyOutputText). - * - Label optionally followed by code point (keyLabel|!code/code_name). - * - Icon followed by keyOutputText (!icon/icon_name|keyOutputText). - * - Icon followed by code point (!icon/icon_name|!code/code_name). - * Label and keyOutputText are one of the following: - * - Literal string. - * - Label reference represented by (!text/label_name), see {@link KeyboardTextsSet}. - * - String resource reference represented by (!text/resource_name), see {@link KeyboardTextsSet}. - * Icon is represented by (!icon/icon_name), see {@link KeyboardIconsSet}. - * Code is one of the following: - * - Code point presented by hexadecimal string prefixed with "0x" - * - Code reference represented by (!code/code_name), see {@link KeyboardCodesSet}. - * Special character, comma ',' backslash '\', and bar '|' can be escaped by '\' character. - * Note that the '\' is also parsed by XML parser and {@link MoreKeySpec#splitKeySpecs(String)} - * as well. - */ -// TODO: Rename to KeySpec and make this class to the key specification object. -public final class KeySpecParser { - // Constants for parsing. - private static final char BACKSLASH = Constants.CODE_BACKSLASH; - private static final char VERTICAL_BAR = Constants.CODE_VERTICAL_BAR; - private static final String PREFIX_HEX = "0x"; - - private KeySpecParser() { - // Intentional empty constructor for utility class. - } - - private static boolean hasIcon(@Nonnull final String keySpec) { - return keySpec.startsWith(KeyboardIconsSet.PREFIX_ICON); - } - - private static boolean hasCode(@Nonnull final String keySpec, final int labelEnd) { - if (labelEnd <= 0 || labelEnd + 1 >= keySpec.length()) { - return false; - } - if (keySpec.startsWith(KeyboardCodesSet.PREFIX_CODE, labelEnd + 1)) { - return true; - } - // This is a workaround to have a key that has a supplementary code point. We can't put a - // string in resource as a XML entity of a supplementary code point or a surrogate pair. - if (keySpec.startsWith(PREFIX_HEX, labelEnd + 1)) { - return true; - } - return false; - } - - @Nonnull - private static String parseEscape(@Nonnull final String text) { - if (text.indexOf(BACKSLASH) < 0) { - return text; - } - final int length = text.length(); - final StringBuilder sb = new StringBuilder(); - for (int pos = 0; pos < length; pos++) { - final char c = text.charAt(pos); - if (c == BACKSLASH && pos + 1 < length) { - // Skip escape char - pos++; - sb.append(text.charAt(pos)); - } else { - sb.append(c); - } - } - return sb.toString(); - } - - private static int indexOfLabelEnd(@Nonnull final String keySpec) { - final int length = keySpec.length(); - if (keySpec.indexOf(BACKSLASH) < 0) { - final int labelEnd = keySpec.indexOf(VERTICAL_BAR); - if (labelEnd == 0) { - if (length == 1) { - // Treat a sole vertical bar as a special case of key label. - return -1; - } - throw new KeySpecParserError("Empty label"); - } - return labelEnd; - } - for (int pos = 0; pos < length; pos++) { - final char c = keySpec.charAt(pos); - if (c == BACKSLASH && pos + 1 < length) { - // Skip escape char - pos++; - } else if (c == VERTICAL_BAR) { - return pos; - } - } - return -1; - } - - @Nonnull - private static String getBeforeLabelEnd(@Nonnull final String keySpec, final int labelEnd) { - return (labelEnd < 0) ? keySpec : keySpec.substring(0, labelEnd); - } - - @Nonnull - private static String getAfterLabelEnd(@Nonnull final String keySpec, final int labelEnd) { - return keySpec.substring(labelEnd + /* VERTICAL_BAR */1); - } - - private static void checkDoubleLabelEnd(@Nonnull final String keySpec, final int labelEnd) { - if (indexOfLabelEnd(getAfterLabelEnd(keySpec, labelEnd)) < 0) { - return; - } - throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec); - } - - @Nullable - public static String getLabel(@Nullable final String keySpec) { - if (keySpec == null) { - // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory. - return null; - } - if (hasIcon(keySpec)) { - return null; - } - final int labelEnd = indexOfLabelEnd(keySpec); - final String label = parseEscape(getBeforeLabelEnd(keySpec, labelEnd)); - if (label.isEmpty()) { - throw new KeySpecParserError("Empty label: " + keySpec); - } - return label; - } - - @Nullable - private static String getOutputTextInternal(@Nonnull final String keySpec, final int labelEnd) { - if (labelEnd <= 0) { - return null; - } - checkDoubleLabelEnd(keySpec, labelEnd); - return parseEscape(getAfterLabelEnd(keySpec, labelEnd)); - } - - @Nullable - public static String getOutputText(@Nullable final String keySpec) { - if (keySpec == null) { - // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory. - return null; - } - final int labelEnd = indexOfLabelEnd(keySpec); - if (hasCode(keySpec, labelEnd)) { - return null; - } - final String outputText = getOutputTextInternal(keySpec, labelEnd); - if (outputText != null) { - if (StringUtils.codePointCount(outputText) == 1) { - // If output text is one code point, it should be treated as a code. - // See {@link #getCode(Resources, String)}. - return null; - } - if (outputText.isEmpty()) { - throw new KeySpecParserError("Empty outputText: " + keySpec); - } - return outputText; - } - final String label = getLabel(keySpec); - if (label == null) { - throw new KeySpecParserError("Empty label: " + keySpec); - } - // Code is automatically generated for one letter label. See {@link getCode()}. - return (StringUtils.codePointCount(label) == 1) ? null : label; - } - - public static int getCode(@Nullable final String keySpec) { - if (keySpec == null) { - // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory. - return CODE_UNSPECIFIED; - } - final int labelEnd = indexOfLabelEnd(keySpec); - if (hasCode(keySpec, labelEnd)) { - checkDoubleLabelEnd(keySpec, labelEnd); - return parseCode(getAfterLabelEnd(keySpec, labelEnd), CODE_UNSPECIFIED); - } - final String outputText = getOutputTextInternal(keySpec, labelEnd); - if (outputText != null) { - // If output text is one code point, it should be treated as a code. - // See {@link #getOutputText(String)}. - if (StringUtils.codePointCount(outputText) == 1) { - return outputText.codePointAt(0); - } - return CODE_OUTPUT_TEXT; - } - final String label = getLabel(keySpec); - if (label == null) { - throw new KeySpecParserError("Empty label: " + keySpec); - } - // Code is automatically generated for one letter label. - return (StringUtils.codePointCount(label) == 1) ? label.codePointAt(0) : CODE_OUTPUT_TEXT; - } - - public static int parseCode(@Nullable final String text, final int defaultCode) { - if (text == null) { - return defaultCode; - } - if (text.startsWith(KeyboardCodesSet.PREFIX_CODE)) { - return KeyboardCodesSet.getCode(text.substring(KeyboardCodesSet.PREFIX_CODE.length())); - } - // This is a workaround to have a key that has a supplementary code point. We can't put a - // string in resource as a XML entity of a supplementary code point or a surrogate pair. - if (text.startsWith(PREFIX_HEX)) { - return Integer.parseInt(text.substring(PREFIX_HEX.length()), 16); - } - return defaultCode; - } - - public static int getIconId(@Nullable final String keySpec) { - if (keySpec == null) { - // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory. - return KeyboardIconsSet.ICON_UNDEFINED; - } - if (!hasIcon(keySpec)) { - return KeyboardIconsSet.ICON_UNDEFINED; - } - final int labelEnd = indexOfLabelEnd(keySpec); - final String iconName = getBeforeLabelEnd(keySpec, labelEnd) - .substring(KeyboardIconsSet.PREFIX_ICON.length()); - return KeyboardIconsSet.getIconId(iconName); - } - - @SuppressWarnings("serial") - public static final class KeySpecParserError extends RuntimeException { - public KeySpecParserError(final String message) { - super(message); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java deleted file mode 100644 index 28aa22c16..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.TypedArray; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public abstract class KeyStyle { - private final KeyboardTextsSet mTextsSet; - - public abstract @Nullable String[] getStringArray(TypedArray a, int index); - public abstract @Nullable String getString(TypedArray a, int index); - public abstract int getInt(TypedArray a, int index, int defaultValue); - public abstract int getFlags(TypedArray a, int index); - - protected KeyStyle(@Nonnull final KeyboardTextsSet textsSet) { - mTextsSet = textsSet; - } - - @Nullable - protected String parseString(final TypedArray a, final int index) { - if (a.hasValue(index)) { - return mTextsSet.resolveTextReference(a.getString(index)); - } - return null; - } - - @Nullable - protected String[] parseStringArray(final TypedArray a, final int index) { - if (a.hasValue(index)) { - final String text = mTextsSet.resolveTextReference(a.getString(index)); - return MoreKeySpec.splitKeySpecs(text); - } - return null; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java deleted file mode 100644 index 61f98c8ff..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.TypedArray; -import android.util.Log; -import android.util.SparseArray; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.XmlParseUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.util.Arrays; -import java.util.HashMap; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public final class KeyStylesSet { - private static final String TAG = KeyStylesSet.class.getSimpleName(); - private static final boolean DEBUG = false; - - @Nonnull - private final HashMap<String, KeyStyle> mStyles = new HashMap<>(); - - @Nonnull - private final KeyboardTextsSet mTextsSet; - @Nonnull - private final KeyStyle mEmptyKeyStyle; - @Nonnull - private static final String EMPTY_STYLE_NAME = "<empty>"; - - public KeyStylesSet(@Nonnull final KeyboardTextsSet textsSet) { - mTextsSet = textsSet; - mEmptyKeyStyle = new EmptyKeyStyle(textsSet); - mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle); - } - - private static final class EmptyKeyStyle extends KeyStyle { - EmptyKeyStyle(@Nonnull final KeyboardTextsSet textsSet) { - super(textsSet); - } - - @Override - @Nullable - public String[] getStringArray(final TypedArray a, final int index) { - return parseStringArray(a, index); - } - - @Override - @Nullable - public String getString(final TypedArray a, final int index) { - return parseString(a, index); - } - - @Override - public int getInt(final TypedArray a, final int index, final int defaultValue) { - return a.getInt(index, defaultValue); - } - - @Override - public int getFlags(final TypedArray a, final int index) { - return a.getInt(index, 0); - } - } - - private static final class DeclaredKeyStyle extends KeyStyle { - private final HashMap<String, KeyStyle> mStyles; - private final String mParentStyleName; - private final SparseArray<Object> mStyleAttributes = new SparseArray<>(); - - public DeclaredKeyStyle(@Nonnull final String parentStyleName, - @Nonnull final KeyboardTextsSet textsSet, - @Nonnull final HashMap<String, KeyStyle> styles) { - super(textsSet); - mParentStyleName = parentStyleName; - mStyles = styles; - } - - @Override - @Nullable - public String[] getStringArray(final TypedArray a, final int index) { - if (a.hasValue(index)) { - return parseStringArray(a, index); - } - final Object value = mStyleAttributes.get(index); - if (value != null) { - final String[] array = (String[])value; - return Arrays.copyOf(array, array.length); - } - final KeyStyle parentStyle = mStyles.get(mParentStyleName); - return parentStyle.getStringArray(a, index); - } - - @Override - @Nullable - public String getString(final TypedArray a, final int index) { - if (a.hasValue(index)) { - return parseString(a, index); - } - final Object value = mStyleAttributes.get(index); - if (value != null) { - return (String)value; - } - final KeyStyle parentStyle = mStyles.get(mParentStyleName); - return parentStyle.getString(a, index); - } - - @Override - public int getInt(final TypedArray a, final int index, final int defaultValue) { - if (a.hasValue(index)) { - return a.getInt(index, defaultValue); - } - final Object value = mStyleAttributes.get(index); - if (value != null) { - return (Integer)value; - } - final KeyStyle parentStyle = mStyles.get(mParentStyleName); - return parentStyle.getInt(a, index, defaultValue); - } - - @Override - public int getFlags(final TypedArray a, final int index) { - final int parentFlags = mStyles.get(mParentStyleName).getFlags(a, index); - final Integer value = (Integer)mStyleAttributes.get(index); - final int styleFlags = (value != null) ? value : 0; - final int flags = a.getInt(index, 0); - return flags | styleFlags | parentFlags; - } - - public void readKeyAttributes(final TypedArray keyAttr) { - // TODO: Currently not all Key attributes can be declared as style. - readString(keyAttr, R.styleable.Keyboard_Key_altCode); - readString(keyAttr, R.styleable.Keyboard_Key_keySpec); - readString(keyAttr, R.styleable.Keyboard_Key_keyHintLabel); - readStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); - readStringArray(keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys); - readFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags); - readString(keyAttr, R.styleable.Keyboard_Key_keyIconDisabled); - readInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn); - readInt(keyAttr, R.styleable.Keyboard_Key_backgroundType); - readFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags); - } - - private void readString(final TypedArray a, final int index) { - if (a.hasValue(index)) { - mStyleAttributes.put(index, parseString(a, index)); - } - } - - private void readInt(final TypedArray a, final int index) { - if (a.hasValue(index)) { - mStyleAttributes.put(index, a.getInt(index, 0)); - } - } - - private void readFlags(final TypedArray a, final int index) { - if (a.hasValue(index)) { - final Integer value = (Integer)mStyleAttributes.get(index); - final int styleFlags = value != null ? value : 0; - mStyleAttributes.put(index, a.getInt(index, 0) | styleFlags); - } - } - - private void readStringArray(final TypedArray a, final int index) { - if (a.hasValue(index)) { - mStyleAttributes.put(index, parseStringArray(a, index)); - } - } - } - - public void parseKeyStyleAttributes(final TypedArray keyStyleAttr, final TypedArray keyAttrs, - final XmlPullParser parser) throws XmlPullParserException { - final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName); - if (styleName == null) { - throw new XmlParseUtils.ParseException( - KeyboardBuilder.TAG_KEY_STYLE + " has no styleName attribute", parser); - } - if (DEBUG) { - Log.d(TAG, String.format("<%s styleName=%s />", - KeyboardBuilder.TAG_KEY_STYLE, styleName)); - if (mStyles.containsKey(styleName)) { - Log.d(TAG, KeyboardBuilder.TAG_KEY_STYLE + " " + styleName + " is overridden at " - + parser.getPositionDescription()); - } - } - - final String parentStyleInAttr = keyStyleAttr.getString( - R.styleable.Keyboard_KeyStyle_parentStyle); - if (parentStyleInAttr != null && !mStyles.containsKey(parentStyleInAttr)) { - throw new XmlParseUtils.ParseException( - "Unknown parentStyle " + parentStyleInAttr, parser); - } - final String parentStyleName = (parentStyleInAttr == null) ? EMPTY_STYLE_NAME - : parentStyleInAttr; - final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName, mTextsSet, mStyles); - style.readKeyAttributes(keyAttrs); - mStyles.put(styleName, style); - } - - @Nonnull - public KeyStyle getKeyStyle(final TypedArray keyAttr, final XmlPullParser parser) - throws XmlParseUtils.ParseException { - final String styleName = keyAttr.getString(R.styleable.Keyboard_Key_keyStyle); - if (styleName == null) { - return mEmptyKeyStyle; - } - final KeyStyle style = mStyles.get(styleName); - if (style == null) { - throw new XmlParseUtils.ParseException("Unknown key style: " + styleName, parser); - } - return style; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java deleted file mode 100644 index 6f000d294..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.TypedArray; -import android.graphics.Typeface; -import android.util.SparseIntArray; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.ResourceUtils; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public final class KeyVisualAttributes { - @Nullable - public final Typeface mTypeface; - - public final float mLetterRatio; - public final int mLetterSize; - public final float mLabelRatio; - public final int mLabelSize; - public final float mLargeLetterRatio; - public final float mHintLetterRatio; - public final float mShiftedLetterHintRatio; - public final float mHintLabelRatio; - public final float mPreviewTextRatio; - - public final int mTextColor; - public final int mTextInactivatedColor; - public final int mTextShadowColor; - public final int mFunctionalTextColor; - public final int mHintLetterColor; - public final int mHintLabelColor; - public final int mShiftedLetterHintInactivatedColor; - public final int mShiftedLetterHintActivatedColor; - public final int mPreviewTextColor; - - public final float mHintLabelVerticalAdjustment; - public final float mLabelOffCenterRatio; - public final float mHintLabelOffCenterRatio; - - private static final int[] VISUAL_ATTRIBUTE_IDS = { - R.styleable.Keyboard_Key_keyTypeface, - R.styleable.Keyboard_Key_keyLetterSize, - R.styleable.Keyboard_Key_keyLabelSize, - R.styleable.Keyboard_Key_keyLargeLetterRatio, - R.styleable.Keyboard_Key_keyHintLetterRatio, - R.styleable.Keyboard_Key_keyShiftedLetterHintRatio, - R.styleable.Keyboard_Key_keyHintLabelRatio, - R.styleable.Keyboard_Key_keyPreviewTextRatio, - R.styleable.Keyboard_Key_keyTextColor, - R.styleable.Keyboard_Key_keyTextInactivatedColor, - R.styleable.Keyboard_Key_keyTextShadowColor, - R.styleable.Keyboard_Key_functionalTextColor, - R.styleable.Keyboard_Key_keyHintLetterColor, - R.styleable.Keyboard_Key_keyHintLabelColor, - R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor, - R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor, - R.styleable.Keyboard_Key_keyPreviewTextColor, - R.styleable.Keyboard_Key_keyHintLabelVerticalAdjustment, - R.styleable.Keyboard_Key_keyLabelOffCenterRatio, - R.styleable.Keyboard_Key_keyHintLabelOffCenterRatio - }; - private static final SparseIntArray sVisualAttributeIds = new SparseIntArray(); - private static final int ATTR_DEFINED = 1; - private static final int ATTR_NOT_FOUND = 0; - static { - for (final int attrId : VISUAL_ATTRIBUTE_IDS) { - sVisualAttributeIds.put(attrId, ATTR_DEFINED); - } - } - - @Nullable - public static KeyVisualAttributes newInstance(@Nonnull final TypedArray keyAttr) { - final int indexCount = keyAttr.getIndexCount(); - for (int i = 0; i < indexCount; i++) { - final int attrId = keyAttr.getIndex(i); - if (sVisualAttributeIds.get(attrId, ATTR_NOT_FOUND) == ATTR_NOT_FOUND) { - continue; - } - return new KeyVisualAttributes(keyAttr); - } - return null; - } - - private KeyVisualAttributes(@Nonnull final TypedArray keyAttr) { - if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyTypeface)) { - mTypeface = Typeface.defaultFromStyle( - keyAttr.getInt(R.styleable.Keyboard_Key_keyTypeface, Typeface.NORMAL)); - } else { - mTypeface = null; - } - - mLetterRatio = ResourceUtils.getFraction(keyAttr, - R.styleable.Keyboard_Key_keyLetterSize); - mLetterSize = ResourceUtils.getDimensionPixelSize(keyAttr, - R.styleable.Keyboard_Key_keyLetterSize); - mLabelRatio = ResourceUtils.getFraction(keyAttr, - R.styleable.Keyboard_Key_keyLabelSize); - mLabelSize = ResourceUtils.getDimensionPixelSize(keyAttr, - R.styleable.Keyboard_Key_keyLabelSize); - mLargeLetterRatio = ResourceUtils.getFraction(keyAttr, - R.styleable.Keyboard_Key_keyLargeLetterRatio); - mHintLetterRatio = ResourceUtils.getFraction(keyAttr, - R.styleable.Keyboard_Key_keyHintLetterRatio); - mShiftedLetterHintRatio = ResourceUtils.getFraction(keyAttr, - R.styleable.Keyboard_Key_keyShiftedLetterHintRatio); - mHintLabelRatio = ResourceUtils.getFraction(keyAttr, - R.styleable.Keyboard_Key_keyHintLabelRatio); - mPreviewTextRatio = ResourceUtils.getFraction(keyAttr, - R.styleable.Keyboard_Key_keyPreviewTextRatio); - - mTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextColor, 0); - mTextInactivatedColor = keyAttr.getColor( - R.styleable.Keyboard_Key_keyTextInactivatedColor, 0); - mTextShadowColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextShadowColor, 0); - mFunctionalTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_functionalTextColor, 0); - mHintLetterColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyHintLetterColor, 0); - mHintLabelColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyHintLabelColor, 0); - mShiftedLetterHintInactivatedColor = keyAttr.getColor( - R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor, 0); - mShiftedLetterHintActivatedColor = keyAttr.getColor( - R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor, 0); - mPreviewTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyPreviewTextColor, 0); - - mHintLabelVerticalAdjustment = ResourceUtils.getFraction(keyAttr, - R.styleable.Keyboard_Key_keyHintLabelVerticalAdjustment, 0.0f); - mLabelOffCenterRatio = ResourceUtils.getFraction(keyAttr, - R.styleable.Keyboard_Key_keyLabelOffCenterRatio, 0.0f); - mHintLabelOffCenterRatio = ResourceUtils.getFraction(keyAttr, - R.styleable.Keyboard_Key_keyHintLabelOffCenterRatio, 0.0f); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java deleted file mode 100644 index 0eabf6cc9..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java +++ /dev/null @@ -1,889 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.os.Build; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.util.Xml; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.keyboard.KeyboardTheme; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.utils.ResourceUtils; -import com.android.inputmethod.latin.utils.XmlParseUtils; -import com.android.inputmethod.latin.utils.XmlParseUtils.ParseException; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Locale; - -import javax.annotation.Nonnull; - -/** - * Keyboard Building helper. - * - * This class parses Keyboard XML file and eventually build a Keyboard. - * The Keyboard XML file looks like: - * <pre> - * <!-- xml/keyboard.xml --> - * <Keyboard keyboard_attributes*> - * <!-- Keyboard Content --> - * <Row row_attributes*> - * <!-- Row Content --> - * <Key key_attributes* /> - * <Spacer horizontalGap="32.0dp" /> - * <include keyboardLayout="@xml/other_keys"> - * ... - * </Row> - * <include keyboardLayout="@xml/other_rows"> - * ... - * </Keyboard> - * </pre> - * The XML file which is included in other file must have <merge> as root element, - * such as: - * <pre> - * <!-- xml/other_keys.xml --> - * <merge> - * <Key key_attributes* /> - * ... - * </merge> - * </pre> - * and - * <pre> - * <!-- xml/other_rows.xml --> - * <merge> - * <Row row_attributes*> - * <Key key_attributes* /> - * </Row> - * ... - * </merge> - * </pre> - * You can also use switch-case-default tags to select Rows and Keys. - * <pre> - * <switch> - * <case case_attribute*> - * <!-- Any valid tags at switch position --> - * </case> - * ... - * <default> - * <!-- Any valid tags at switch position --> - * </default> - * </switch> - * </pre> - * You can declare Key style and specify styles within Key tags. - * <pre> - * <switch> - * <case mode="email"> - * <key-style styleName="f1-key" parentStyle="modifier-key" - * keyLabel=".com" - * /> - * </case> - * <case mode="url"> - * <key-style styleName="f1-key" parentStyle="modifier-key" - * keyLabel="http://" - * /> - * </case> - * </switch> - * ... - * <Key keyStyle="shift-key" ... /> - * </pre> - */ - -// TODO: Write unit tests for this class. -public class KeyboardBuilder<KP extends KeyboardParams> { - private static final String BUILDER_TAG = "Keyboard.Builder"; - private static final boolean DEBUG = false; - - // Keyboard XML Tags - private static final String TAG_KEYBOARD = "Keyboard"; - private static final String TAG_ROW = "Row"; - private static final String TAG_GRID_ROWS = "GridRows"; - private static final String TAG_KEY = "Key"; - private static final String TAG_SPACER = "Spacer"; - private static final String TAG_INCLUDE = "include"; - private static final String TAG_MERGE = "merge"; - private static final String TAG_SWITCH = "switch"; - private static final String TAG_CASE = "case"; - private static final String TAG_DEFAULT = "default"; - public static final String TAG_KEY_STYLE = "key-style"; - - private static final int DEFAULT_KEYBOARD_COLUMNS = 10; - private static final int DEFAULT_KEYBOARD_ROWS = 4; - - @Nonnull - protected final KP mParams; - protected final Context mContext; - protected final Resources mResources; - - private int mCurrentY = 0; - private KeyboardRow mCurrentRow = null; - private boolean mLeftEdge; - private boolean mTopEdge; - private Key mRightEdgeKey = null; - - public KeyboardBuilder(final Context context, @Nonnull final KP params) { - mContext = context; - final Resources res = context.getResources(); - mResources = res; - - mParams = params; - - params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); - params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); - } - - public void setAllowRedundantMoreKes(final boolean enabled) { - mParams.mAllowRedundantMoreKeys = enabled; - } - - public KeyboardBuilder<KP> load(final int xmlId, final KeyboardId id) { - mParams.mId = id; - final XmlResourceParser parser = mResources.getXml(xmlId); - try { - parseKeyboard(parser); - } catch (XmlPullParserException e) { - Log.w(BUILDER_TAG, "keyboard XML parse error", e); - throw new IllegalArgumentException(e.getMessage(), e); - } catch (IOException e) { - Log.w(BUILDER_TAG, "keyboard XML parse error", e); - throw new RuntimeException(e.getMessage(), e); - } finally { - parser.close(); - } - return this; - } - - @UsedForTesting - public void disableTouchPositionCorrectionDataForTest() { - mParams.mTouchPositionCorrection.setEnabled(false); - } - - public void setProximityCharsCorrectionEnabled(final boolean enabled) { - mParams.mProximityCharsCorrectionEnabled = enabled; - } - - @Nonnull - public Keyboard build() { - return new Keyboard(mParams); - } - - private int mIndent; - private static final String SPACES = " "; - - private static String spaces(final int count) { - return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES; - } - - private void startTag(final String format, final Object ... args) { - Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args)); - } - - private void endTag(final String format, final Object ... args) { - Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args)); - } - - private void startEndTag(final String format, final Object ... args) { - Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args)); - mIndent--; - } - - private void parseKeyboard(final XmlPullParser parser) - throws XmlPullParserException, IOException { - if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId); - while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { - final int event = parser.next(); - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_KEYBOARD.equals(tag)) { - parseKeyboardAttributes(parser); - startKeyboard(); - parseKeyboardContent(parser, false); - return; - } - throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD); - } - } - } - - private void parseKeyboardAttributes(final XmlPullParser parser) { - final AttributeSet attr = Xml.asAttributeSet(parser); - final TypedArray keyboardAttr = mContext.obtainStyledAttributes( - attr, R.styleable.Keyboard, R.attr.keyboardStyle, R.style.Keyboard); - final TypedArray keyAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard_Key); - try { - final KeyboardParams params = mParams; - final int height = params.mId.mHeight; - final int width = params.mId.mWidth; - params.mOccupiedHeight = height; - params.mOccupiedWidth = width; - params.mTopPadding = (int)keyboardAttr.getFraction( - R.styleable.Keyboard_keyboardTopPadding, height, height, 0); - params.mBottomPadding = (int)keyboardAttr.getFraction( - R.styleable.Keyboard_keyboardBottomPadding, height, height, 0); - params.mLeftPadding = (int)keyboardAttr.getFraction( - R.styleable.Keyboard_keyboardLeftPadding, width, width, 0); - params.mRightPadding = (int)keyboardAttr.getFraction( - R.styleable.Keyboard_keyboardRightPadding, width, width, 0); - - final int baseWidth = - params.mOccupiedWidth - params.mLeftPadding - params.mRightPadding; - params.mBaseWidth = baseWidth; - params.mDefaultKeyWidth = (int)keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth, - baseWidth, baseWidth, baseWidth / DEFAULT_KEYBOARD_COLUMNS); - params.mHorizontalGap = (int)keyboardAttr.getFraction( - R.styleable.Keyboard_horizontalGap, baseWidth, baseWidth, 0); - // TODO: Fix keyboard geometry calculation clearer. Historically vertical gap between - // rows are determined based on the entire keyboard height including top and bottom - // paddings. - params.mVerticalGap = (int)keyboardAttr.getFraction( - R.styleable.Keyboard_verticalGap, height, height, 0); - final int baseHeight = params.mOccupiedHeight - params.mTopPadding - - params.mBottomPadding + params.mVerticalGap; - params.mBaseHeight = baseHeight; - params.mDefaultRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_rowHeight, baseHeight, baseHeight / DEFAULT_KEYBOARD_ROWS); - - params.mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); - - params.mMoreKeysTemplate = keyboardAttr.getResourceId( - R.styleable.Keyboard_moreKeysTemplate, 0); - params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt( - R.styleable.Keyboard_Key_maxMoreKeysColumn, 5); - - params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0); - params.mIconsSet.loadIcons(keyboardAttr); - params.mTextsSet.setLocale(params.mId.getLocale(), mContext); - - final int resourceId = keyboardAttr.getResourceId( - R.styleable.Keyboard_touchPositionCorrectionData, 0); - if (resourceId != 0) { - final String[] data = mResources.getStringArray(resourceId); - params.mTouchPositionCorrection.load(data); - } - } finally { - keyAttr.recycle(); - keyboardAttr.recycle(); - } - } - - private void parseKeyboardContent(final XmlPullParser parser, final boolean skip) - throws XmlPullParserException, IOException { - while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { - final int event = parser.next(); - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_ROW.equals(tag)) { - final KeyboardRow row = parseRowAttributes(parser); - if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : ""); - if (!skip) { - startRow(row); - } - parseRowContent(parser, row, skip); - } else if (TAG_GRID_ROWS.equals(tag)) { - if (DEBUG) startTag("<%s>%s", TAG_GRID_ROWS, skip ? " skipped" : ""); - parseGridRows(parser, skip); - } else if (TAG_INCLUDE.equals(tag)) { - parseIncludeKeyboardContent(parser, skip); - } else if (TAG_SWITCH.equals(tag)) { - parseSwitchKeyboardContent(parser, skip); - } else if (TAG_KEY_STYLE.equals(tag)) { - parseKeyStyle(parser, skip); - } else { - throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_ROW); - } - } else if (event == XmlPullParser.END_TAG) { - final String tag = parser.getName(); - if (DEBUG) endTag("</%s>", tag); - if (TAG_KEYBOARD.equals(tag)) { - endKeyboard(); - return; - } - if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) || TAG_MERGE.equals(tag)) { - return; - } - throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_ROW); - } - } - } - - private KeyboardRow parseRowAttributes(final XmlPullParser parser) - throws XmlPullParserException { - final AttributeSet attr = Xml.asAttributeSet(parser); - final TypedArray keyboardAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard); - try { - if (keyboardAttr.hasValue(R.styleable.Keyboard_horizontalGap)) { - throw new XmlParseUtils.IllegalAttribute(parser, TAG_ROW, "horizontalGap"); - } - if (keyboardAttr.hasValue(R.styleable.Keyboard_verticalGap)) { - throw new XmlParseUtils.IllegalAttribute(parser, TAG_ROW, "verticalGap"); - } - return new KeyboardRow(mResources, mParams, parser, mCurrentY); - } finally { - keyboardAttr.recycle(); - } - } - - private void parseRowContent(final XmlPullParser parser, final KeyboardRow row, - final boolean skip) throws XmlPullParserException, IOException { - while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { - final int event = parser.next(); - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_KEY.equals(tag)) { - parseKey(parser, row, skip); - } else if (TAG_SPACER.equals(tag)) { - parseSpacer(parser, row, skip); - } else if (TAG_INCLUDE.equals(tag)) { - parseIncludeRowContent(parser, row, skip); - } else if (TAG_SWITCH.equals(tag)) { - parseSwitchRowContent(parser, row, skip); - } else if (TAG_KEY_STYLE.equals(tag)) { - parseKeyStyle(parser, skip); - } else { - throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_ROW); - } - } else if (event == XmlPullParser.END_TAG) { - final String tag = parser.getName(); - if (DEBUG) endTag("</%s>", tag); - if (TAG_ROW.equals(tag)) { - if (!skip) { - endRow(row); - } - return; - } - if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) || TAG_MERGE.equals(tag)) { - return; - } - throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_ROW); - } - } - } - - private void parseGridRows(final XmlPullParser parser, final boolean skip) - throws XmlPullParserException, IOException { - if (skip) { - XmlParseUtils.checkEndTag(TAG_GRID_ROWS, parser); - if (DEBUG) { - startEndTag("<%s /> skipped", TAG_GRID_ROWS); - } - return; - } - final KeyboardRow gridRows = new KeyboardRow(mResources, mParams, parser, mCurrentY); - final TypedArray gridRowAttr = mResources.obtainAttributes( - Xml.asAttributeSet(parser), R.styleable.Keyboard_GridRows); - final int codesArrayId = gridRowAttr.getResourceId( - R.styleable.Keyboard_GridRows_codesArray, 0); - final int textsArrayId = gridRowAttr.getResourceId( - R.styleable.Keyboard_GridRows_textsArray, 0); - gridRowAttr.recycle(); - if (codesArrayId == 0 && textsArrayId == 0) { - throw new XmlParseUtils.ParseException( - "Missing codesArray or textsArray attributes", parser); - } - if (codesArrayId != 0 && textsArrayId != 0) { - throw new XmlParseUtils.ParseException( - "Both codesArray and textsArray attributes specifed", parser); - } - final String[] array = mResources.getStringArray( - codesArrayId != 0 ? codesArrayId : textsArrayId); - final int counts = array.length; - final float keyWidth = gridRows.getKeyWidth(null, 0.0f); - final int numColumns = (int)(mParams.mOccupiedWidth / keyWidth); - for (int index = 0; index < counts; index += numColumns) { - final KeyboardRow row = new KeyboardRow(mResources, mParams, parser, mCurrentY); - startRow(row); - for (int c = 0; c < numColumns; c++) { - final int i = index + c; - if (i >= counts) { - break; - } - final String label; - final int code; - final String outputText; - final int supportedMinSdkVersion; - if (codesArrayId != 0) { - final String codeArraySpec = array[i]; - label = CodesArrayParser.parseLabel(codeArraySpec); - code = CodesArrayParser.parseCode(codeArraySpec); - outputText = CodesArrayParser.parseOutputText(codeArraySpec); - supportedMinSdkVersion = - CodesArrayParser.getMinSupportSdkVersion(codeArraySpec); - } else { - final String textArraySpec = array[i]; - // TODO: Utilize KeySpecParser or write more generic TextsArrayParser. - label = textArraySpec; - code = Constants.CODE_OUTPUT_TEXT; - outputText = textArraySpec + (char)Constants.CODE_SPACE; - supportedMinSdkVersion = 0; - } - if (Build.VERSION.SDK_INT < supportedMinSdkVersion) { - continue; - } - final int labelFlags = row.getDefaultKeyLabelFlags(); - // TODO: Should be able to assign default keyActionFlags as well. - final int backgroundType = row.getDefaultBackgroundType(); - final int x = (int)row.getKeyX(null); - final int y = row.getKeyY(); - final int width = (int)keyWidth; - final int height = row.getRowHeight(); - final Key key = new Key(label, KeyboardIconsSet.ICON_UNDEFINED, code, outputText, - null /* hintLabel */, labelFlags, backgroundType, x, y, width, height, - mParams.mHorizontalGap, mParams.mVerticalGap); - endKey(key); - row.advanceXPos(keyWidth); - } - endRow(row); - } - - XmlParseUtils.checkEndTag(TAG_GRID_ROWS, parser); - } - - private void parseKey(final XmlPullParser parser, final KeyboardRow row, final boolean skip) - throws XmlPullParserException, IOException { - if (skip) { - XmlParseUtils.checkEndTag(TAG_KEY, parser); - if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY); - return; - } - final TypedArray keyAttr = mResources.obtainAttributes( - Xml.asAttributeSet(parser), R.styleable.Keyboard_Key); - final KeyStyle keyStyle = mParams.mKeyStyles.getKeyStyle(keyAttr, parser); - final String keySpec = keyStyle.getString(keyAttr, R.styleable.Keyboard_Key_keySpec); - if (TextUtils.isEmpty(keySpec)) { - throw new ParseException("Empty keySpec", parser); - } - final Key key = new Key(keySpec, keyAttr, keyStyle, mParams, row); - keyAttr.recycle(); - if (DEBUG) { - startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY, (key.isEnabled() ? "" : " disabled"), - key, Arrays.toString(key.getMoreKeys())); - } - XmlParseUtils.checkEndTag(TAG_KEY, parser); - endKey(key); - } - - private void parseSpacer(final XmlPullParser parser, final KeyboardRow row, final boolean skip) - throws XmlPullParserException, IOException { - if (skip) { - XmlParseUtils.checkEndTag(TAG_SPACER, parser); - if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER); - return; - } - final TypedArray keyAttr = mResources.obtainAttributes( - Xml.asAttributeSet(parser), R.styleable.Keyboard_Key); - final KeyStyle keyStyle = mParams.mKeyStyles.getKeyStyle(keyAttr, parser); - final Key spacer = new Key.Spacer(keyAttr, keyStyle, mParams, row); - keyAttr.recycle(); - if (DEBUG) startEndTag("<%s />", TAG_SPACER); - XmlParseUtils.checkEndTag(TAG_SPACER, parser); - endKey(spacer); - } - - private void parseIncludeKeyboardContent(final XmlPullParser parser, final boolean skip) - throws XmlPullParserException, IOException { - parseIncludeInternal(parser, null, skip); - } - - private void parseIncludeRowContent(final XmlPullParser parser, final KeyboardRow row, - final boolean skip) throws XmlPullParserException, IOException { - parseIncludeInternal(parser, row, skip); - } - - private void parseIncludeInternal(final XmlPullParser parser, final KeyboardRow row, - final boolean skip) throws XmlPullParserException, IOException { - if (skip) { - XmlParseUtils.checkEndTag(TAG_INCLUDE, parser); - if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE); - return; - } - final AttributeSet attr = Xml.asAttributeSet(parser); - final TypedArray keyboardAttr = mResources.obtainAttributes( - attr, R.styleable.Keyboard_Include); - final TypedArray keyAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard_Key); - int keyboardLayout = 0; - try { - XmlParseUtils.checkAttributeExists( - keyboardAttr, R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout", - TAG_INCLUDE, parser); - keyboardLayout = keyboardAttr.getResourceId( - R.styleable.Keyboard_Include_keyboardLayout, 0); - if (row != null) { - // Override current x coordinate. - row.setXPos(row.getKeyX(keyAttr)); - // Push current Row attributes and update with new attributes. - row.pushRowAttributes(keyAttr); - } - } finally { - keyboardAttr.recycle(); - keyAttr.recycle(); - } - - XmlParseUtils.checkEndTag(TAG_INCLUDE, parser); - if (DEBUG) { - startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE, - mResources.getResourceEntryName(keyboardLayout)); - } - final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout); - try { - parseMerge(parserForInclude, row, skip); - } finally { - if (row != null) { - // Restore Row attributes. - row.popRowAttributes(); - } - parserForInclude.close(); - } - } - - private void parseMerge(final XmlPullParser parser, final KeyboardRow row, final boolean skip) - throws XmlPullParserException, IOException { - if (DEBUG) startTag("<%s>", TAG_MERGE); - while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { - final int event = parser.next(); - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_MERGE.equals(tag)) { - if (row == null) { - parseKeyboardContent(parser, skip); - } else { - parseRowContent(parser, row, skip); - } - return; - } - throw new XmlParseUtils.ParseException( - "Included keyboard layout must have <merge> root element", parser); - } - } - } - - private void parseSwitchKeyboardContent(final XmlPullParser parser, final boolean skip) - throws XmlPullParserException, IOException { - parseSwitchInternal(parser, null, skip); - } - - private void parseSwitchRowContent(final XmlPullParser parser, final KeyboardRow row, - final boolean skip) throws XmlPullParserException, IOException { - parseSwitchInternal(parser, row, skip); - } - - private void parseSwitchInternal(final XmlPullParser parser, final KeyboardRow row, - final boolean skip) throws XmlPullParserException, IOException { - if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId); - boolean selected = false; - while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { - final int event = parser.next(); - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_CASE.equals(tag)) { - selected |= parseCase(parser, row, selected ? true : skip); - } else if (TAG_DEFAULT.equals(tag)) { - selected |= parseDefault(parser, row, selected ? true : skip); - } else { - throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_SWITCH); - } - } else if (event == XmlPullParser.END_TAG) { - final String tag = parser.getName(); - if (TAG_SWITCH.equals(tag)) { - if (DEBUG) endTag("</%s>", TAG_SWITCH); - return; - } - throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_SWITCH); - } - } - } - - private boolean parseCase(final XmlPullParser parser, final KeyboardRow row, final boolean skip) - throws XmlPullParserException, IOException { - final boolean selected = parseCaseCondition(parser); - if (row == null) { - // Processing Rows. - parseKeyboardContent(parser, selected ? skip : true); - } else { - // Processing Keys. - parseRowContent(parser, row, selected ? skip : true); - } - return selected; - } - - private boolean parseCaseCondition(final XmlPullParser parser) { - final KeyboardId id = mParams.mId; - if (id == null) { - return true; - } - final AttributeSet attr = Xml.asAttributeSet(parser); - final TypedArray caseAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard_Case); - try { - final boolean keyboardLayoutSetMatched = matchString(caseAttr, - R.styleable.Keyboard_Case_keyboardLayoutSet, - id.mSubtype.getKeyboardLayoutSetName()); - final boolean keyboardLayoutSetElementMatched = matchTypedValue(caseAttr, - R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId, - KeyboardId.elementIdToName(id.mElementId)); - final boolean keyboardThemeMacthed = matchTypedValue(caseAttr, - R.styleable.Keyboard_Case_keyboardTheme, mParams.mThemeId, - KeyboardTheme.getKeyboardThemeName(mParams.mThemeId)); - final boolean modeMatched = matchTypedValue(caseAttr, - R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode)); - final boolean navigateNextMatched = matchBoolean(caseAttr, - R.styleable.Keyboard_Case_navigateNext, id.navigateNext()); - final boolean navigatePreviousMatched = matchBoolean(caseAttr, - R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious()); - final boolean passwordInputMatched = matchBoolean(caseAttr, - R.styleable.Keyboard_Case_passwordInput, id.passwordInput()); - final boolean clobberSettingsKeyMatched = matchBoolean(caseAttr, - R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey); - final boolean hasShortcutKeyMatched = matchBoolean(caseAttr, - R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey); - final boolean languageSwitchKeyEnabledMatched = matchBoolean(caseAttr, - R.styleable.Keyboard_Case_languageSwitchKeyEnabled, - id.mLanguageSwitchKeyEnabled); - final boolean isMultiLineMatched = matchBoolean(caseAttr, - R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine()); - final boolean imeActionMatched = matchInteger(caseAttr, - R.styleable.Keyboard_Case_imeAction, id.imeAction()); - final boolean isIconDefinedMatched = isIconDefined(caseAttr, - R.styleable.Keyboard_Case_isIconDefined, mParams.mIconsSet); - final Locale locale = id.getLocale(); - final boolean localeCodeMatched = matchLocaleCodes(caseAttr, locale); - final boolean languageCodeMatched = matchLanguageCodes(caseAttr, locale); - final boolean countryCodeMatched = matchCountryCodes(caseAttr, locale); - final boolean splitLayoutMatched = matchBoolean(caseAttr, - R.styleable.Keyboard_Case_isSplitLayout, id.mIsSplitLayout); - final boolean selected = keyboardLayoutSetMatched && keyboardLayoutSetElementMatched - && keyboardThemeMacthed && modeMatched && navigateNextMatched - && navigatePreviousMatched && passwordInputMatched && clobberSettingsKeyMatched - && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched - && isMultiLineMatched && imeActionMatched && isIconDefinedMatched - && localeCodeMatched && languageCodeMatched && countryCodeMatched - && splitLayoutMatched; - - if (DEBUG) { - startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE, - textAttr(caseAttr.getString( - R.styleable.Keyboard_Case_keyboardLayoutSet), "keyboardLayoutSet"), - textAttr(caseAttr.getString( - R.styleable.Keyboard_Case_keyboardLayoutSetElement), - "keyboardLayoutSetElement"), - textAttr(caseAttr.getString( - R.styleable.Keyboard_Case_keyboardTheme), "keyboardTheme"), - textAttr(caseAttr.getString(R.styleable.Keyboard_Case_mode), "mode"), - textAttr(caseAttr.getString(R.styleable.Keyboard_Case_imeAction), - "imeAction"), - booleanAttr(caseAttr, R.styleable.Keyboard_Case_navigateNext, - "navigateNext"), - booleanAttr(caseAttr, R.styleable.Keyboard_Case_navigatePrevious, - "navigatePrevious"), - booleanAttr(caseAttr, R.styleable.Keyboard_Case_clobberSettingsKey, - "clobberSettingsKey"), - booleanAttr(caseAttr, R.styleable.Keyboard_Case_passwordInput, - "passwordInput"), - booleanAttr(caseAttr, R.styleable.Keyboard_Case_hasShortcutKey, - "hasShortcutKey"), - booleanAttr(caseAttr, R.styleable.Keyboard_Case_languageSwitchKeyEnabled, - "languageSwitchKeyEnabled"), - booleanAttr(caseAttr, R.styleable.Keyboard_Case_isMultiLine, - "isMultiLine"), - booleanAttr(caseAttr, R.styleable.Keyboard_Case_isSplitLayout, - "splitLayout"), - textAttr(caseAttr.getString(R.styleable.Keyboard_Case_isIconDefined), - "isIconDefined"), - textAttr(caseAttr.getString(R.styleable.Keyboard_Case_localeCode), - "localeCode"), - textAttr(caseAttr.getString(R.styleable.Keyboard_Case_languageCode), - "languageCode"), - textAttr(caseAttr.getString(R.styleable.Keyboard_Case_countryCode), - "countryCode"), - selected ? "" : " skipped"); - } - - return selected; - } finally { - caseAttr.recycle(); - } - } - - private static boolean matchLocaleCodes(TypedArray caseAttr, final Locale locale) { - return matchString(caseAttr, R.styleable.Keyboard_Case_localeCode, locale.toString()); - } - - private static boolean matchLanguageCodes(TypedArray caseAttr, Locale locale) { - return matchString(caseAttr, R.styleable.Keyboard_Case_languageCode, locale.getLanguage()); - } - - private static boolean matchCountryCodes(TypedArray caseAttr, Locale locale) { - return matchString(caseAttr, R.styleable.Keyboard_Case_countryCode, locale.getCountry()); - } - - private static boolean matchInteger(final TypedArray a, final int index, final int value) { - // If <case> does not have "index" attribute, that means this <case> is wild-card for - // the attribute. - return !a.hasValue(index) || a.getInt(index, 0) == value; - } - - private static boolean matchBoolean(final TypedArray a, final int index, final boolean value) { - // If <case> does not have "index" attribute, that means this <case> is wild-card for - // the attribute. - return !a.hasValue(index) || a.getBoolean(index, false) == value; - } - - private static boolean matchString(final TypedArray a, final int index, final String value) { - // If <case> does not have "index" attribute, that means this <case> is wild-card for - // the attribute. - return !a.hasValue(index) - || StringUtils.containsInArray(value, a.getString(index).split("\\|")); - } - - private static boolean matchTypedValue(final TypedArray a, final int index, final int intValue, - final String strValue) { - // If <case> does not have "index" attribute, that means this <case> is wild-card for - // the attribute. - final TypedValue v = a.peekValue(index); - if (v == null) { - return true; - } - if (ResourceUtils.isIntegerValue(v)) { - return intValue == a.getInt(index, 0); - } - if (ResourceUtils.isStringValue(v)) { - return StringUtils.containsInArray(strValue, a.getString(index).split("\\|")); - } - return false; - } - - private static boolean isIconDefined(final TypedArray a, final int index, - final KeyboardIconsSet iconsSet) { - if (!a.hasValue(index)) { - return true; - } - final String iconName = a.getString(index); - final int iconId = KeyboardIconsSet.getIconId(iconName); - return iconsSet.getIconDrawable(iconId) != null; - } - - private boolean parseDefault(final XmlPullParser parser, final KeyboardRow row, - final boolean skip) throws XmlPullParserException, IOException { - if (DEBUG) startTag("<%s>", TAG_DEFAULT); - if (row == null) { - parseKeyboardContent(parser, skip); - } else { - parseRowContent(parser, row, skip); - } - return true; - } - - private void parseKeyStyle(final XmlPullParser parser, final boolean skip) - throws XmlPullParserException, IOException { - final AttributeSet attr = Xml.asAttributeSet(parser); - final TypedArray keyStyleAttr = mResources.obtainAttributes( - attr, R.styleable.Keyboard_KeyStyle); - final TypedArray keyAttrs = mResources.obtainAttributes(attr, R.styleable.Keyboard_Key); - try { - if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) { - throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE - + "/> needs styleName attribute", parser); - } - if (DEBUG) { - startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE, - keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName), - skip ? " skipped" : ""); - } - if (!skip) { - mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser); - } - } finally { - keyStyleAttr.recycle(); - keyAttrs.recycle(); - } - XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser); - } - - private void startKeyboard() { - mCurrentY += mParams.mTopPadding; - mTopEdge = true; - } - - private void startRow(final KeyboardRow row) { - addEdgeSpace(mParams.mLeftPadding, row); - mCurrentRow = row; - mLeftEdge = true; - mRightEdgeKey = null; - } - - private void endRow(final KeyboardRow row) { - if (mCurrentRow == null) { - throw new RuntimeException("orphan end row tag"); - } - if (mRightEdgeKey != null) { - mRightEdgeKey.markAsRightEdge(mParams); - mRightEdgeKey = null; - } - addEdgeSpace(mParams.mRightPadding, row); - mCurrentY += row.getRowHeight(); - mCurrentRow = null; - mTopEdge = false; - } - - private void endKey(@Nonnull final Key key) { - mParams.onAddKey(key); - if (mLeftEdge) { - key.markAsLeftEdge(mParams); - mLeftEdge = false; - } - if (mTopEdge) { - key.markAsTopEdge(mParams); - } - mRightEdgeKey = key; - } - - private void endKeyboard() { - mParams.removeRedundantMoreKeys(); - // {@link #parseGridRows(XmlPullParser,boolean)} may populate keyboard rows higher than - // previously expected. - final int actualHeight = mCurrentY - mParams.mVerticalGap + mParams.mBottomPadding; - mParams.mOccupiedHeight = Math.max(mParams.mOccupiedHeight, actualHeight); - } - - private void addEdgeSpace(final float width, final KeyboardRow row) { - row.advanceXPos(width); - mLeftEdge = false; - mRightEdgeKey = null; - } - - private static String textAttr(final String value, final String name) { - return value != null ? String.format(" %s=%s", name, value) : ""; - } - - private static String booleanAttr(final TypedArray a, final int index, final String name) { - return a.hasValue(index) - ? String.format(" %s=%s", name, a.getBoolean(index, false)) : ""; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java deleted file mode 100644 index 05b4c7473..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import com.android.inputmethod.latin.common.Constants; - -import java.util.HashMap; - -public final class KeyboardCodesSet { - public static final String PREFIX_CODE = "!code/"; - - private static final HashMap<String, Integer> sNameToIdMap = new HashMap<>(); - - private KeyboardCodesSet() { - // This utility class is not publicly instantiable. - } - - public static int getCode(final String name) { - Integer id = sNameToIdMap.get(name); - if (id == null) throw new RuntimeException("Unknown key code: " + name); - return DEFAULT[id]; - } - - private static final String[] ID_TO_NAME = { - "key_tab", - "key_enter", - "key_space", - "key_shift", - "key_capslock", - "key_switch_alpha_symbol", - "key_output_text", - "key_delete", - "key_settings", - "key_shortcut", - "key_action_next", - "key_action_previous", - "key_shift_enter", - "key_language_switch", - "key_emoji", - "key_alpha_from_emoji", - "key_unspecified", - }; - - private static final int[] DEFAULT = { - Constants.CODE_TAB, - Constants.CODE_ENTER, - Constants.CODE_SPACE, - Constants.CODE_SHIFT, - Constants.CODE_CAPSLOCK, - Constants.CODE_SWITCH_ALPHA_SYMBOL, - Constants.CODE_OUTPUT_TEXT, - Constants.CODE_DELETE, - Constants.CODE_SETTINGS, - Constants.CODE_SHORTCUT, - Constants.CODE_ACTION_NEXT, - Constants.CODE_ACTION_PREVIOUS, - Constants.CODE_SHIFT_ENTER, - Constants.CODE_LANGUAGE_SWITCH, - Constants.CODE_EMOJI, - Constants.CODE_ALPHA_FROM_EMOJI, - Constants.CODE_UNSPECIFIED, - }; - - static { - for (int i = 0; i < ID_TO_NAME.length; i++) { - sNameToIdMap.put(ID_TO_NAME[i], i); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java deleted file mode 100644 index 15a5bd456..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.util.Log; -import android.util.SparseIntArray; - -import com.android.inputmethod.latin.R; - -import java.util.HashMap; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public final class KeyboardIconsSet { - private static final String TAG = KeyboardIconsSet.class.getSimpleName(); - - public static final String PREFIX_ICON = "!icon/"; - public static final int ICON_UNDEFINED = 0; - private static final int ATTR_UNDEFINED = 0; - - private static final String NAME_UNDEFINED = "undefined"; - public static final String NAME_SHIFT_KEY = "shift_key"; - public static final String NAME_SHIFT_KEY_SHIFTED = "shift_key_shifted"; - public static final String NAME_DELETE_KEY = "delete_key"; - public static final String NAME_SETTINGS_KEY = "settings_key"; - public static final String NAME_SPACE_KEY = "space_key"; - public static final String NAME_SPACE_KEY_FOR_NUMBER_LAYOUT = "space_key_for_number_layout"; - public static final String NAME_ENTER_KEY = "enter_key"; - public static final String NAME_GO_KEY = "go_key"; - public static final String NAME_SEARCH_KEY = "search_key"; - public static final String NAME_SEND_KEY = "send_key"; - public static final String NAME_NEXT_KEY = "next_key"; - public static final String NAME_DONE_KEY = "done_key"; - public static final String NAME_PREVIOUS_KEY = "previous_key"; - public static final String NAME_TAB_KEY = "tab_key"; - public static final String NAME_SHORTCUT_KEY = "shortcut_key"; - public static final String NAME_SHORTCUT_KEY_DISABLED = "shortcut_key_disabled"; - public static final String NAME_LANGUAGE_SWITCH_KEY = "language_switch_key"; - public static final String NAME_ZWNJ_KEY = "zwnj_key"; - public static final String NAME_ZWJ_KEY = "zwj_key"; - public static final String NAME_EMOJI_ACTION_KEY = "emoji_action_key"; - public static final String NAME_EMOJI_NORMAL_KEY = "emoji_normal_key"; - - private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray(); - - // Icon name to icon id map. - private static final HashMap<String, Integer> sNameToIdsMap = new HashMap<>(); - - private static final Object[] NAMES_AND_ATTR_IDS = { - NAME_UNDEFINED, ATTR_UNDEFINED, - NAME_SHIFT_KEY, R.styleable.Keyboard_iconShiftKey, - NAME_DELETE_KEY, R.styleable.Keyboard_iconDeleteKey, - NAME_SETTINGS_KEY, R.styleable.Keyboard_iconSettingsKey, - NAME_SPACE_KEY, R.styleable.Keyboard_iconSpaceKey, - NAME_ENTER_KEY, R.styleable.Keyboard_iconEnterKey, - NAME_GO_KEY, R.styleable.Keyboard_iconGoKey, - NAME_SEARCH_KEY, R.styleable.Keyboard_iconSearchKey, - NAME_SEND_KEY, R.styleable.Keyboard_iconSendKey, - NAME_NEXT_KEY, R.styleable.Keyboard_iconNextKey, - NAME_DONE_KEY, R.styleable.Keyboard_iconDoneKey, - NAME_PREVIOUS_KEY, R.styleable.Keyboard_iconPreviousKey, - NAME_TAB_KEY, R.styleable.Keyboard_iconTabKey, - NAME_SHORTCUT_KEY, R.styleable.Keyboard_iconShortcutKey, - NAME_SPACE_KEY_FOR_NUMBER_LAYOUT, R.styleable.Keyboard_iconSpaceKeyForNumberLayout, - NAME_SHIFT_KEY_SHIFTED, R.styleable.Keyboard_iconShiftKeyShifted, - NAME_SHORTCUT_KEY_DISABLED, R.styleable.Keyboard_iconShortcutKeyDisabled, - NAME_LANGUAGE_SWITCH_KEY, R.styleable.Keyboard_iconLanguageSwitchKey, - NAME_ZWNJ_KEY, R.styleable.Keyboard_iconZwnjKey, - NAME_ZWJ_KEY, R.styleable.Keyboard_iconZwjKey, - NAME_EMOJI_ACTION_KEY, R.styleable.Keyboard_iconEmojiActionKey, - NAME_EMOJI_NORMAL_KEY, R.styleable.Keyboard_iconEmojiNormalKey, - }; - - private static int NUM_ICONS = NAMES_AND_ATTR_IDS.length / 2; - private static final String[] ICON_NAMES = new String[NUM_ICONS]; - private final Drawable[] mIcons = new Drawable[NUM_ICONS]; - private final int[] mIconResourceIds = new int[NUM_ICONS]; - - static { - int iconId = ICON_UNDEFINED; - for (int i = 0; i < NAMES_AND_ATTR_IDS.length; i += 2) { - final String name = (String)NAMES_AND_ATTR_IDS[i]; - final Integer attrId = (Integer)NAMES_AND_ATTR_IDS[i + 1]; - if (attrId != ATTR_UNDEFINED) { - ATTR_ID_TO_ICON_ID.put(attrId, iconId); - } - sNameToIdsMap.put(name, iconId); - ICON_NAMES[iconId] = name; - iconId++; - } - } - - public void loadIcons(final TypedArray keyboardAttrs) { - final int size = ATTR_ID_TO_ICON_ID.size(); - for (int index = 0; index < size; index++) { - final int attrId = ATTR_ID_TO_ICON_ID.keyAt(index); - try { - final Drawable icon = keyboardAttrs.getDrawable(attrId); - setDefaultBounds(icon); - final Integer iconId = ATTR_ID_TO_ICON_ID.get(attrId); - mIcons[iconId] = icon; - mIconResourceIds[iconId] = keyboardAttrs.getResourceId(attrId, 0); - } catch (Resources.NotFoundException e) { - Log.w(TAG, "Drawable resource for icon #" - + keyboardAttrs.getResources().getResourceEntryName(attrId) - + " not found"); - } - } - } - - private static boolean isValidIconId(final int iconId) { - return iconId >= 0 && iconId < ICON_NAMES.length; - } - - @Nonnull - public static String getIconName(final int iconId) { - return isValidIconId(iconId) ? ICON_NAMES[iconId] : "unknown<" + iconId + ">"; - } - - public static int getIconId(final String name) { - Integer iconId = sNameToIdsMap.get(name); - if (iconId != null) { - return iconId; - } - throw new RuntimeException("unknown icon name: " + name); - } - - public int getIconResourceId(final String name) { - final int iconId = getIconId(name); - if (isValidIconId(iconId)) { - return mIconResourceIds[iconId]; - } - throw new RuntimeException("unknown icon name: " + name); - } - - @Nullable - public Drawable getIconDrawable(final int iconId) { - if (isValidIconId(iconId)) { - return mIcons[iconId]; - } - throw new RuntimeException("unknown icon id: " + getIconName(iconId)); - } - - private static void setDefaultBounds(final Drawable icon) { - if (icon != null) { - icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java deleted file mode 100644 index 738d6a400..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.util.SparseIntArray; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.latin.common.Constants; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.SortedSet; -import java.util.TreeSet; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class KeyboardParams { - public KeyboardId mId; - public int mThemeId; - - /** Total height and width of the keyboard, including the paddings and keys */ - public int mOccupiedHeight; - public int mOccupiedWidth; - - /** Base height and width of the keyboard used to calculate rows' or keys' heights and - * widths - */ - public int mBaseHeight; - public int mBaseWidth; - - public int mTopPadding; - public int mBottomPadding; - public int mLeftPadding; - public int mRightPadding; - - @Nullable - public KeyVisualAttributes mKeyVisualAttributes; - - public int mDefaultRowHeight; - public int mDefaultKeyWidth; - public int mHorizontalGap; - public int mVerticalGap; - - public int mMoreKeysTemplate; - public int mMaxMoreKeysKeyboardColumn; - - public int GRID_WIDTH; - public int GRID_HEIGHT; - - // Keys are sorted from top-left to bottom-right order. - @Nonnull - public final SortedSet<Key> mSortedKeys = new TreeSet<>(ROW_COLUMN_COMPARATOR); - @Nonnull - public final ArrayList<Key> mShiftKeys = new ArrayList<>(); - @Nonnull - public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<>(); - @Nonnull - public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); - @Nonnull - public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); - @Nonnull - public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet); - - @Nonnull - private final UniqueKeysCache mUniqueKeysCache; - public boolean mAllowRedundantMoreKeys; - - public int mMostCommonKeyHeight = 0; - public int mMostCommonKeyWidth = 0; - - public boolean mProximityCharsCorrectionEnabled; - - @Nonnull - public final TouchPositionCorrection mTouchPositionCorrection = - new TouchPositionCorrection(); - - // Comparator to sort {@link Key}s from top-left to bottom-right order. - private static final Comparator<Key> ROW_COLUMN_COMPARATOR = new Comparator<Key>() { - @Override - public int compare(final Key lhs, final Key rhs) { - if (lhs.getY() < rhs.getY()) return -1; - if (lhs.getY() > rhs.getY()) return 1; - if (lhs.getX() < rhs.getX()) return -1; - if (lhs.getX() > rhs.getX()) return 1; - return 0; - } - }; - - public KeyboardParams() { - this(UniqueKeysCache.NO_CACHE); - } - - public KeyboardParams(@Nonnull final UniqueKeysCache keysCache) { - mUniqueKeysCache = keysCache; - } - - protected void clearKeys() { - mSortedKeys.clear(); - mShiftKeys.clear(); - clearHistogram(); - } - - public void onAddKey(@Nonnull final Key newKey) { - final Key key = mUniqueKeysCache.getUniqueKey(newKey); - final boolean isSpacer = key.isSpacer(); - if (isSpacer && key.getWidth() == 0) { - // Ignore zero width {@link Spacer}. - return; - } - mSortedKeys.add(key); - if (isSpacer) { - return; - } - updateHistogram(key); - if (key.getCode() == Constants.CODE_SHIFT) { - mShiftKeys.add(key); - } - if (key.altCodeWhileTyping()) { - mAltCodeKeysWhileTyping.add(key); - } - } - - public void removeRedundantMoreKeys() { - if (mAllowRedundantMoreKeys) { - return; - } - final MoreKeySpec.LettersOnBaseLayout lettersOnBaseLayout = - new MoreKeySpec.LettersOnBaseLayout(); - for (final Key key : mSortedKeys) { - lettersOnBaseLayout.addLetter(key); - } - final ArrayList<Key> allKeys = new ArrayList<>(mSortedKeys); - mSortedKeys.clear(); - for (final Key key : allKeys) { - final Key filteredKey = Key.removeRedundantMoreKeys(key, lettersOnBaseLayout); - mSortedKeys.add(mUniqueKeysCache.getUniqueKey(filteredKey)); - } - } - - private int mMaxHeightCount = 0; - private int mMaxWidthCount = 0; - private final SparseIntArray mHeightHistogram = new SparseIntArray(); - private final SparseIntArray mWidthHistogram = new SparseIntArray(); - - private void clearHistogram() { - mMostCommonKeyHeight = 0; - mMaxHeightCount = 0; - mHeightHistogram.clear(); - - mMaxWidthCount = 0; - mMostCommonKeyWidth = 0; - mWidthHistogram.clear(); - } - - private static int updateHistogramCounter(final SparseIntArray histogram, final int key) { - final int index = histogram.indexOfKey(key); - final int count = (index >= 0 ? histogram.get(key) : 0) + 1; - histogram.put(key, count); - return count; - } - - private void updateHistogram(final Key key) { - final int height = key.getHeight() + mVerticalGap; - final int heightCount = updateHistogramCounter(mHeightHistogram, height); - if (heightCount > mMaxHeightCount) { - mMaxHeightCount = heightCount; - mMostCommonKeyHeight = height; - } - - final int width = key.getWidth() + mHorizontalGap; - final int widthCount = updateHistogramCounter(mWidthHistogram, width); - if (widthCount > mMaxWidthCount) { - mMaxWidthCount = widthCount; - mMostCommonKeyWidth = width; - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java deleted file mode 100644 index 92daf0742..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.util.Xml; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.ResourceUtils; - -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayDeque; - -/** - * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. - * Some of the key size defaults can be overridden per row from what the {@link Keyboard} - * defines. - */ -public final class KeyboardRow { - // keyWidth enum constants - private static final int KEYWIDTH_NOT_ENUM = 0; - private static final int KEYWIDTH_FILL_RIGHT = -1; - - private final KeyboardParams mParams; - /** The height of this row. */ - private final int mRowHeight; - - private final ArrayDeque<RowAttributes> mRowAttributesStack = new ArrayDeque<>(); - - // TODO: Add keyActionFlags. - private static class RowAttributes { - /** Default width of a key in this row. */ - public final float mDefaultKeyWidth; - /** Default keyLabelFlags in this row. */ - public final int mDefaultKeyLabelFlags; - /** Default backgroundType for this row */ - public final int mDefaultBackgroundType; - - /** - * Parse and create key attributes. This constructor is used to parse Row tag. - * - * @param keyAttr an attributes array of Row tag. - * @param defaultKeyWidth a default key width. - * @param keyboardWidth the keyboard width that is required to calculate keyWidth attribute. - */ - public RowAttributes(final TypedArray keyAttr, final float defaultKeyWidth, - final int keyboardWidth) { - mDefaultKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth, - keyboardWidth, keyboardWidth, defaultKeyWidth); - mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0); - mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType, - Key.BACKGROUND_TYPE_NORMAL); - } - - /** - * Parse and update key attributes using default attributes. This constructor is used - * to parse include tag. - * - * @param keyAttr an attributes array of include tag. - * @param defaultRowAttr default Row attributes. - * @param keyboardWidth the keyboard width that is required to calculate keyWidth attribute. - */ - public RowAttributes(final TypedArray keyAttr, final RowAttributes defaultRowAttr, - final int keyboardWidth) { - mDefaultKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth, - keyboardWidth, keyboardWidth, defaultRowAttr.mDefaultKeyWidth); - mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0) - | defaultRowAttr.mDefaultKeyLabelFlags; - mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType, - defaultRowAttr.mDefaultBackgroundType); - } - } - - private final int mCurrentY; - // Will be updated by {@link Key}'s constructor. - private float mCurrentX; - - public KeyboardRow(final Resources res, final KeyboardParams params, - final XmlPullParser parser, final int y) { - mParams = params; - final TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.Keyboard); - mRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_rowHeight, params.mBaseHeight, params.mDefaultRowHeight); - keyboardAttr.recycle(); - final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.Keyboard_Key); - mRowAttributesStack.push(new RowAttributes( - keyAttr, params.mDefaultKeyWidth, params.mBaseWidth)); - keyAttr.recycle(); - - mCurrentY = y; - mCurrentX = 0.0f; - } - - public int getRowHeight() { - return mRowHeight; - } - - public void pushRowAttributes(final TypedArray keyAttr) { - final RowAttributes newAttributes = new RowAttributes( - keyAttr, mRowAttributesStack.peek(), mParams.mBaseWidth); - mRowAttributesStack.push(newAttributes); - } - - public void popRowAttributes() { - mRowAttributesStack.pop(); - } - - public float getDefaultKeyWidth() { - return mRowAttributesStack.peek().mDefaultKeyWidth; - } - - public int getDefaultKeyLabelFlags() { - return mRowAttributesStack.peek().mDefaultKeyLabelFlags; - } - - public int getDefaultBackgroundType() { - return mRowAttributesStack.peek().mDefaultBackgroundType; - } - - public void setXPos(final float keyXPos) { - mCurrentX = keyXPos; - } - - public void advanceXPos(final float width) { - mCurrentX += width; - } - - public int getKeyY() { - return mCurrentY; - } - - public float getKeyX(final TypedArray keyAttr) { - if (keyAttr == null || !keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) { - return mCurrentX; - } - final float keyXPos = keyAttr.getFraction(R.styleable.Keyboard_Key_keyXPos, - mParams.mBaseWidth, mParams.mBaseWidth, 0); - if (keyXPos >= 0) { - return keyXPos + mParams.mLeftPadding; - } - // If keyXPos is negative, the actual x-coordinate will be - // keyboardWidth + keyXPos. - // keyXPos shouldn't be less than mCurrentX because drawable area for this - // key starts at mCurrentX. Or, this key will overlaps the adjacent key on - // its left hand side. - final int keyboardRightEdge = mParams.mOccupiedWidth - mParams.mRightPadding; - return Math.max(keyXPos + keyboardRightEdge, mCurrentX); - } - - public float getKeyWidth(final TypedArray keyAttr, final float keyXPos) { - if (keyAttr == null) { - return getDefaultKeyWidth(); - } - final int widthType = ResourceUtils.getEnumValue(keyAttr, - R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM); - switch (widthType) { - case KEYWIDTH_FILL_RIGHT: - // If keyWidth is fillRight, the actual key width will be determined to fill - // out the area up to the right edge of the keyboard. - final int keyboardRightEdge = mParams.mOccupiedWidth - mParams.mRightPadding; - return keyboardRightEdge - keyXPos; - default: // KEYWIDTH_NOT_ENUM - return keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth, - mParams.mBaseWidth, mParams.mBaseWidth, getDefaultKeyWidth()); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java deleted file mode 100644 index 973e956db..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ /dev/null @@ -1,711 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.keyboard.internal; - -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.event.Event; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.utils.CapsModeUtils; -import com.android.inputmethod.latin.utils.RecapitalizeStatus; - -/** - * Keyboard state machine. - * - * This class contains all keyboard state transition logic. - * - * The input events are {@link #onLoadKeyboard(int, int)}, {@link #onSaveKeyboardState()}, - * {@link #onPressKey(int,boolean,int,int)}, {@link #onReleaseKey(int,boolean,int,int)}, - * {@link #onEvent(Event,int,int)}, {@link #onFinishSlidingInput(int,int)}, - * {@link #onUpdateShiftState(int,int)}, {@link #onResetKeyboardStateToAlphabet(int,int)}. - * - * The actions are {@link SwitchActions}'s methods. - */ -public final class KeyboardState { - private static final String TAG = KeyboardState.class.getSimpleName(); - private static final boolean DEBUG_EVENT = false; - private static final boolean DEBUG_INTERNAL_ACTION = false; - - public interface SwitchActions { - public static final boolean DEBUG_ACTION = false; - - public void setAlphabetKeyboard(); - public void setAlphabetManualShiftedKeyboard(); - public void setAlphabetAutomaticShiftedKeyboard(); - public void setAlphabetShiftLockedKeyboard(); - public void setAlphabetShiftLockShiftedKeyboard(); - public void setEmojiKeyboard(); - public void setSymbolsKeyboard(); - public void setSymbolsShiftedKeyboard(); - - /** - * Request to call back {@link KeyboardState#onUpdateShiftState(int, int)}. - */ - public void requestUpdatingShiftState(final int autoCapsFlags, final int recapitalizeMode); - - public static final boolean DEBUG_TIMER_ACTION = false; - - public void startDoubleTapShiftKeyTimer(); - public boolean isInDoubleTapShiftKeyTimeout(); - public void cancelDoubleTapShiftKeyTimer(); - } - - private final SwitchActions mSwitchActions; - - private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift"); - private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol"); - - // TODO: Merge {@link #mSwitchState}, {@link #mIsAlphabetMode}, {@link #mAlphabetShiftState}, - // {@link #mIsSymbolShifted}, {@link #mPrevMainKeyboardWasShiftLocked}, and - // {@link #mPrevSymbolsKeyboardWasShifted} into single state variable. - private static final int SWITCH_STATE_ALPHA = 0; - private static final int SWITCH_STATE_SYMBOL_BEGIN = 1; - private static final int SWITCH_STATE_SYMBOL = 2; - private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3; - private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4; - private static final int SWITCH_STATE_MOMENTARY_ALPHA_SHIFT = 5; - private int mSwitchState = SWITCH_STATE_ALPHA; - - // TODO: Consolidate these two mode booleans into one integer to distinguish between alphabet, - // symbols, and emoji mode. - private boolean mIsAlphabetMode; - private boolean mIsEmojiMode; - private AlphabetShiftState mAlphabetShiftState = new AlphabetShiftState(); - private boolean mIsSymbolShifted; - private boolean mPrevMainKeyboardWasShiftLocked; - private boolean mPrevSymbolsKeyboardWasShifted; - private int mRecapitalizeMode; - - // For handling double tap. - private boolean mIsInAlphabetUnshiftedFromShifted; - private boolean mIsInDoubleTapShiftKey; - - private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState(); - - static final class SavedKeyboardState { - public boolean mIsValid; - public boolean mIsAlphabetMode; - public boolean mIsAlphabetShiftLocked; - public boolean mIsEmojiMode; - public int mShiftMode; - - @Override - public String toString() { - if (!mIsValid) { - return "INVALID"; - } - if (mIsAlphabetMode) { - return mIsAlphabetShiftLocked ? "ALPHABET_SHIFT_LOCKED" - : "ALPHABET_" + shiftModeToString(mShiftMode); - } - if (mIsEmojiMode) { - return "EMOJI"; - } - return "SYMBOLS_" + shiftModeToString(mShiftMode); - } - } - - public KeyboardState(final SwitchActions switchActions) { - mSwitchActions = switchActions; - mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; - } - - public void onLoadKeyboard(final int autoCapsFlags, final int recapitalizeMode) { - if (DEBUG_EVENT) { - Log.d(TAG, "onLoadKeyboard: " + stateToString(autoCapsFlags, recapitalizeMode)); - } - // Reset alphabet shift state. - mAlphabetShiftState.setShiftLocked(false); - mPrevMainKeyboardWasShiftLocked = false; - mPrevSymbolsKeyboardWasShifted = false; - mShiftKeyState.onRelease(); - mSymbolKeyState.onRelease(); - if (mSavedKeyboardState.mIsValid) { - onRestoreKeyboardState(autoCapsFlags, recapitalizeMode); - mSavedKeyboardState.mIsValid = false; - } else { - // Reset keyboard to alphabet mode. - setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); - } - } - - // Constants for {@link SavedKeyboardState#mShiftMode} and {@link #setShifted(int)}. - private static final int UNSHIFT = 0; - private static final int MANUAL_SHIFT = 1; - private static final int AUTOMATIC_SHIFT = 2; - private static final int SHIFT_LOCK_SHIFTED = 3; - - public void onSaveKeyboardState() { - final SavedKeyboardState state = mSavedKeyboardState; - state.mIsAlphabetMode = mIsAlphabetMode; - state.mIsEmojiMode = mIsEmojiMode; - if (mIsAlphabetMode) { - state.mIsAlphabetShiftLocked = mAlphabetShiftState.isShiftLocked(); - state.mShiftMode = mAlphabetShiftState.isAutomaticShifted() ? AUTOMATIC_SHIFT - : (mAlphabetShiftState.isShiftedOrShiftLocked() ? MANUAL_SHIFT : UNSHIFT); - } else { - state.mIsAlphabetShiftLocked = mPrevMainKeyboardWasShiftLocked; - state.mShiftMode = mIsSymbolShifted ? MANUAL_SHIFT : UNSHIFT; - } - state.mIsValid = true; - if (DEBUG_EVENT) { - Log.d(TAG, "onSaveKeyboardState: saved=" + state + " " + this); - } - } - - private void onRestoreKeyboardState(final int autoCapsFlags, final int recapitalizeMode) { - final SavedKeyboardState state = mSavedKeyboardState; - if (DEBUG_EVENT) { - Log.d(TAG, "onRestoreKeyboardState: saved=" + state - + " " + stateToString(autoCapsFlags, recapitalizeMode)); - } - mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked; - if (state.mIsAlphabetMode) { - setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); - setShiftLocked(state.mIsAlphabetShiftLocked); - if (!state.mIsAlphabetShiftLocked) { - setShifted(state.mShiftMode); - } - return; - } - if (state.mIsEmojiMode) { - setEmojiKeyboard(); - return; - } - // Symbol mode - if (state.mShiftMode == MANUAL_SHIFT) { - setSymbolsShiftedKeyboard(); - } else { - setSymbolsKeyboard(); - } - } - - private void setShifted(final int shiftMode) { - if (DEBUG_INTERNAL_ACTION) { - Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this); - } - if (!mIsAlphabetMode) return; - final int prevShiftMode; - if (mAlphabetShiftState.isAutomaticShifted()) { - prevShiftMode = AUTOMATIC_SHIFT; - } else if (mAlphabetShiftState.isManualShifted()) { - prevShiftMode = MANUAL_SHIFT; - } else { - prevShiftMode = UNSHIFT; - } - switch (shiftMode) { - case AUTOMATIC_SHIFT: - mAlphabetShiftState.setAutomaticShifted(); - if (shiftMode != prevShiftMode) { - mSwitchActions.setAlphabetAutomaticShiftedKeyboard(); - } - break; - case MANUAL_SHIFT: - mAlphabetShiftState.setShifted(true); - if (shiftMode != prevShiftMode) { - mSwitchActions.setAlphabetManualShiftedKeyboard(); - } - break; - case UNSHIFT: - mAlphabetShiftState.setShifted(false); - if (shiftMode != prevShiftMode) { - mSwitchActions.setAlphabetKeyboard(); - } - break; - case SHIFT_LOCK_SHIFTED: - mAlphabetShiftState.setShifted(true); - mSwitchActions.setAlphabetShiftLockShiftedKeyboard(); - break; - } - } - - private void setShiftLocked(final boolean shiftLocked) { - if (DEBUG_INTERNAL_ACTION) { - Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this); - } - if (!mIsAlphabetMode) return; - if (shiftLocked && (!mAlphabetShiftState.isShiftLocked() - || mAlphabetShiftState.isShiftLockShifted())) { - mSwitchActions.setAlphabetShiftLockedKeyboard(); - } - if (!shiftLocked && mAlphabetShiftState.isShiftLocked()) { - mSwitchActions.setAlphabetKeyboard(); - } - mAlphabetShiftState.setShiftLocked(shiftLocked); - } - - private void toggleAlphabetAndSymbols(final int autoCapsFlags, final int recapitalizeMode) { - if (DEBUG_INTERNAL_ACTION) { - Log.d(TAG, "toggleAlphabetAndSymbols: " - + stateToString(autoCapsFlags, recapitalizeMode)); - } - if (mIsAlphabetMode) { - mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); - if (mPrevSymbolsKeyboardWasShifted) { - setSymbolsShiftedKeyboard(); - } else { - setSymbolsKeyboard(); - } - mPrevSymbolsKeyboardWasShifted = false; - } else { - mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted; - setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); - if (mPrevMainKeyboardWasShiftLocked) { - setShiftLocked(true); - } - mPrevMainKeyboardWasShiftLocked = false; - } - } - - // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout - // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal(). - private void resetKeyboardStateToAlphabet(final int autoCapsFlags, final int recapitalizeMode) { - if (DEBUG_INTERNAL_ACTION) { - Log.d(TAG, "resetKeyboardStateToAlphabet: " - + stateToString(autoCapsFlags, recapitalizeMode)); - } - if (mIsAlphabetMode) return; - - mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted; - setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); - if (mPrevMainKeyboardWasShiftLocked) { - setShiftLocked(true); - } - mPrevMainKeyboardWasShiftLocked = false; - } - - private void toggleShiftInSymbols() { - if (mIsSymbolShifted) { - setSymbolsKeyboard(); - } else { - setSymbolsShiftedKeyboard(); - } - } - - private void setAlphabetKeyboard(final int autoCapsFlags, final int recapitalizeMode) { - if (DEBUG_INTERNAL_ACTION) { - Log.d(TAG, "setAlphabetKeyboard: " + stateToString(autoCapsFlags, recapitalizeMode)); - } - - mSwitchActions.setAlphabetKeyboard(); - mIsAlphabetMode = true; - mIsEmojiMode = false; - mIsSymbolShifted = false; - mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; - mSwitchState = SWITCH_STATE_ALPHA; - mSwitchActions.requestUpdatingShiftState(autoCapsFlags, recapitalizeMode); - } - - private void setSymbolsKeyboard() { - if (DEBUG_INTERNAL_ACTION) { - Log.d(TAG, "setSymbolsKeyboard"); - } - mSwitchActions.setSymbolsKeyboard(); - mIsAlphabetMode = false; - mIsSymbolShifted = false; - mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; - // Reset alphabet shift state. - mAlphabetShiftState.setShiftLocked(false); - mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; - } - - private void setSymbolsShiftedKeyboard() { - if (DEBUG_INTERNAL_ACTION) { - Log.d(TAG, "setSymbolsShiftedKeyboard"); - } - mSwitchActions.setSymbolsShiftedKeyboard(); - mIsAlphabetMode = false; - mIsSymbolShifted = true; - mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; - // Reset alphabet shift state. - mAlphabetShiftState.setShiftLocked(false); - mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; - } - - private void setEmojiKeyboard() { - if (DEBUG_INTERNAL_ACTION) { - Log.d(TAG, "setEmojiKeyboard"); - } - mIsAlphabetMode = false; - mIsEmojiMode = true; - mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; - // Remember caps lock mode and reset alphabet shift state. - mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); - mAlphabetShiftState.setShiftLocked(false); - mSwitchActions.setEmojiKeyboard(); - } - - public void onPressKey(final int code, final boolean isSinglePointer, final int autoCapsFlags, - final int recapitalizeMode) { - if (DEBUG_EVENT) { - Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code) - + " single=" + isSinglePointer - + " " + stateToString(autoCapsFlags, recapitalizeMode)); - } - if (code != Constants.CODE_SHIFT) { - // Because the double tap shift key timer is to detect two consecutive shift key press, - // it should be canceled when a non-shift key is pressed. - mSwitchActions.cancelDoubleTapShiftKeyTimer(); - } - if (code == Constants.CODE_SHIFT) { - onPressShift(); - } else if (code == Constants.CODE_CAPSLOCK) { - // Nothing to do here. See {@link #onReleaseKey(int,boolean)}. - } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { - onPressSymbol(autoCapsFlags, recapitalizeMode); - } else { - mShiftKeyState.onOtherKeyPressed(); - mSymbolKeyState.onOtherKeyPressed(); - // It is required to reset the auto caps state when all of the following conditions - // are met: - // 1) two or more fingers are in action - // 2) in alphabet layout - // 3) not in all characters caps mode - // As for #3, please note that it's required to check even when the auto caps mode is - // off because, for example, we may be in the #1 state within the manual temporary - // shifted mode. - if (!isSinglePointer && mIsAlphabetMode - && autoCapsFlags != TextUtils.CAP_MODE_CHARACTERS) { - final boolean needsToResetAutoCaps = mAlphabetShiftState.isAutomaticShifted() - || (mAlphabetShiftState.isManualShifted() && mShiftKeyState.isReleasing()); - if (needsToResetAutoCaps) { - mSwitchActions.setAlphabetKeyboard(); - } - } - } - } - - public void onReleaseKey(final int code, final boolean withSliding, final int autoCapsFlags, - final int recapitalizeMode) { - if (DEBUG_EVENT) { - Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code) - + " sliding=" + withSliding - + " " + stateToString(autoCapsFlags, recapitalizeMode)); - } - if (code == Constants.CODE_SHIFT) { - onReleaseShift(withSliding, autoCapsFlags, recapitalizeMode); - } else if (code == Constants.CODE_CAPSLOCK) { - setShiftLocked(!mAlphabetShiftState.isShiftLocked()); - } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { - onReleaseSymbol(withSliding, autoCapsFlags, recapitalizeMode); - } - } - - private void onPressSymbol(final int autoCapsFlags, - final int recapitalizeMode) { - toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode); - mSymbolKeyState.onPress(); - mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL; - } - - private void onReleaseSymbol(final boolean withSliding, final int autoCapsFlags, - final int recapitalizeMode) { - if (mSymbolKeyState.isChording()) { - // Switch back to the previous keyboard mode if the user chords the mode change key and - // another key, then releases the mode change key. - toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode); - } else if (!withSliding) { - // If the mode change key is being released without sliding, we should forget the - // previous symbols keyboard shift state and simply switch back to symbols layout - // (never symbols shifted) next time the mode gets changed to symbols layout. - mPrevSymbolsKeyboardWasShifted = false; - } - mSymbolKeyState.onRelease(); - } - - public void onUpdateShiftState(final int autoCapsFlags, final int recapitalizeMode) { - if (DEBUG_EVENT) { - Log.d(TAG, "onUpdateShiftState: " + stateToString(autoCapsFlags, recapitalizeMode)); - } - mRecapitalizeMode = recapitalizeMode; - updateAlphabetShiftState(autoCapsFlags, recapitalizeMode); - } - - // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout - // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal(). - public void onResetKeyboardStateToAlphabet(final int autoCapsFlags, - final int recapitalizeMode) { - if (DEBUG_EVENT) { - Log.d(TAG, "onResetKeyboardStateToAlphabet: " - + stateToString(autoCapsFlags, recapitalizeMode)); - } - resetKeyboardStateToAlphabet(autoCapsFlags, recapitalizeMode); - } - - private void updateShiftStateForRecapitalize(final int recapitalizeMode) { - switch (recapitalizeMode) { - case RecapitalizeStatus.CAPS_MODE_ALL_UPPER: - setShifted(SHIFT_LOCK_SHIFTED); - break; - case RecapitalizeStatus.CAPS_MODE_FIRST_WORD_UPPER: - setShifted(AUTOMATIC_SHIFT); - break; - case RecapitalizeStatus.CAPS_MODE_ALL_LOWER: - case RecapitalizeStatus.CAPS_MODE_ORIGINAL_MIXED_CASE: - default: - setShifted(UNSHIFT); - } - } - - private void updateAlphabetShiftState(final int autoCapsFlags, final int recapitalizeMode) { - if (!mIsAlphabetMode) return; - if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != recapitalizeMode) { - // We are recapitalizing. Match the keyboard to the current recapitalize state. - updateShiftStateForRecapitalize(recapitalizeMode); - return; - } - if (!mShiftKeyState.isReleasing()) { - // Ignore update shift state event while the shift key is being pressed (including - // chording). - return; - } - if (!mAlphabetShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) { - if (mShiftKeyState.isReleasing() && autoCapsFlags != Constants.TextUtils.CAP_MODE_OFF) { - // Only when shift key is releasing, automatic temporary upper case will be set. - setShifted(AUTOMATIC_SHIFT); - } else { - setShifted(mShiftKeyState.isChording() ? MANUAL_SHIFT : UNSHIFT); - } - } - } - - private void onPressShift() { - // If we are recapitalizing, we don't do any of the normal processing, including - // importantly the double tap timer. - if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) { - return; - } - if (mIsAlphabetMode) { - mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapShiftKeyTimeout(); - if (!mIsInDoubleTapShiftKey) { - // This is first tap. - mSwitchActions.startDoubleTapShiftKeyTimer(); - } - if (mIsInDoubleTapShiftKey) { - if (mAlphabetShiftState.isManualShifted() || mIsInAlphabetUnshiftedFromShifted) { - // Shift key has been double tapped while in manual shifted or automatic - // shifted state. - setShiftLocked(true); - } else { - // Shift key has been double tapped while in normal state. This is the second - // tap to disable shift locked state, so just ignore this. - } - } else { - if (mAlphabetShiftState.isShiftLocked()) { - // Shift key is pressed while shift locked state, we will treat this state as - // shift lock shifted state and mark as if shift key pressed while normal - // state. - setShifted(SHIFT_LOCK_SHIFTED); - mShiftKeyState.onPress(); - } else if (mAlphabetShiftState.isAutomaticShifted()) { - // Shift key is pressed while automatic shifted, we have to move to manual - // shifted. - setShifted(MANUAL_SHIFT); - mShiftKeyState.onPress(); - } else if (mAlphabetShiftState.isShiftedOrShiftLocked()) { - // In manual shifted state, we just record shift key has been pressing while - // shifted state. - mShiftKeyState.onPressOnShifted(); - } else { - // In base layout, chording or manual shifted mode is started. - setShifted(MANUAL_SHIFT); - mShiftKeyState.onPress(); - } - } - } else { - // In symbol mode, just toggle symbol and symbol more keyboard. - toggleShiftInSymbols(); - mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; - mShiftKeyState.onPress(); - } - } - - private void onReleaseShift(final boolean withSliding, final int autoCapsFlags, - final int recapitalizeMode) { - if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) { - // We are recapitalizing. We should match the keyboard state to the recapitalize - // state in priority. - updateShiftStateForRecapitalize(mRecapitalizeMode); - } else if (mIsAlphabetMode) { - final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked(); - mIsInAlphabetUnshiftedFromShifted = false; - if (mIsInDoubleTapShiftKey) { - // Double tap shift key has been handled in {@link #onPressShift}, so that just - // ignore this release shift key here. - mIsInDoubleTapShiftKey = false; - } else if (mShiftKeyState.isChording()) { - if (mAlphabetShiftState.isShiftLockShifted()) { - // After chording input while shift locked state. - setShiftLocked(true); - } else { - // After chording input while normal state. - setShifted(UNSHIFT); - } - // After chording input, automatic shift state may have been changed depending on - // what characters were input. - mShiftKeyState.onRelease(); - mSwitchActions.requestUpdatingShiftState(autoCapsFlags, recapitalizeMode); - return; - } else if (mAlphabetShiftState.isShiftLockShifted() && withSliding) { - // In shift locked state, shift has been pressed and slid out to other key. - setShiftLocked(true); - } else if (mAlphabetShiftState.isManualShifted() && withSliding) { - // Shift has been pressed and slid out to other key. - mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_SHIFT; - } else if (isShiftLocked && !mAlphabetShiftState.isShiftLockShifted() - && (mShiftKeyState.isPressing() || mShiftKeyState.isPressingOnShifted()) - && !withSliding) { - // Shift has been long pressed, ignore this release. - } else if (isShiftLocked && !mShiftKeyState.isIgnoring() && !withSliding) { - // Shift has been pressed without chording while shift locked state. - setShiftLocked(false); - } else if (mAlphabetShiftState.isShiftedOrShiftLocked() - && mShiftKeyState.isPressingOnShifted() && !withSliding) { - // Shift has been pressed without chording while shifted state. - setShifted(UNSHIFT); - mIsInAlphabetUnshiftedFromShifted = true; - } else if (mAlphabetShiftState.isManualShiftedFromAutomaticShifted() - && mShiftKeyState.isPressing() && !withSliding) { - // Shift has been pressed without chording while manual shifted transited from - // automatic shifted - setShifted(UNSHIFT); - mIsInAlphabetUnshiftedFromShifted = true; - } - } else { - // In symbol mode, switch back to the previous keyboard mode if the user chords the - // shift key and another key, then releases the shift key. - if (mShiftKeyState.isChording()) { - toggleShiftInSymbols(); - } - } - mShiftKeyState.onRelease(); - } - - public void onFinishSlidingInput(final int autoCapsFlags, final int recapitalizeMode) { - if (DEBUG_EVENT) { - Log.d(TAG, "onFinishSlidingInput: " + stateToString(autoCapsFlags, recapitalizeMode)); - } - // Switch back to the previous keyboard mode if the user cancels sliding input. - switch (mSwitchState) { - case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: - toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode); - break; - case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: - toggleShiftInSymbols(); - break; - case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT: - setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); - break; - } - } - - private static boolean isSpaceOrEnter(final int c) { - return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER; - } - - public void onEvent(final Event event, final int autoCapsFlags, final int recapitalizeMode) { - final int code = event.isFunctionalKeyEvent() ? event.mKeyCode : event.mCodePoint; - if (DEBUG_EVENT) { - Log.d(TAG, "onEvent: code=" + Constants.printableCode(code) - + " " + stateToString(autoCapsFlags, recapitalizeMode)); - } - - switch (mSwitchState) { - case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: - if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { - // Detected only the mode change key has been pressed, and then released. - if (mIsAlphabetMode) { - mSwitchState = SWITCH_STATE_ALPHA; - } else { - mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; - } - } - break; - case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: - if (code == Constants.CODE_SHIFT) { - // Detected only the shift key has been pressed on symbol layout, and then - // released. - mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; - } - break; - case SWITCH_STATE_SYMBOL_BEGIN: - if (mIsEmojiMode) { - // When in the Emoji keyboard, we don't want to switch back to the main layout even - // after the user hits an emoji letter followed by an enter or a space. - break; - } - if (!isSpaceOrEnter(code) && (Constants.isLetterCode(code) - || code == Constants.CODE_OUTPUT_TEXT)) { - mSwitchState = SWITCH_STATE_SYMBOL; - } - break; - case SWITCH_STATE_SYMBOL: - // Switch back to alpha keyboard mode if user types one or more non-space/enter - // characters followed by a space/enter. - if (isSpaceOrEnter(code)) { - toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode); - mPrevSymbolsKeyboardWasShifted = false; - } - break; - } - - // If the code is a letter, update keyboard shift state. - if (Constants.isLetterCode(code)) { - updateAlphabetShiftState(autoCapsFlags, recapitalizeMode); - } else if (code == Constants.CODE_EMOJI) { - setEmojiKeyboard(); - } else if (code == Constants.CODE_ALPHA_FROM_EMOJI) { - setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); - } - } - - static String shiftModeToString(final int shiftMode) { - switch (shiftMode) { - case UNSHIFT: return "UNSHIFT"; - case MANUAL_SHIFT: return "MANUAL"; - case AUTOMATIC_SHIFT: return "AUTOMATIC"; - default: return null; - } - } - - private static String switchStateToString(final int switchState) { - switch (switchState) { - case SWITCH_STATE_ALPHA: return "ALPHA"; - case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN"; - case SWITCH_STATE_SYMBOL: return "SYMBOL"; - case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: return "MOMENTARY-ALPHA-SYMBOL"; - case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: return "MOMENTARY-SYMBOL-MORE"; - case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT: return "MOMENTARY-ALPHA_SHIFT"; - default: return null; - } - } - - @Override - public String toString() { - return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString() - : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS")) - + " shift=" + mShiftKeyState - + " symbol=" + mSymbolKeyState - + " switch=" + switchStateToString(mSwitchState) + "]"; - } - - private String stateToString(final int autoCapsFlags, final int recapitalizeMode) { - return this + " autoCapsFlags=" + CapsModeUtils.flagsToString(autoCapsFlags) - + " recapitalizeMode=" + RecapitalizeStatus.modeToString(recapitalizeMode); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java deleted file mode 100644 index 0aaf6b401..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.content.Context; -import android.content.res.Resources; -import android.text.TextUtils; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.utils.RunInLocale; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; - -import java.util.Locale; - -// TODO: Make this an immutable class. -public final class KeyboardTextsSet { - public static final String PREFIX_TEXT = "!text/"; - private static final String PREFIX_RESOURCE = "!string/"; - public static final String SWITCH_TO_ALPHA_KEY_LABEL = "keylabel_to_alpha"; - - private static final char BACKSLASH = Constants.CODE_BACKSLASH; - private static final int MAX_REFERENCE_INDIRECTION = 10; - - private Resources mResources; - private Locale mResourceLocale; - private String mResourcePackageName; - private String[] mTextsTable; - - public void setLocale(final Locale locale, final Context context) { - final Resources res = context.getResources(); - // Null means the current system locale. - final String resourcePackageName = res.getResourcePackageName( - context.getApplicationInfo().labelRes); - setLocale(locale, res, resourcePackageName); - } - - @UsedForTesting - public void setLocale(final Locale locale, final Resources res, - final String resourcePackageName) { - mResources = res; - // Null means the current system locale. - mResourceLocale = SubtypeLocaleUtils.NO_LANGUAGE.equals(locale.toString()) ? null : locale; - mResourcePackageName = resourcePackageName; - mTextsTable = KeyboardTextsTable.getTextsTable(locale); - } - - public String getText(final String name) { - return KeyboardTextsTable.getText(name, mTextsTable); - } - - private static int searchTextNameEnd(final String text, final int start) { - final int size = text.length(); - for (int pos = start; pos < size; pos++) { - final char c = text.charAt(pos); - // Label name should be consisted of [a-zA-Z_0-9]. - if ((c >= 'a' && c <= 'z') || c == '_' || (c >= '0' && c <= '9')) { - continue; - } - return pos; - } - return size; - } - - // TODO: Resolve text reference when creating {@link KeyboardTextsTable} class. - public String resolveTextReference(final String rawText) { - if (TextUtils.isEmpty(rawText)) { - return null; - } - int level = 0; - String text = rawText; - StringBuilder sb; - do { - level++; - if (level >= MAX_REFERENCE_INDIRECTION) { - throw new RuntimeException("Too many " + PREFIX_TEXT + " or " + PREFIX_RESOURCE + - " reference indirection: " + text); - } - - final int prefixLength = PREFIX_TEXT.length(); - final int size = text.length(); - if (size < prefixLength) { - break; - } - - sb = null; - for (int pos = 0; pos < size; pos++) { - final char c = text.charAt(pos); - if (text.startsWith(PREFIX_TEXT, pos)) { - if (sb == null) { - sb = new StringBuilder(text.substring(0, pos)); - } - pos = expandReference(text, pos, PREFIX_TEXT, sb); - } else if (text.startsWith(PREFIX_RESOURCE, pos)) { - if (sb == null) { - sb = new StringBuilder(text.substring(0, pos)); - } - pos = expandReference(text, pos, PREFIX_RESOURCE, sb); - } else if (c == BACKSLASH) { - if (sb != null) { - // Append both escape character and escaped character. - sb.append(text.substring(pos, Math.min(pos + 2, size))); - } - pos++; - } else if (sb != null) { - sb.append(c); - } - } - - if (sb != null) { - text = sb.toString(); - } - } while (sb != null); - return TextUtils.isEmpty(text) ? null : text; - } - - private int expandReference(final String text, final int pos, final String prefix, - final StringBuilder sb) { - final int prefixLength = prefix.length(); - final int end = searchTextNameEnd(text, pos + prefixLength); - final String name = text.substring(pos + prefixLength, end); - if (prefix.equals(PREFIX_TEXT)) { - sb.append(getText(name)); - } else { // PREFIX_RESOURCE - final String resourcePackageName = mResourcePackageName; - final RunInLocale<String> getTextJob = new RunInLocale<String>() { - @Override - protected String job(final Resources res) { - final int resId = res.getIdentifier(name, "string", resourcePackageName); - return res.getString(resId); - } - }; - sb.append(getTextJob.runInLocale(mResources, mResourceLocale)); - } - return end - 1; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java deleted file mode 100644 index 7dfb5328c..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java +++ /dev/null @@ -1,4198 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.keyboard.internal; - -import java.util.HashMap; -import java.util.Locale; - -/** - * !!!!! DO NOT EDIT THIS FILE !!!!! - * - * This file is generated by tools/make-keyboard-text. The base template file is - * tools/make-keyboard-text/res/src/com/android/inputmethod/keyboard/internal/ - * KeyboardTextsTable.tmpl - * - * This file must be updated when any text resources in keyboard layout files have been changed. - * These text resources are referred as "!text/<resource_name>" in keyboard XML definitions, - * and should be defined in - * tools/make-keyboard-text/res/values-<locale>/donottranslate-more-keys.xml - * - * To update this file, please run the following commands. - * $ cd $ANDROID_BUILD_TOP - * $ mmm packages/inputmethods/LatinIME/tools/make-keyboard-text - * $ make-keyboard-text -java packages/inputmethods/LatinIME/java - * - * The updated source file will be generated to the following path (this file). - * packages/inputmethods/LatinIME/java/src/com/android/inputmethod/keyboard/internal/ - * KeyboardTextsTable.java - */ -public final class KeyboardTextsTable { - // Name to index map. - private static final HashMap<String, Integer> sNameToIndexesMap = new HashMap<>(); - // Locale to texts table map. - private static final HashMap<String, String[]> sLocaleToTextsTableMap = new HashMap<>(); - // TODO: Remove this variable after debugging. - // Texts table to locale maps. - private static final HashMap<String[], String> sTextsTableToLocaleMap = new HashMap<>(); - - public static String getText(final String name, final String[] textsTable) { - final Integer indexObj = sNameToIndexesMap.get(name); - if (indexObj == null) { - throw new RuntimeException("Unknown text name=" + name + " locale=" - + sTextsTableToLocaleMap.get(textsTable)); - } - final int index = indexObj; - final String text = (index < textsTable.length) ? textsTable[index] : null; - if (text != null) { - return text; - } - // Validity check. - if (index >= 0 && index < TEXTS_DEFAULT.length) { - return TEXTS_DEFAULT[index]; - } - // Throw exception for debugging purpose. - throw new RuntimeException("Illegal index=" + index + " for name=" + name - + " locale=" + sTextsTableToLocaleMap.get(textsTable)); - } - - public static String[] getTextsTable(final Locale locale) { - final String localeKey = locale.toString(); - if (sLocaleToTextsTableMap.containsKey(localeKey)) { - return sLocaleToTextsTableMap.get(localeKey); - } - final String languageKey = locale.getLanguage(); - if (sLocaleToTextsTableMap.containsKey(languageKey)) { - return sLocaleToTextsTableMap.get(languageKey); - } - return TEXTS_DEFAULT; - } - - private static final String[] NAMES = { - // /* index:histogram */ "name", - /* 0:33 */ "morekeys_a", - /* 1:33 */ "morekeys_o", - /* 2:32 */ "morekeys_e", - /* 3:31 */ "morekeys_u", - /* 4:31 */ "keylabel_to_alpha", - /* 5:30 */ "morekeys_i", - /* 6:25 */ "morekeys_n", - /* 7:25 */ "morekeys_c", - /* 8:23 */ "double_quotes", - /* 9:22 */ "morekeys_s", - /* 10:22 */ "single_quotes", - /* 11:19 */ "keyspec_currency", - /* 12:17 */ "morekeys_y", - /* 13:16 */ "morekeys_z", - /* 14:14 */ "morekeys_d", - /* 15:10 */ "morekeys_t", - /* 16:10 */ "morekeys_l", - /* 17:10 */ "morekeys_g", - /* 18: 9 */ "single_angle_quotes", - /* 19: 9 */ "double_angle_quotes", - /* 20: 8 */ "morekeys_r", - /* 21: 6 */ "morekeys_k", - /* 22: 6 */ "morekeys_cyrillic_ie", - /* 23: 5 */ "keyspec_nordic_row1_11", - /* 24: 5 */ "keyspec_nordic_row2_10", - /* 25: 5 */ "keyspec_nordic_row2_11", - /* 26: 5 */ "morekeys_nordic_row2_10", - /* 27: 5 */ "keyspec_east_slavic_row1_9", - /* 28: 5 */ "keyspec_east_slavic_row2_2", - /* 29: 5 */ "keyspec_east_slavic_row2_11", - /* 30: 5 */ "keyspec_east_slavic_row3_5", - /* 31: 5 */ "morekeys_cyrillic_soft_sign", - /* 32: 5 */ "keyspec_symbols_1", - /* 33: 5 */ "keyspec_symbols_2", - /* 34: 5 */ "keyspec_symbols_3", - /* 35: 5 */ "keyspec_symbols_4", - /* 36: 5 */ "keyspec_symbols_5", - /* 37: 5 */ "keyspec_symbols_6", - /* 38: 5 */ "keyspec_symbols_7", - /* 39: 5 */ "keyspec_symbols_8", - /* 40: 5 */ "keyspec_symbols_9", - /* 41: 5 */ "keyspec_symbols_0", - /* 42: 5 */ "keylabel_to_symbol", - /* 43: 5 */ "additional_morekeys_symbols_1", - /* 44: 5 */ "additional_morekeys_symbols_2", - /* 45: 5 */ "additional_morekeys_symbols_3", - /* 46: 5 */ "additional_morekeys_symbols_4", - /* 47: 5 */ "additional_morekeys_symbols_5", - /* 48: 5 */ "additional_morekeys_symbols_6", - /* 49: 5 */ "additional_morekeys_symbols_7", - /* 50: 5 */ "additional_morekeys_symbols_8", - /* 51: 5 */ "additional_morekeys_symbols_9", - /* 52: 5 */ "additional_morekeys_symbols_0", - /* 53: 5 */ "morekeys_tablet_period", - /* 54: 4 */ "morekeys_nordic_row2_11", - /* 55: 4 */ "morekeys_punctuation", - /* 56: 4 */ "keyspec_tablet_comma", - /* 57: 4 */ "keyspec_period", - /* 58: 4 */ "morekeys_period", - /* 59: 4 */ "keyspec_tablet_period", - /* 60: 3 */ "keyspec_swiss_row1_11", - /* 61: 3 */ "keyspec_swiss_row2_10", - /* 62: 3 */ "keyspec_swiss_row2_11", - /* 63: 3 */ "morekeys_swiss_row1_11", - /* 64: 3 */ "morekeys_swiss_row2_10", - /* 65: 3 */ "morekeys_swiss_row2_11", - /* 66: 3 */ "morekeys_star", - /* 67: 3 */ "keyspec_left_parenthesis", - /* 68: 3 */ "keyspec_right_parenthesis", - /* 69: 3 */ "keyspec_left_square_bracket", - /* 70: 3 */ "keyspec_right_square_bracket", - /* 71: 3 */ "keyspec_left_curly_bracket", - /* 72: 3 */ "keyspec_right_curly_bracket", - /* 73: 3 */ "keyspec_less_than", - /* 74: 3 */ "keyspec_greater_than", - /* 75: 3 */ "keyspec_less_than_equal", - /* 76: 3 */ "keyspec_greater_than_equal", - /* 77: 3 */ "keyspec_left_double_angle_quote", - /* 78: 3 */ "keyspec_right_double_angle_quote", - /* 79: 3 */ "keyspec_left_single_angle_quote", - /* 80: 3 */ "keyspec_right_single_angle_quote", - /* 81: 3 */ "keyspec_comma", - /* 82: 3 */ "morekeys_tablet_comma", - /* 83: 3 */ "keyhintlabel_period", - /* 84: 3 */ "morekeys_question", - /* 85: 2 */ "morekeys_h", - /* 86: 2 */ "morekeys_w", - /* 87: 2 */ "morekeys_east_slavic_row2_2", - /* 88: 2 */ "morekeys_cyrillic_u", - /* 89: 2 */ "morekeys_cyrillic_en", - /* 90: 2 */ "morekeys_cyrillic_ghe", - /* 91: 2 */ "morekeys_cyrillic_o", - /* 92: 2 */ "morekeys_cyrillic_i", - /* 93: 2 */ "keyspec_south_slavic_row1_6", - /* 94: 2 */ "keyspec_south_slavic_row2_11", - /* 95: 2 */ "keyspec_south_slavic_row3_1", - /* 96: 2 */ "keyspec_south_slavic_row3_8", - /* 97: 2 */ "morekeys_tablet_punctuation", - /* 98: 2 */ "keyspec_spanish_row2_10", - /* 99: 2 */ "morekeys_bullet", - /* 100: 2 */ "morekeys_left_parenthesis", - /* 101: 2 */ "morekeys_right_parenthesis", - /* 102: 2 */ "morekeys_arabic_diacritics", - /* 103: 2 */ "keyhintlabel_tablet_comma", - /* 104: 2 */ "keyhintlabel_tablet_period", - /* 105: 2 */ "keyspec_symbols_question", - /* 106: 2 */ "keyspec_symbols_semicolon", - /* 107: 2 */ "keyspec_symbols_percent", - /* 108: 2 */ "morekeys_symbols_semicolon", - /* 109: 2 */ "morekeys_symbols_percent", - /* 110: 2 */ "label_go_key", - /* 111: 2 */ "label_send_key", - /* 112: 2 */ "label_next_key", - /* 113: 2 */ "label_done_key", - /* 114: 2 */ "label_search_key", - /* 115: 2 */ "label_previous_key", - /* 116: 2 */ "label_pause_key", - /* 117: 2 */ "label_wait_key", - /* 118: 1 */ "morekeys_v", - /* 119: 1 */ "morekeys_j", - /* 120: 1 */ "morekeys_q", - /* 121: 1 */ "morekeys_x", - /* 122: 1 */ "keyspec_q", - /* 123: 1 */ "keyspec_w", - /* 124: 1 */ "keyspec_y", - /* 125: 1 */ "keyspec_x", - /* 126: 1 */ "morekeys_east_slavic_row2_11", - /* 127: 1 */ "morekeys_cyrillic_ka", - /* 128: 1 */ "morekeys_cyrillic_a", - /* 129: 1 */ "morekeys_currency_dollar", - /* 130: 1 */ "morekeys_plus", - /* 131: 1 */ "morekeys_less_than", - /* 132: 1 */ "morekeys_greater_than", - /* 133: 1 */ "morekeys_exclamation", - /* 134: 0 */ "morekeys_currency_generic", - /* 135: 0 */ "morekeys_symbols_1", - /* 136: 0 */ "morekeys_symbols_2", - /* 137: 0 */ "morekeys_symbols_3", - /* 138: 0 */ "morekeys_symbols_4", - /* 139: 0 */ "morekeys_symbols_5", - /* 140: 0 */ "morekeys_symbols_6", - /* 141: 0 */ "morekeys_symbols_7", - /* 142: 0 */ "morekeys_symbols_8", - /* 143: 0 */ "morekeys_symbols_9", - /* 144: 0 */ "morekeys_symbols_0", - /* 145: 0 */ "morekeys_am_pm", - /* 146: 0 */ "keyspec_settings", - /* 147: 0 */ "keyspec_shortcut", - /* 148: 0 */ "keyspec_action_next", - /* 149: 0 */ "keyspec_action_previous", - /* 150: 0 */ "keylabel_to_more_symbol", - /* 151: 0 */ "keylabel_tablet_to_more_symbol", - /* 152: 0 */ "keylabel_to_phone_numeric", - /* 153: 0 */ "keylabel_to_phone_symbols", - /* 154: 0 */ "keylabel_time_am", - /* 155: 0 */ "keylabel_time_pm", - /* 156: 0 */ "keyspec_popular_domain", - /* 157: 0 */ "morekeys_popular_domain", - /* 158: 0 */ "keyspecs_left_parenthesis_more_keys", - /* 159: 0 */ "keyspecs_right_parenthesis_more_keys", - /* 160: 0 */ "single_laqm_raqm", - /* 161: 0 */ "single_raqm_laqm", - /* 162: 0 */ "double_laqm_raqm", - /* 163: 0 */ "double_raqm_laqm", - /* 164: 0 */ "single_lqm_rqm", - /* 165: 0 */ "single_9qm_lqm", - /* 166: 0 */ "single_9qm_rqm", - /* 167: 0 */ "single_rqm_9qm", - /* 168: 0 */ "double_lqm_rqm", - /* 169: 0 */ "double_9qm_lqm", - /* 170: 0 */ "double_9qm_rqm", - /* 171: 0 */ "double_rqm_9qm", - /* 172: 0 */ "morekeys_single_quote", - /* 173: 0 */ "morekeys_double_quote", - /* 174: 0 */ "morekeys_tablet_double_quote", - /* 175: 0 */ "keyspec_emoji_action_key", - }; - - private static final String EMPTY = ""; - - /* Default texts */ - private static final String[] TEXTS_DEFAULT = { - /* morekeys_a ~ */ - EMPTY, EMPTY, EMPTY, EMPTY, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - /* keylabel_to_alpha */ "ABC", - /* morekeys_i ~ */ - EMPTY, EMPTY, EMPTY, - /* ~ morekeys_c */ - /* double_quotes */ "!text/double_lqm_rqm", - /* morekeys_s */ EMPTY, - /* single_quotes */ "!text/single_lqm_rqm", - /* keyspec_currency */ "$", - /* morekeys_y ~ */ - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - /* ~ morekeys_g */ - /* single_angle_quotes */ "!text/single_laqm_raqm", - /* double_angle_quotes */ "!text/double_laqm_raqm", - /* morekeys_r ~ */ - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - /* ~ morekeys_cyrillic_soft_sign */ - /* keyspec_symbols_1 */ "1", - /* keyspec_symbols_2 */ "2", - /* keyspec_symbols_3 */ "3", - /* keyspec_symbols_4 */ "4", - /* keyspec_symbols_5 */ "5", - /* keyspec_symbols_6 */ "6", - /* keyspec_symbols_7 */ "7", - /* keyspec_symbols_8 */ "8", - /* keyspec_symbols_9 */ "9", - /* keyspec_symbols_0 */ "0", - // Label for "switch to symbols" key. - /* keylabel_to_symbol */ "?123", - /* additional_morekeys_symbols_1 ~ */ - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - /* ~ additional_morekeys_symbols_0 */ - /* morekeys_tablet_period */ "!text/morekeys_tablet_punctuation", - /* morekeys_nordic_row2_11 */ EMPTY, - /* morekeys_punctuation */ "!autoColumnOrder!8,\\,,?,!,#,!text/keyspec_right_parenthesis,!text/keyspec_left_parenthesis,/,;,',@,:,-,\",+,\\%,&", - /* keyspec_tablet_comma */ ",", - // Period key - /* keyspec_period */ ".", - /* morekeys_period */ "!text/morekeys_punctuation", - /* keyspec_tablet_period */ ".", - /* keyspec_swiss_row1_11 ~ */ - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - /* ~ morekeys_swiss_row2_11 */ - // U+2020: "†" DAGGER - // U+2021: "‡" DOUBLE DAGGER - // U+2605: "★" BLACK STAR - /* morekeys_star */ "\u2020,\u2021,\u2605", - // The all letters need to be mirrored are found at - // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt - // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK - // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK - // U+2264: "≤" LESS-THAN OR EQUAL TO - // U+2265: "≥" GREATER-THAN EQUAL TO - // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - /* keyspec_left_parenthesis */ "(", - /* keyspec_right_parenthesis */ ")", - /* keyspec_left_square_bracket */ "[", - /* keyspec_right_square_bracket */ "]", - /* keyspec_left_curly_bracket */ "{", - /* keyspec_right_curly_bracket */ "}", - /* keyspec_less_than */ "<", - /* keyspec_greater_than */ ">", - /* keyspec_less_than_equal */ "\u2264", - /* keyspec_greater_than_equal */ "\u2265", - /* keyspec_left_double_angle_quote */ "\u00AB", - /* keyspec_right_double_angle_quote */ "\u00BB", - /* keyspec_left_single_angle_quote */ "\u2039", - /* keyspec_right_single_angle_quote */ "\u203A", - // Comma key - /* keyspec_comma */ ",", - /* morekeys_tablet_comma */ EMPTY, - /* keyhintlabel_period */ EMPTY, - // U+00BF: "¿" INVERTED QUESTION MARK - /* morekeys_question */ "\u00BF", - /* morekeys_h ~ */ - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - /* ~ keyspec_south_slavic_row3_8 */ - /* morekeys_tablet_punctuation */ "!autoColumnOrder!7,\\,,',#,!text/keyspec_right_parenthesis,!text/keyspec_left_parenthesis,/,;,@,:,-,\",+,\\%,&", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - /* keyspec_spanish_row2_10 */ "\u00F1", - // U+266A: "♪" EIGHTH NOTE - // U+2665: "♥" BLACK HEART SUIT - // U+2660: "♠" BLACK SPADE SUIT - // U+2666: "♦" BLACK DIAMOND SUIT - // U+2663: "♣" BLACK CLUB SUIT - /* morekeys_bullet */ "\u266A,\u2665,\u2660,\u2666,\u2663", - /* morekeys_left_parenthesis */ "!fixedColumnOrder!3,!text/keyspecs_left_parenthesis_more_keys", - /* morekeys_right_parenthesis */ "!fixedColumnOrder!3,!text/keyspecs_right_parenthesis_more_keys", - /* morekeys_arabic_diacritics ~ */ - EMPTY, EMPTY, EMPTY, - /* ~ keyhintlabel_tablet_period */ - /* keyspec_symbols_question */ "?", - /* keyspec_symbols_semicolon */ ";", - /* keyspec_symbols_percent */ "%", - /* morekeys_symbols_semicolon */ EMPTY, - // U+2030: "‰" PER MILLE SIGN - /* morekeys_symbols_percent */ "\u2030", - /* label_go_key */ "!string/label_go_key", - /* label_send_key */ "!string/label_send_key", - /* label_next_key */ "!string/label_next_key", - /* label_done_key */ "!string/label_done_key", - /* label_search_key */ "!string/label_search_key", - /* label_previous_key */ "!string/label_previous_key", - /* label_pause_key */ "!string/label_pause_key", - /* label_wait_key */ "!string/label_wait_key", - /* morekeys_v ~ */ - EMPTY, EMPTY, EMPTY, EMPTY, - /* ~ morekeys_x */ - /* keyspec_q */ "q", - /* keyspec_w */ "w", - /* keyspec_y */ "y", - /* keyspec_x */ "x", - /* morekeys_east_slavic_row2_11 ~ */ - EMPTY, EMPTY, EMPTY, - /* ~ morekeys_cyrillic_a */ - // U+00A2: "¢" CENT SIGN - // U+00A3: "£" POUND SIGN - // U+20AC: "€" EURO SIGN - // U+00A5: "¥" YEN SIGN - // U+20B1: "₱" PESO SIGN - /* morekeys_currency_dollar */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1", - // U+00B1: "±" PLUS-MINUS SIGN - /* morekeys_plus */ "\u00B1", - /* morekeys_less_than */ "!fixedColumnOrder!3,!text/keyspec_left_single_angle_quote,!text/keyspec_less_than_equal,!text/keyspec_left_double_angle_quote", - /* morekeys_greater_than */ "!fixedColumnOrder!3,!text/keyspec_right_single_angle_quote,!text/keyspec_greater_than_equal,!text/keyspec_right_double_angle_quote", - // U+00A1: "¡" INVERTED EXCLAMATION MARK - /* morekeys_exclamation */ "\u00A1", - /* morekeys_currency_generic */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1", - // U+00B9: "¹" SUPERSCRIPT ONE - // U+00BD: "½" VULGAR FRACTION ONE HALF - // U+2153: "⅓" VULGAR FRACTION ONE THIRD - // U+00BC: "¼" VULGAR FRACTION ONE QUARTER - // U+215B: "⅛" VULGAR FRACTION ONE EIGHTH - /* morekeys_symbols_1 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B", - // U+00B2: "²" SUPERSCRIPT TWO - // U+2154: "⅔" VULGAR FRACTION TWO THIRDS - /* morekeys_symbols_2 */ "\u00B2,\u2154", - // U+00B3: "³" SUPERSCRIPT THREE - // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS - // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS - /* morekeys_symbols_3 */ "\u00B3,\u00BE,\u215C", - // U+2074: "⁴" SUPERSCRIPT FOUR - /* morekeys_symbols_4 */ "\u2074", - // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS - /* morekeys_symbols_5 */ "\u215D", - /* morekeys_symbols_6 */ EMPTY, - // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS - /* morekeys_symbols_7 */ "\u215E", - /* morekeys_symbols_8 */ EMPTY, - /* morekeys_symbols_9 */ EMPTY, - // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N - // U+2205: "∅" EMPTY SET - /* morekeys_symbols_0 */ "\u207F,\u2205", - /* morekeys_am_pm */ "!fixedColumnOrder!2,!hasLabels!,!text/keylabel_time_am,!text/keylabel_time_pm", - /* keyspec_settings */ "!icon/settings_key|!code/key_settings", - /* keyspec_shortcut */ "!icon/shortcut_key|!code/key_shortcut", - /* keyspec_action_next */ "!hasLabels!,!text/label_next_key|!code/key_action_next", - /* keyspec_action_previous */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous", - // Label for "switch to more symbol" modifier key ("= \ <"). Must be short to fit on key! - /* keylabel_to_more_symbol */ "= \\\\ <", - // Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key! - /* keylabel_tablet_to_more_symbol */ "~ [ <", - // Label for "switch to phone numeric" key. Must be short to fit on key! - /* keylabel_to_phone_numeric */ "123", - // Label for "switch to phone symbols" key. Must be short to fit on key! - // U+FF0A: "*" FULLWIDTH ASTERISK - // U+FF03: "#" FULLWIDTH NUMBER SIGN - /* keylabel_to_phone_symbols */ "\uFF0A\uFF03", - // Key label for "ante meridiem" - /* keylabel_time_am */ "AM", - // Key label for "post meridiem" - /* keylabel_time_pm */ "PM", - /* keyspec_popular_domain */ ".com", - // popular web domains for the locale - most popular, displayed on the keyboard - /* morekeys_popular_domain */ "!hasLabels!,.net,.org,.gov,.edu", - /* keyspecs_left_parenthesis_more_keys */ "!text/keyspec_less_than,!text/keyspec_left_curly_bracket,!text/keyspec_left_square_bracket", - /* keyspecs_right_parenthesis_more_keys */ "!text/keyspec_greater_than,!text/keyspec_right_curly_bracket,!text/keyspec_right_square_bracket", - // The following characters don't need BIDI mirroring. - // U+2018: "‘" LEFT SINGLE QUOTATION MARK - // U+2019: "’" RIGHT SINGLE QUOTATION MARK - // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK - // U+201C: "“" LEFT DOUBLE QUOTATION MARK - // U+201D: "”" RIGHT DOUBLE QUOTATION MARK - // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK - // Abbreviations are: - // laqm: LEFT-POINTING ANGLE QUOTATION MARK - // raqm: RIGHT-POINTING ANGLE QUOTATION MARK - // lqm: LEFT QUOTATION MARK - // rqm: RIGHT QUOTATION MARK - // 9qm: LOW-9 QUOTATION MARK - // The following each quotation mark pair consist of - // <opening quotation mark>, <closing quotation mark> - // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>. - /* single_laqm_raqm */ "!text/keyspec_left_single_angle_quote,!text/keyspec_right_single_angle_quote", - /* single_raqm_laqm */ "!text/keyspec_right_single_angle_quote,!text/keyspec_left_single_angle_quote", - /* double_laqm_raqm */ "!text/keyspec_left_double_angle_quote,!text/keyspec_right_double_angle_quote", - /* double_raqm_laqm */ "!text/keyspec_right_double_angle_quote,!text/keyspec_left_double_angle_quote", - // The following each quotation mark triplet consists of - // <another quotation mark>, <opening quotation mark>, <closing quotation mark> - // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>. - /* single_lqm_rqm */ "\u201A,\u2018,\u2019", - /* single_9qm_lqm */ "\u2019,\u201A,\u2018", - /* single_9qm_rqm */ "\u2018,\u201A,\u2019", - /* single_rqm_9qm */ "\u2018,\u2019,\u201A", - /* double_lqm_rqm */ "\u201E,\u201C,\u201D", - /* double_9qm_lqm */ "\u201D,\u201E,\u201C", - /* double_9qm_rqm */ "\u201C,\u201E,\u201D", - /* double_rqm_9qm */ "\u201C,\u201D,\u201E", - /* morekeys_single_quote */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes", - /* morekeys_double_quote */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes", - /* morekeys_tablet_double_quote */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes", - /* keyspec_emoji_action_key */ "!icon/emoji_action_key|!code/key_emoji", - }; - - /* Locale af: Afrikaans */ - private static final String[] TEXTS_af = { - // This is the same as Dutch except more keys of y and demoting vowels with diaeresis. - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E1,\u00E2,\u00E4,\u00E0,\u00E6,\u00E3,\u00E5,\u0101", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u00F5,\u0153,\u00F8,\u014D", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - // U+0133: "ij" LATIN SMALL LIGATURE IJ - /* morekeys_i */ "\u00ED,\u00EC,\u00EF,\u00EE,\u012F,\u012B,\u0133", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u00F1,\u0144", - /* morekeys_c ~ */ - null, null, null, null, null, - /* ~ keyspec_currency */ - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+0133: "ij" LATIN SMALL LIGATURE IJ - /* morekeys_y */ "\u00FD,\u0133", - }; - - /* Locale ar: Arabic */ - private static final String[] TEXTS_ar = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE - // U+200C: ZERO WIDTH NON-JOINER - // U+0628: "ب" ARABIC LETTER BEH - // U+062C: "ج" ARABIC LETTER JEEM - /* keylabel_to_alpha */ "\u0623\u200C\u0628\u200C\u062C", - /* morekeys_i ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_cyrillic_soft_sign */ - // U+0661: "١" ARABIC-INDIC DIGIT ONE - /* keyspec_symbols_1 */ "\u0661", - // U+0662: "٢" ARABIC-INDIC DIGIT TWO - /* keyspec_symbols_2 */ "\u0662", - // U+0663: "٣" ARABIC-INDIC DIGIT THREE - /* keyspec_symbols_3 */ "\u0663", - // U+0664: "٤" ARABIC-INDIC DIGIT FOUR - /* keyspec_symbols_4 */ "\u0664", - // U+0665: "٥" ARABIC-INDIC DIGIT FIVE - /* keyspec_symbols_5 */ "\u0665", - // U+0666: "٦" ARABIC-INDIC DIGIT SIX - /* keyspec_symbols_6 */ "\u0666", - // U+0667: "٧" ARABIC-INDIC DIGIT SEVEN - /* keyspec_symbols_7 */ "\u0667", - // U+0668: "٨" ARABIC-INDIC DIGIT EIGHT - /* keyspec_symbols_8 */ "\u0668", - // U+0669: "٩" ARABIC-INDIC DIGIT NINE - /* keyspec_symbols_9 */ "\u0669", - // U+0660: "٠" ARABIC-INDIC DIGIT ZERO - /* keyspec_symbols_0 */ "\u0660", - // Label for "switch to symbols" key. - // U+061F: "؟" ARABIC QUESTION MARK - /* keylabel_to_symbol */ "\u0663\u0662\u0661\u061F", - /* additional_morekeys_symbols_1 */ "1", - /* additional_morekeys_symbols_2 */ "2", - /* additional_morekeys_symbols_3 */ "3", - /* additional_morekeys_symbols_4 */ "4", - /* additional_morekeys_symbols_5 */ "5", - /* additional_morekeys_symbols_6 */ "6", - /* additional_morekeys_symbols_7 */ "7", - /* additional_morekeys_symbols_8 */ "8", - /* additional_morekeys_symbols_9 */ "9", - // U+066B: "٫" ARABIC DECIMAL SEPARATOR - // U+066C: "٬" ARABIC THOUSANDS SEPARATOR - /* additional_morekeys_symbols_0 */ "0,\u066B,\u066C", - /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics", - /* morekeys_nordic_row2_11 */ null, - /* morekeys_punctuation */ null, - // U+061F: "؟" ARABIC QUESTION MARK - // U+060C: "،" ARABIC COMMA - // U+061B: "؛" ARABIC SEMICOLON - /* keyspec_tablet_comma */ "\u060C", - /* keyspec_period */ null, - /* morekeys_period */ "!text/morekeys_arabic_diacritics", - /* keyspec_tablet_period ~ */ - null, null, null, null, null, null, null, - /* ~ morekeys_swiss_row2_11 */ - // U+2605: "★" BLACK STAR - // U+066D: "٭" ARABIC FIVE POINTED STAR - /* morekeys_star */ "\u2605,\u066D", - // U+2264: "≤" LESS-THAN OR EQUAL TO - // U+2265: "≥" GREATER-THAN EQUAL TO - // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK - // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK - /* keyspec_left_parenthesis */ "(|)", - /* keyspec_right_parenthesis */ ")|(", - /* keyspec_left_square_bracket */ "[|]", - /* keyspec_right_square_bracket */ "]|[", - /* keyspec_left_curly_bracket */ "{|}", - /* keyspec_right_curly_bracket */ "}|{", - /* keyspec_less_than */ "<|>", - /* keyspec_greater_than */ ">|<", - /* keyspec_less_than_equal */ "\u2264|\u2265", - /* keyspec_greater_than_equal */ "\u2265|\u2264", - /* keyspec_left_double_angle_quote */ "\u00AB|\u00BB", - /* keyspec_right_double_angle_quote */ "\u00BB|\u00AB", - /* keyspec_left_single_angle_quote */ "\u2039|\u203A", - /* keyspec_right_single_angle_quote */ "\u203A|\u2039", - // U+060C: "،" ARABIC COMMA - /* keyspec_comma */ "\u060C", - /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,\",\'", - // U+0651: "ّ" ARABIC SHADDA - /* keyhintlabel_period */ "\u0651", - // U+00BF: "¿" INVERTED QUESTION MARK - /* morekeys_question */ "?,\u00BF", - /* morekeys_h ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, - /* ~ keyspec_spanish_row2_10 */ - // U+266A: "♪" EIGHTH NOTE - /* morekeys_bullet */ "\u266A", - // The all letters need to be mirrored are found at - // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt - // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS - // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS - /* morekeys_left_parenthesis */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,!text/keyspecs_left_parenthesis_more_keys", - /* morekeys_right_parenthesis */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,!text/keyspecs_right_parenthesis_more_keys", - // U+0655: "ٕ" ARABIC HAMZA BELOW - // U+0654: "ٔ" ARABIC HAMZA ABOVE - // U+0652: "ْ" ARABIC SUKUN - // U+064D: "ٍ" ARABIC KASRATAN - // U+064C: "ٌ" ARABIC DAMMATAN - // U+064B: "ً" ARABIC FATHATAN - // U+0651: "ّ" ARABIC SHADDA - // U+0656: "ٖ" ARABIC SUBSCRIPT ALEF - // U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF - // U+0653: "ٓ" ARABIC MADDAH ABOVE - // U+0650: "ِ" ARABIC KASRA - // U+064F: "ُ" ARABIC DAMMA - // U+064E: "َ" ARABIC FATHA - // U+0640: "ـ" ARABIC TATWEEL - // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. - // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly. - /* morekeys_arabic_diacritics */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640", - /* keyhintlabel_tablet_comma */ "\u061F", - /* keyhintlabel_tablet_period */ "\u0651", - /* keyspec_symbols_question */ "\u061F", - /* keyspec_symbols_semicolon */ "\u061B", - // U+066A: "٪" ARABIC PERCENT SIGN - /* keyspec_symbols_percent */ "\u066A", - /* morekeys_symbols_semicolon */ ";", - // U+2030: "‰" PER MILLE SIGN - /* morekeys_symbols_percent */ "\\%,\u2030", - }; - - /* Locale az_AZ: Azerbaijani (Azerbaijan) */ - private static final String[] TEXTS_az_AZ = { - // This is the same as Turkish - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - /* morekeys_a */ "\u00E2,\u00E4,\u00E1", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D", - // U+0259: "ə" LATIN SMALL LETTER SCHWA - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - /* morekeys_e */ "\u0259,\u00E9", - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B", - /* keylabel_to_alpha */ null, - // U+0131: "ı" LATIN SMALL LETTER DOTLESS I - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B", - // U+0148: "ň" LATIN SMALL LETTER N WITH CARON - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - /* morekeys_n */ "\u0148,\u00F1", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u00E7,\u0107,\u010D", - /* double_quotes */ null, - // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - /* morekeys_s */ "\u015F,\u00DF,\u015B,\u0161", - /* single_quotes */ null, - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - /* morekeys_y */ "\u00FD", - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - /* morekeys_z */ "\u017E", - /* morekeys_d ~ */ - null, null, null, - /* ~ morekeys_l */ - // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE - /* morekeys_g */ "\u011F", - }; - - /* Locale be_BY: Belarusian (Belarus) */ - private static final String[] TEXTS_be_BY = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0410: "А" CYRILLIC CAPITAL LETTER A - // U+0411: "Б" CYRILLIC CAPITAL LETTER BE - // U+0412: "В" CYRILLIC CAPITAL LETTER VE - /* keylabel_to_alpha */ "\u0410\u0411\u0412", - /* morekeys_i ~ */ - null, null, null, - /* ~ morekeys_c */ - /* double_quotes */ "!text/double_9qm_lqm", - /* morekeys_s */ null, - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency ~ */ - null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_k */ - // U+0451: "ё" CYRILLIC SMALL LETTER IO - /* morekeys_cyrillic_ie */ "\u0451", - /* keyspec_nordic_row1_11 ~ */ - null, null, null, null, - /* ~ morekeys_nordic_row2_10 */ - // U+045E: "ў" CYRILLIC SMALL LETTER SHORT U - /* keyspec_east_slavic_row1_9 */ "\u045E", - // U+044B: "ы" CYRILLIC SMALL LETTER YERU - /* keyspec_east_slavic_row2_2 */ "\u044B", - // U+044D: "э" CYRILLIC SMALL LETTER E - /* keyspec_east_slavic_row2_11 */ "\u044D", - // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I - /* keyspec_east_slavic_row3_5 */ "\u0456", - // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN - /* morekeys_cyrillic_soft_sign */ "\u044A", - }; - - /* Locale bg: Bulgarian */ - private static final String[] TEXTS_bg = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0410: "А" CYRILLIC CAPITAL LETTER A - // U+0411: "Б" CYRILLIC CAPITAL LETTER BE - // U+0412: "В" CYRILLIC CAPITAL LETTER VE - /* keylabel_to_alpha */ "\u0410\u0411\u0412", - /* morekeys_i ~ */ - null, null, null, - /* ~ morekeys_c */ - // single_quotes of Bulgarian is default single_quotes_right_left. - /* double_quotes */ "!text/double_9qm_lqm", - }; - - /* Locale bn_BD: Bengali (Bangladesh) */ - private static final String[] TEXTS_bn_BD = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0995: "क" BENGALI LETTER KA - // U+0996: "ख" BENGALI LETTER KHA - // U+0997: "ग" BENGALI LETTER GA - /* keylabel_to_alpha */ "\u0995\u0996\u0997", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+09F3: "৳" BENGALI RUPEE SIGN - /* keyspec_currency */ "\u09F3", - }; - - /* Locale bn_IN: Bengali (India) */ - private static final String[] TEXTS_bn_IN = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0995: "क" BENGALI LETTER KA - // U+0996: "ख" BENGALI LETTER KHA - // U+0997: "ग" BENGALI LETTER GA - /* keylabel_to_alpha */ "\u0995\u0996\u0997", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+20B9: "₹" INDIAN RUPEE SIGN - /* keyspec_currency */ "\u20B9", - }; - - /* Locale ca: Catalan */ - private static final String[] TEXTS_ca = { - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+00AA: "ª" FEMININE ORDINAL INDICATOR - /* morekeys_a */ "\u00E0,\u00E1,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA", - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+00BA: "º" MASCULINE ORDINAL INDICATOR - /* morekeys_o */ "\u00F2,\u00F3,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA", - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E8,\u00E9,\u00EB,\u00EA,\u0119,\u0117,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u00F1,\u0144", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u00E7,\u0107,\u010D", - /* double_quotes ~ */ - null, null, null, null, null, null, null, null, - /* ~ morekeys_t */ - // U+00B7: "·" MIDDLE DOT - // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE - /* morekeys_l */ "l\u00B7l,\u0142", - /* morekeys_g ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, - /* ~ morekeys_nordic_row2_11 */ - // U+00B7: "·" MIDDLE DOT - /* morekeys_punctuation */ "!autoColumnOrder!9,\\,,?,!,\u00B7,#,),(,/,;,',@,:,-,\",+,\\%,&", - /* keyspec_tablet_comma ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - /* ~ keyspec_south_slavic_row3_8 */ - /* morekeys_tablet_punctuation */ "!autoColumnOrder!8,\\,,',\u00B7,#,),(,/,;,@,:,-,\",+,\\%,&", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - /* keyspec_spanish_row2_10 */ "\u00E7", - }; - - /* Locale cs: Czech */ - private static final String[] TEXTS_cs = { - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+011B: "ě" LATIN SMALL LETTER E WITH CARON - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u011B,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u00EC,\u012F,\u012B", - // U+0148: "ň" LATIN SMALL LETTER N WITH CARON - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u0148,\u00F1,\u0144", - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - /* morekeys_c */ "\u010D,\u00E7,\u0107", - /* double_quotes */ "!text/double_9qm_lqm", - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - /* morekeys_s */ "\u0161,\u00DF,\u015B", - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS - /* morekeys_y */ "\u00FD,\u00FF", - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - /* morekeys_z */ "\u017E,\u017A,\u017C", - // U+010F: "ď" LATIN SMALL LETTER D WITH CARON - /* morekeys_d */ "\u010F", - // U+0165: "ť" LATIN SMALL LETTER T WITH CARON - /* morekeys_t */ "\u0165", - /* morekeys_l */ null, - /* morekeys_g */ null, - /* single_angle_quotes */ "!text/single_raqm_laqm", - /* double_angle_quotes */ "!text/double_raqm_laqm", - // U+0159: "ř" LATIN SMALL LETTER R WITH CARON - /* morekeys_r */ "\u0159", - }; - - /* Locale da: Danish */ - private static final String[] TEXTS_da = { - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E5,\u00E6,\u00E1,\u00E4,\u00E0,\u00E2,\u00E3,\u0101", - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F8,\u00F6,\u00F3,\u00F4,\u00F2,\u00F5,\u0153,\u014D", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - /* morekeys_e */ "\u00E9,\u00EB", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - /* morekeys_i */ "\u00ED,\u00EF", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u00F1,\u0144", - /* morekeys_c */ null, - /* double_quotes */ "!text/double_9qm_lqm", - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - /* morekeys_s */ "\u00DF,\u015B,\u0161", - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS - /* morekeys_y */ "\u00FD,\u00FF", - /* morekeys_z */ null, - // U+00F0: "ð" LATIN SMALL LETTER ETH - /* morekeys_d */ "\u00F0", - /* morekeys_t */ null, - // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE - /* morekeys_l */ "\u0142", - /* morekeys_g */ null, - /* single_angle_quotes */ "!text/single_raqm_laqm", - /* double_angle_quotes */ "!text/double_raqm_laqm", - /* morekeys_r ~ */ - null, null, null, - /* ~ morekeys_cyrillic_ie */ - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - /* keyspec_nordic_row1_11 */ "\u00E5", - // U+00E6: "æ" LATIN SMALL LETTER AE - /* keyspec_nordic_row2_10 */ "\u00E6", - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - /* keyspec_nordic_row2_11 */ "\u00F8", - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - /* morekeys_nordic_row2_10 */ "\u00E4", - /* keyspec_east_slavic_row1_9 ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_tablet_period */ - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - /* morekeys_nordic_row2_11 */ "\u00F6", - }; - - /* Locale de: German */ - private static final String[] TEXTS_de = { - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E4,%,\u00E2,\u00E0,\u00E1,\u00E6,\u00E3,\u00E5,\u0101", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F6,%,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u00F8,\u014D", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0117", - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FC,%,\u00FB,\u00F9,\u00FA,\u016B", - /* keylabel_to_alpha */ null, - /* morekeys_i */ null, - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u00F1,\u0144", - /* morekeys_c */ null, - /* double_quotes */ "!text/double_9qm_lqm", - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - /* morekeys_s */ "\u00DF,\u015B,\u0161", - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency ~ */ - null, null, null, null, null, null, null, - /* ~ morekeys_g */ - /* single_angle_quotes */ "!text/single_raqm_laqm", - /* double_angle_quotes */ "!text/double_raqm_laqm", - /* morekeys_r ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, - /* ~ keyspec_tablet_period */ - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - /* keyspec_swiss_row1_11 */ "\u00FC", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - /* keyspec_swiss_row2_10 */ "\u00F6", - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - /* keyspec_swiss_row2_11 */ "\u00E4", - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - /* morekeys_swiss_row1_11 */ "\u00E8", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - /* morekeys_swiss_row2_10 */ "\u00E9", - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - /* morekeys_swiss_row2_11 */ "\u00E0", - }; - - /* Locale el: Greek */ - private static final String[] TEXTS_el = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0391: "Α" GREEK CAPITAL LETTER ALPHA - // U+0392: "Β" GREEK CAPITAL LETTER BETA - // U+0393: "Γ" GREEK CAPITAL LETTER GAMMA - /* keylabel_to_alpha */ "\u0391\u0392\u0393", - }; - - /* Locale en: English */ - private static final String[] TEXTS_en = { - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - /* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u0153,\u00F8,\u014D,\u00F5", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - /* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u012B,\u00EC", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - /* morekeys_n */ "\u00F1", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - /* morekeys_c */ "\u00E7", - /* double_quotes */ null, - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - /* morekeys_s */ "\u00DF", - }; - - /* Locale eo: Esperanto */ - private static final String[] TEXTS_eo = { - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - // U+00AA: "ª" FEMININE ORDINAL INDICATOR - /* morekeys_a */ "\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101,\u0103,\u0105,\u00AA", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE - // U+00BA: "º" MASCULINE ORDINAL INDICATOR - /* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D,\u0151,\u00BA", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+011B: "ě" LATIN SMALL LETTER E WITH CARON - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u011B,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE - // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE - // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK - // U+00B5: "µ" MICRO SIGN - /* morekeys_u */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B,\u0169,\u0171,\u0173,\u00B5", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - // U+0131: "ı" LATIN SMALL LETTER DOTLESS I - // U+0133: "ij" LATIN SMALL LIGATURE IJ - /* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u0129,\u00EC,\u012F,\u012B,\u0131,\u0133", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA - // U+0148: "ň" LATIN SMALL LETTER N WITH CARON - // U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE - // U+014B: "ŋ" LATIN SMALL LETTER ENG - /* morekeys_n */ "\u00F1,\u0144,\u0146,\u0148,\u0149,\u014B", - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE - /* morekeys_c */ "\u0107,\u010D,\u00E7,\u010B", - /* double_quotes */ null, - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW - // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA - /* morekeys_s */ "\u00DF,\u0161,\u015B,\u0219,\u015F", - /* single_quotes */ null, - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX - // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS - // U+00FE: "þ" LATIN SMALL LETTER THORN - /* morekeys_y */ "y,\u00FD,\u0177,\u00FF,\u00FE", - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - /* morekeys_z */ "\u017A,\u017C,\u017E", - // U+00F0: "ð" LATIN SMALL LETTER ETH - // U+010F: "ď" LATIN SMALL LETTER D WITH CARON - // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE - /* morekeys_d */ "\u00F0,\u010F,\u0111", - // U+0165: "ť" LATIN SMALL LETTER T WITH CARON - // U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW - // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA - // U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE - /* morekeys_t */ "\u0165,\u021B,\u0163,\u0167", - // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE - // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA - // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON - // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT - // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE - /* morekeys_l */ "\u013A,\u013C,\u013E,\u0140,\u0142", - // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE - // U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE - // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA - /* morekeys_g */ "\u011F,\u0121,\u0123", - /* single_angle_quotes */ null, - /* double_angle_quotes */ null, - // U+0159: "ř" LATIN SMALL LETTER R WITH CARON - // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE - // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA - /* morekeys_r */ "\u0159,\u0155,\u0157", - // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA - // U+0138: "ĸ" LATIN SMALL LETTER KRA - /* morekeys_k */ "\u0137,\u0138", - /* morekeys_cyrillic_ie ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, - /* ~ morekeys_question */ - // U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX - // U+0127: "ħ" LATIN SMALL LETTER H WITH STROKE - /* morekeys_h */ "\u0125,\u0127", - // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX - /* morekeys_w */ "w,\u0175", - /* morekeys_east_slavic_row2_2 ~ */ - null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_tablet_punctuation */ - // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX - /* keyspec_spanish_row2_10 */ "\u0135", - /* morekeys_bullet ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, - /* ~ label_wait_key */ - // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX - /* morekeys_v */ "w,\u0175", - /* morekeys_j */ null, - /* morekeys_q */ "q", - /* morekeys_x */ "x", - // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX - /* keyspec_q */ "\u015D", - // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX - /* keyspec_w */ "\u011D", - // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE - /* keyspec_y */ "\u016D", - // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX - /* keyspec_x */ "\u0109", - }; - - /* Locale es: Spanish */ - private static final String[] TEXTS_es = { - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+00AA: "ª" FEMININE ORDINAL INDICATOR - /* morekeys_a */ "\u00E1,\u00E0,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+00BA: "º" MASCULINE ORDINAL INDICATOR - /* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u00F1,\u0144", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u00E7,\u0107,\u010D", - /* double_quotes ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, - /* ~ morekeys_nordic_row2_11 */ - // U+00A1: "¡" INVERTED EXCLAMATION MARK - // U+00BF: "¿" INVERTED QUESTION MARK - /* morekeys_punctuation */ "!autoColumnOrder!9,\\,,?,!,#,),(,/,;,\u00A1,',@,:,-,\",+,\\%,&,\u00BF", - }; - - /* Locale et_EE: Estonian (Estonia) */ - private static final String[] TEXTS_et_EE = { - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - /* morekeys_a */ "\u00E4,\u0101,\u00E0,\u00E1,\u00E2,\u00E3,\u00E5,\u00E6,\u0105", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - /* morekeys_o */ "\u00F6,\u00F5,\u00F2,\u00F3,\u00F4,\u0153,\u0151,\u00F8", - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+011B: "ě" LATIN SMALL LETTER E WITH CARON - /* morekeys_e */ "\u0113,\u00E8,\u0117,\u00E9,\u00EA,\u00EB,\u0119,\u011B", - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE - // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE - /* morekeys_u */ "\u00FC,\u016B,\u0173,\u00F9,\u00FA,\u00FB,\u016F,\u0171", - /* keylabel_to_alpha */ null, - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+0131: "ı" LATIN SMALL LETTER DOTLESS I - /* morekeys_i */ "\u012B,\u00EC,\u012F,\u00ED,\u00EE,\u00EF,\u0131", - // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u0146,\u00F1,\u0144", - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - /* morekeys_c */ "\u010D,\u00E7,\u0107", - /* double_quotes */ "!text/double_9qm_lqm", - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA - /* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F", - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS - /* morekeys_y */ "\u00FD,\u00FF", - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - /* morekeys_z */ "\u017E,\u017C,\u017A", - // U+010F: "ď" LATIN SMALL LETTER D WITH CARON - /* morekeys_d */ "\u010F", - // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA - // U+0165: "ť" LATIN SMALL LETTER T WITH CARON - /* morekeys_t */ "\u0163,\u0165", - // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA - // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE - // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE - // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON - /* morekeys_l */ "\u013C,\u0142,\u013A,\u013E", - // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA - // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE - /* morekeys_g */ "\u0123,\u011F", - /* single_angle_quotes */ null, - /* double_angle_quotes */ null, - // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA - // U+0159: "ř" LATIN SMALL LETTER R WITH CARON - // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE - /* morekeys_r */ "\u0157,\u0159,\u0155", - // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA - /* morekeys_k */ "\u0137", - /* morekeys_cyrillic_ie */ null, - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - /* keyspec_nordic_row1_11 */ "\u00FC", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - /* keyspec_nordic_row2_10 */ "\u00F6", - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - /* keyspec_nordic_row2_11 */ "\u00E4", - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - /* morekeys_nordic_row2_10 */ "\u00F5", - }; - - /* Locale eu_ES: Basque (Spain) */ - private static final String[] TEXTS_eu_ES = { - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+00AA: "ª" FEMININE ORDINAL INDICATOR - /* morekeys_a */ "\u00E1,\u00E0,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+00BA: "º" MASCULINE ORDINAL INDICATOR - /* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u00F1,\u0144", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u00E7,\u0107,\u010D", - }; - - /* Locale fa: Persian */ - private static final String[] TEXTS_fa = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0627: "ا" ARABIC LETTER ALEF - // U+200C: ZERO WIDTH NON-JOINER - // U+0628: "ب" ARABIC LETTER BEH - // U+067E: "پ" ARABIC LETTER PEH - /* keylabel_to_alpha */ "\u0627\u200C\u0628\u200C\u067E", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+FDFC: "﷼" RIAL SIGN - /* keyspec_currency */ "\uFDFC", - /* morekeys_y ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, - /* ~ morekeys_cyrillic_soft_sign */ - // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE - /* keyspec_symbols_1 */ "\u06F1", - // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO - /* keyspec_symbols_2 */ "\u06F2", - // U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE - /* keyspec_symbols_3 */ "\u06F3", - // U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR - /* keyspec_symbols_4 */ "\u06F4", - // U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE - /* keyspec_symbols_5 */ "\u06F5", - // U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX - /* keyspec_symbols_6 */ "\u06F6", - // U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN - /* keyspec_symbols_7 */ "\u06F7", - // U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT - /* keyspec_symbols_8 */ "\u06F8", - // U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE - /* keyspec_symbols_9 */ "\u06F9", - // U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO - /* keyspec_symbols_0 */ "\u06F0", - // Label for "switch to symbols" key. - // U+061F: "؟" ARABIC QUESTION MARK - /* keylabel_to_symbol */ "\u06F3\u06F2\u06F1\u061F", - /* additional_morekeys_symbols_1 */ "1", - /* additional_morekeys_symbols_2 */ "2", - /* additional_morekeys_symbols_3 */ "3", - /* additional_morekeys_symbols_4 */ "4", - /* additional_morekeys_symbols_5 */ "5", - /* additional_morekeys_symbols_6 */ "6", - /* additional_morekeys_symbols_7 */ "7", - /* additional_morekeys_symbols_8 */ "8", - /* additional_morekeys_symbols_9 */ "9", - // U+066B: "٫" ARABIC DECIMAL SEPARATOR - // U+066C: "٬" ARABIC THOUSANDS SEPARATOR - /* additional_morekeys_symbols_0 */ "0,\u066B,\u066C", - /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics", - /* morekeys_nordic_row2_11 */ null, - /* morekeys_punctuation */ null, - // U+060C: "،" ARABIC COMMA - // U+061B: "؛" ARABIC SEMICOLON - // U+061F: "؟" ARABIC QUESTION MARK - // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - /* keyspec_tablet_comma */ "\u060C", - /* keyspec_period */ null, - /* morekeys_period */ "!text/morekeys_arabic_diacritics", - /* keyspec_tablet_period ~ */ - null, null, null, null, null, null, null, - /* ~ morekeys_swiss_row2_11 */ - // U+2605: "★" BLACK STAR - // U+066D: "٭" ARABIC FIVE POINTED STAR - /* morekeys_star */ "\u2605,\u066D", - /* keyspec_left_parenthesis */ "(|)", - /* keyspec_right_parenthesis */ ")|(", - /* keyspec_left_square_bracket */ "[|]", - /* keyspec_right_square_bracket */ "]|[", - /* keyspec_left_curly_bracket */ "{|}", - /* keyspec_right_curly_bracket */ "}|{", - /* keyspec_less_than */ "<|>", - /* keyspec_greater_than */ ">|<", - /* keyspec_less_than_equal */ "\u2264|\u2265", - /* keyspec_greater_than_equal */ "\u2265|\u2264", - /* keyspec_left_double_angle_quote */ "\u00AB|\u00BB", - /* keyspec_right_double_angle_quote */ "\u00BB|\u00AB", - /* keyspec_left_single_angle_quote */ "\u2039|\u203A", - /* keyspec_right_single_angle_quote */ "\u203A|\u2039", - // U+060C: "،" ARABIC COMMA - /* keyspec_comma */ "\u060C", - /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,!text/keyspec_left_double_angle_quote,!text/keyspec_right_double_angle_quote", - // U+064B: "ً" ARABIC FATHATAN - /* keyhintlabel_period */ "\u064B", - // U+00BF: "¿" INVERTED QUESTION MARK - /* morekeys_question */ "?,\u00BF", - /* morekeys_h ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, - /* ~ keyspec_spanish_row2_10 */ - // U+266A: "♪" EIGHTH NOTE - /* morekeys_bullet */ "\u266A", - // The all letters need to be mirrored are found at - // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt - // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS - // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS - /* morekeys_left_parenthesis */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,!text/keyspecs_left_parenthesis_more_keys", - /* morekeys_right_parenthesis */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,!text/keyspecs_right_parenthesis_more_keys", - // U+0655: "ٕ" ARABIC HAMZA BELOW - // U+0652: "ْ" ARABIC SUKUN - // U+0651: "ّ" ARABIC SHADDA - // U+064C: "ٌ" ARABIC DAMMATAN - // U+064D: "ٍ" ARABIC KASRATAN - // U+064B: "ً" ARABIC FATHATAN - // U+0654: "ٔ" ARABIC HAMZA ABOVE - // U+0656: "ٖ" ARABIC SUBSCRIPT ALEF - // U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF - // U+0653: "ٓ" ARABIC MADDAH ABOVE - // U+064F: "ُ" ARABIC DAMMA - // U+0650: "ِ" ARABIC KASRA - // U+064E: "َ" ARABIC FATHA - // U+0640: "ـ" ARABIC TATWEEL - // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. - // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly. - /* morekeys_arabic_diacritics */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640", - /* keyhintlabel_tablet_comma */ "\u061F", - /* keyhintlabel_tablet_period */ "\u064B", - /* keyspec_symbols_question */ "\u061F", - /* keyspec_symbols_semicolon */ "\u061B", - // U+066A: "٪" ARABIC PERCENT SIGN - /* keyspec_symbols_percent */ "\u066A", - /* morekeys_symbols_semicolon */ ";", - // U+2030: "‰" PER MILLE SIGN - /* morekeys_symbols_percent */ "\\%,\u2030", - /* label_go_key ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, - /* ~ morekeys_plus */ - // U+2264: "≤" LESS-THAN OR EQUAL TO - // U+2265: "≥" GREATER-THAN EQUAL TO - // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK - // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK - /* morekeys_less_than */ "!fixedColumnOrder!3,!text/keyspec_left_single_angle_quote,!text/keyspec_less_than_equal,!text/keyspec_less_than", - /* morekeys_greater_than */ "!fixedColumnOrder!3,!text/keyspec_right_single_angle_quote,!text/keyspec_greater_than_equal,!text/keyspec_greater_than", - }; - - /* Locale fi: Finnish */ - private static final String[] TEXTS_fi = { - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E4,\u00E5,\u00E6,\u00E0,\u00E1,\u00E2,\u00E3,\u0101", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F6,\u00F8,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u014D", - /* morekeys_e */ null, - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - /* morekeys_u */ "\u00FC", - /* keylabel_to_alpha ~ */ - null, null, null, null, null, - /* ~ double_quotes */ - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - /* morekeys_s */ "\u0161,\u00DF,\u015B", - /* single_quotes ~ */ - null, null, null, - /* ~ morekeys_y */ - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - /* morekeys_z */ "\u017E,\u017A,\u017C", - /* morekeys_d ~ */ - null, null, null, null, null, null, null, null, null, - /* ~ morekeys_cyrillic_ie */ - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - /* keyspec_nordic_row1_11 */ "\u00E5", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - /* keyspec_nordic_row2_10 */ "\u00F6", - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - /* keyspec_nordic_row2_11 */ "\u00E4", - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - /* morekeys_nordic_row2_10 */ "\u00F8", - /* keyspec_east_slavic_row1_9 ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_tablet_period */ - // U+00E6: "æ" LATIN SMALL LETTER AE - /* morekeys_nordic_row2_11 */ "\u00E6", - }; - - /* Locale fr: French */ - private static final String[] TEXTS_fr = { - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+00AA: "ª" FEMININE ORDINAL INDICATOR - /* morekeys_a */ "\u00E0,\u00E2,%,\u00E6,\u00E1,\u00E4,\u00E3,\u00E5,\u0101,\u00AA", - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+00BA: "º" MASCULINE ORDINAL INDICATOR - /* morekeys_o */ "\u00F4,\u0153,%,\u00F6,\u00F2,\u00F3,\u00F5,\u00F8,\u014D,\u00BA", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,%,\u0119,\u0117,\u0113", - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00F9,\u00FB,%,\u00FC,\u00FA,\u016B", - /* keylabel_to_alpha */ null, - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00EE,%,\u00EF,\u00EC,\u00ED,\u012F,\u012B", - /* morekeys_n */ null, - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u00E7,%,\u0107,\u010D", - /* double_quotes ~ */ - null, null, null, null, - /* ~ keyspec_currency */ - // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS - /* morekeys_y */ "%,\u00FF", - /* morekeys_z ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, - /* ~ keyspec_tablet_period */ - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - /* keyspec_swiss_row1_11 */ "\u00E8", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - /* keyspec_swiss_row2_10 */ "\u00E9", - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - /* keyspec_swiss_row2_11 */ "\u00E0", - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - /* morekeys_swiss_row1_11 */ "\u00FC", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - /* morekeys_swiss_row2_10 */ "\u00F6", - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - /* morekeys_swiss_row2_11 */ "\u00E4", - }; - - /* Locale gl_ES: Gallegan (Spain) */ - private static final String[] TEXTS_gl_ES = { - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+00AA: "ª" FEMININE ORDINAL INDICATOR - /* morekeys_a */ "\u00E1,\u00E0,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+00BA: "º" MASCULINE ORDINAL INDICATOR - /* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u00F1,\u0144", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u00E7,\u0107,\u010D", - }; - - /* Locale hi: Hindi */ - private static final String[] TEXTS_hi = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0915: "क" DEVANAGARI LETTER KA - // U+0916: "ख" DEVANAGARI LETTER KHA - // U+0917: "ग" DEVANAGARI LETTER GA - /* keylabel_to_alpha */ "\u0915\u0916\u0917", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+20B9: "₹" INDIAN RUPEE SIGN - /* keyspec_currency */ "\u20B9", - /* morekeys_y ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, - /* ~ morekeys_cyrillic_soft_sign */ - // U+0967: "१" DEVANAGARI DIGIT ONE - /* keyspec_symbols_1 */ "\u0967", - // U+0968: "२" DEVANAGARI DIGIT TWO - /* keyspec_symbols_2 */ "\u0968", - // U+0969: "३" DEVANAGARI DIGIT THREE - /* keyspec_symbols_3 */ "\u0969", - // U+096A: "४" DEVANAGARI DIGIT FOUR - /* keyspec_symbols_4 */ "\u096A", - // U+096B: "५" DEVANAGARI DIGIT FIVE - /* keyspec_symbols_5 */ "\u096B", - // U+096C: "६" DEVANAGARI DIGIT SIX - /* keyspec_symbols_6 */ "\u096C", - // U+096D: "७" DEVANAGARI DIGIT SEVEN - /* keyspec_symbols_7 */ "\u096D", - // U+096E: "८" DEVANAGARI DIGIT EIGHT - /* keyspec_symbols_8 */ "\u096E", - // U+096F: "९" DEVANAGARI DIGIT NINE - /* keyspec_symbols_9 */ "\u096F", - // U+0966: "०" DEVANAGARI DIGIT ZERO - /* keyspec_symbols_0 */ "\u0966", - // Label for "switch to symbols" key. - /* keylabel_to_symbol */ "?\u0967\u0968\u0969", - /* additional_morekeys_symbols_1 */ "1", - /* additional_morekeys_symbols_2 */ "2", - /* additional_morekeys_symbols_3 */ "3", - /* additional_morekeys_symbols_4 */ "4", - /* additional_morekeys_symbols_5 */ "5", - /* additional_morekeys_symbols_6 */ "6", - /* additional_morekeys_symbols_7 */ "7", - /* additional_morekeys_symbols_8 */ "8", - /* additional_morekeys_symbols_9 */ "9", - /* additional_morekeys_symbols_0 */ "0", - /* morekeys_tablet_period */ "!autoColumnOrder!8,\\,,.,',#,),(,/,;,@,:,-,\",+,\\%,&", - /* morekeys_nordic_row2_11 ~ */ - null, null, null, - /* ~ keyspec_tablet_comma */ - // U+0964: "।" DEVANAGARI DANDA - /* keyspec_period */ "\u0964", - /* morekeys_period */ "!autoColumnOrder!9,\\,,.,?,!,#,),(,/,;,',@,:,-,\",+,\\%,&", - /* keyspec_tablet_period */ "\u0964", - }; - - /* Locale hi_ZZ: Hindi (ZZ) */ - private static final String[] TEXTS_hi_ZZ = { - /* morekeys_a ~ */ - null, null, null, null, null, null, null, null, null, null, null, - /* ~ single_quotes */ - // U+20B9: "₹" INDIAN RUPEE SIGN - /* keyspec_currency */ "\u20B9", - /* morekeys_y ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, - /* ~ morekeys_symbols_percent */ - /* label_go_key */ "Go", - /* label_send_key */ "Send", - /* label_next_key */ "Next", - /* label_done_key */ "Done", - /* label_search_key */ "Search", - /* label_previous_key */ "Prev", - /* label_pause_key */ "Pause", - /* label_wait_key */ "Wait", - }; - - /* Locale hr: Croatian */ - private static final String[] TEXTS_hr = { - /* morekeys_a ~ */ - null, null, null, null, null, null, - /* ~ morekeys_i */ - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u00F1,\u0144", - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - /* morekeys_c */ "\u010D,\u0107,\u00E7", - /* double_quotes */ "!text/double_9qm_rqm", - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - /* morekeys_s */ "\u0161,\u015B,\u00DF", - /* single_quotes */ "!text/single_9qm_rqm", - /* keyspec_currency */ null, - /* morekeys_y */ null, - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - /* morekeys_z */ "\u017E,\u017A,\u017C", - // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE - /* morekeys_d */ "\u0111", - /* morekeys_t ~ */ - null, null, null, - /* ~ morekeys_g */ - /* single_angle_quotes */ "!text/single_raqm_laqm", - /* double_angle_quotes */ "!text/double_raqm_laqm", - }; - - /* Locale hu: Hungarian */ - private static final String[] TEXTS_hu = { - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F3,\u00F6,\u0151,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FC,\u0171,\u00FB,\u00F9,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u00EC,\u012F,\u012B", - /* morekeys_n */ null, - /* morekeys_c */ null, - /* double_quotes */ "!text/double_9qm_rqm", - /* morekeys_s */ null, - /* single_quotes */ "!text/single_9qm_rqm", - /* keyspec_currency ~ */ - null, null, null, null, null, null, null, - /* ~ morekeys_g */ - /* single_angle_quotes */ "!text/single_raqm_laqm", - /* double_angle_quotes */ "!text/double_raqm_laqm", - }; - - /* Locale hy_AM: Armenian (Armenia) */ - private static final String[] TEXTS_hy_AM = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0531: "Ա" ARMENIAN CAPITAL LETTER AYB - // U+0532: "Բ" ARMENIAN CAPITAL LETTER BEN - // U+0533: "Գ" ARMENIAN CAPITAL LETTER GIM - /* keylabel_to_alpha */ "\u0531\u0532\u0533", - /* morekeys_i ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, - /* ~ additional_morekeys_symbols_0 */ - /* morekeys_tablet_period */ "!text/morekeys_punctuation", - /* morekeys_nordic_row2_11 */ null, - // U+055E: "՞" ARMENIAN QUESTION MARK - // U+055C: "՜" ARMENIAN EXCLAMATION MARK - // U+055A: "՚" ARMENIAN APOSTROPHE - // U+0559: "ՙ" ARMENIAN MODIFIER LETTER LEFT HALF RING - // U+055D: "՝" ARMENIAN COMMA - // U+055B: "՛" ARMENIAN EMPHASIS MARK - // U+058A: "֊" ARMENIAN HYPHEN - // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - // U+055F: "՟" ARMENIAN ABBREVIATION MARK - /* morekeys_punctuation */ "!autoColumnOrder!8,\\,,\u055E,\u055C,.,\u055A,\u0559,?,!,\u055D,\u055B,\u058A,\u00BB,\u00AB,\u055F,;,:", - /* keyspec_tablet_comma */ "\u055D", - // U+0589: "։" ARMENIAN FULL STOP - /* keyspec_period */ "\u0589", - /* morekeys_period */ null, - /* keyspec_tablet_period */ "\u0589", - /* keyspec_swiss_row1_11 ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, - /* ~ keyspec_right_single_angle_quote */ - // U+058F: "֏" ARMENIAN DRAM SIGN - // TODO: Enable this when we have glyph for the following letter - // <string name="keyspec_currency">֏</string> - // - // U+055D: "՝" ARMENIAN COMMA - /* keyspec_comma */ "\u055D", - /* morekeys_tablet_comma */ null, - /* keyhintlabel_period */ null, - // U+055E: "՞" ARMENIAN QUESTION MARK - // U+00BF: "¿" INVERTED QUESTION MARK - /* morekeys_question */ "\u055E,\u00BF", - /* morekeys_h ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, - /* ~ morekeys_greater_than */ - // U+055C: "՜" ARMENIAN EXCLAMATION MARK - // U+00A1: "¡" INVERTED EXCLAMATION MARK - /* morekeys_exclamation */ "\u055C,\u00A1", - }; - - /* Locale is: Icelandic */ - private static final String[] TEXTS_is = { - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E1,\u00E4,\u00E6,\u00E5,\u00E0,\u00E2,\u00E3,\u0101", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00EB,\u00E8,\u00EA,\u0119,\u0117,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00ED,\u00EF,\u00EE,\u00EC,\u012F,\u012B", - /* morekeys_n */ null, - /* morekeys_c */ null, - /* double_quotes */ "!text/double_9qm_lqm", - /* morekeys_s */ null, - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS - /* morekeys_y */ "\u00FD,\u00FF", - /* morekeys_z */ null, - // U+00F0: "ð" LATIN SMALL LETTER ETH - /* morekeys_d */ "\u00F0", - // U+00FE: "þ" LATIN SMALL LETTER THORN - /* morekeys_t */ "\u00FE", - }; - - /* Locale it: Italian */ - private static final String[] TEXTS_it = { - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+00AA: "ª" FEMININE ORDINAL INDICATOR - /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101,\u00AA", - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+00BA: "º" MASCULINE ORDINAL INDICATOR - /* morekeys_o */ "\u00F2,\u00F3,\u00F4,\u00F6,\u00F5,\u0153,\u00F8,\u014D,\u00BA", - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0119,\u0117,\u0113", - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00F9,\u00FA,\u00FB,\u00FC,\u016B", - /* keylabel_to_alpha */ null, - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00EC,\u00ED,\u00EE,\u00EF,\u012F,\u012B", - /* morekeys_n ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, - /* ~ keyspec_tablet_period */ - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - /* keyspec_swiss_row1_11 */ "\u00FC", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - /* keyspec_swiss_row2_10 */ "\u00F6", - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - /* keyspec_swiss_row2_11 */ "\u00E4", - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - /* morekeys_swiss_row1_11 */ "\u00E8", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - /* morekeys_swiss_row2_10 */ "\u00E9", - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - /* morekeys_swiss_row2_11 */ "\u00E0", - }; - - /* Locale iw: Hebrew */ - private static final String[] TEXTS_iw = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+05D0: "א" HEBREW LETTER ALEF - // U+05D1: "ב" HEBREW LETTER BET - // U+05D2: "ג" HEBREW LETTER GIMEL - /* keylabel_to_alpha */ "\u05D0\u05D1\u05D2", - /* morekeys_i ~ */ - null, null, null, - /* ~ morekeys_c */ - /* double_quotes */ "!text/double_rqm_9qm", - /* morekeys_s */ null, - /* single_quotes */ "!text/single_rqm_9qm", - // U+20AA: "₪" NEW SHEQEL SIGN - /* keyspec_currency */ "\u20AA", - /* morekeys_y ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, - /* ~ morekeys_swiss_row2_11 */ - // U+2605: "★" BLACK STAR - /* morekeys_star */ "\u2605", - // The all letters need to be mirrored are found at - // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt - // U+2264: "≤" LESS-THAN OR EQUAL TO - // U+2265: "≥" GREATER-THAN EQUAL TO - // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK - // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK - /* keyspec_left_parenthesis */ "(|)", - /* keyspec_right_parenthesis */ ")|(", - /* keyspec_left_square_bracket */ "[|]", - /* keyspec_right_square_bracket */ "]|[", - /* keyspec_left_curly_bracket */ "{|}", - /* keyspec_right_curly_bracket */ "}|{", - /* keyspec_less_than */ "<|>", - /* keyspec_greater_than */ ">|<", - /* keyspec_less_than_equal */ "\u2264|\u2265", - /* keyspec_greater_than_equal */ "\u2265|\u2264", - /* keyspec_left_double_angle_quote */ "\u00AB|\u00BB", - /* keyspec_right_double_angle_quote */ "\u00BB|\u00AB", - /* keyspec_left_single_angle_quote */ "\u2039|\u203A", - /* keyspec_right_single_angle_quote */ "\u203A|\u2039", - /* keyspec_comma ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, - /* ~ morekeys_currency_dollar */ - // U+00B1: "±" PLUS-MINUS SIGN - // U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN - /* morekeys_plus */ "\u00B1,\uFB29", - }; - - /* Locale ka_GE: Georgian (Georgia) */ - private static final String[] TEXTS_ka_GE = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+10D0: "ა" GEORGIAN LETTER AN - // U+10D1: "ბ" GEORGIAN LETTER BAN - // U+10D2: "გ" GEORGIAN LETTER GAN - /* keylabel_to_alpha */ "\u10D0\u10D1\u10D2", - /* morekeys_i ~ */ - null, null, null, - /* ~ morekeys_c */ - /* double_quotes */ "!text/double_9qm_lqm", - /* morekeys_s */ null, - /* single_quotes */ "!text/single_9qm_lqm", - }; - - /* Locale kk: Kazakh */ - private static final String[] TEXTS_kk = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0410: "А" CYRILLIC CAPITAL LETTER A - // U+0411: "Б" CYRILLIC CAPITAL LETTER BE - // U+0412: "В" CYRILLIC CAPITAL LETTER VE - /* keylabel_to_alpha */ "\u0410\u0411\u0412", - /* morekeys_i ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, - /* ~ morekeys_k */ - // U+0451: "ё" CYRILLIC SMALL LETTER IO - /* morekeys_cyrillic_ie */ "\u0451", - /* keyspec_nordic_row1_11 ~ */ - null, null, null, null, - /* ~ morekeys_nordic_row2_10 */ - // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA - /* keyspec_east_slavic_row1_9 */ "\u0449", - // U+044B: "ы" CYRILLIC SMALL LETTER YERU - /* keyspec_east_slavic_row2_2 */ "\u044B", - // U+044D: "э" CYRILLIC SMALL LETTER E - /* keyspec_east_slavic_row2_11 */ "\u044D", - // U+0438: "и" CYRILLIC SMALL LETTER I - /* keyspec_east_slavic_row3_5 */ "\u0438", - // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN - /* morekeys_cyrillic_soft_sign */ "\u044A", - /* keyspec_symbols_1 ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_w */ - // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I - /* morekeys_east_slavic_row2_2 */ "\u0456", - // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U - // U+04B1: "ұ" CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE - /* morekeys_cyrillic_u */ "\u04AF,\u04B1", - // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER - /* morekeys_cyrillic_en */ "\u04A3", - // U+0493: "ғ" CYRILLIC SMALL LETTER GHE WITH STROKE - /* morekeys_cyrillic_ghe */ "\u0493", - // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O - /* morekeys_cyrillic_o */ "\u04E9", - /* morekeys_cyrillic_i ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, - /* ~ keyspec_x */ - // U+04BB: "һ" CYRILLIC SMALL LETTER SHHA - /* morekeys_east_slavic_row2_11 */ "\u04BB", - // U+049B: "қ" CYRILLIC SMALL LETTER KA WITH DESCENDER - /* morekeys_cyrillic_ka */ "\u049B", - // U+04D9: "ә" CYRILLIC SMALL LETTER SCHWA - /* morekeys_cyrillic_a */ "\u04D9", - }; - - /* Locale km_KH: Khmer (Cambodia) */ - private static final String[] TEXTS_km_KH = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+1780: "ក" KHMER LETTER KA - // U+1781: "ខ" KHMER LETTER KHA - // U+1782: "គ" KHMER LETTER KO - /* keylabel_to_alpha */ "\u1780\u1781\u1782", - /* morekeys_i ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, - /* ~ morekeys_cyrillic_a */ - // U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL - /* morekeys_currency_dollar */ "\u17DB,\u00A2,\u00A3,\u20AC,\u00A5,\u20B1", - }; - - /* Locale kn_IN: Kannada (India) */ - private static final String[] TEXTS_kn_IN = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0C85: "ಅ" KANNADA LETTER A - // U+0C86: "ಆ" KANNADA LETTER AA - // U+0C87: "ಇ" KANNADA LETTER I - /* keylabel_to_alpha */ "\u0C85\u0C86\u0C87", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+20B9: "₹" INDIAN RUPEE SIGN - /* keyspec_currency */ "\u20B9", - }; - - /* Locale ky: Kirghiz */ - private static final String[] TEXTS_ky = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0410: "А" CYRILLIC CAPITAL LETTER A - // U+0411: "Б" CYRILLIC CAPITAL LETTER BE - // U+0412: "В" CYRILLIC CAPITAL LETTER VE - /* keylabel_to_alpha */ "\u0410\u0411\u0412", - /* morekeys_i ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, - /* ~ morekeys_k */ - // U+0451: "ё" CYRILLIC SMALL LETTER IO - /* morekeys_cyrillic_ie */ "\u0451", - /* keyspec_nordic_row1_11 ~ */ - null, null, null, null, - /* ~ morekeys_nordic_row2_10 */ - // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA - /* keyspec_east_slavic_row1_9 */ "\u0449", - // U+044B: "ы" CYRILLIC SMALL LETTER YERU - /* keyspec_east_slavic_row2_2 */ "\u044B", - // U+044D: "э" CYRILLIC SMALL LETTER E - /* keyspec_east_slavic_row2_11 */ "\u044D", - // U+0438: "и" CYRILLIC SMALL LETTER I - /* keyspec_east_slavic_row3_5 */ "\u0438", - // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN - /* morekeys_cyrillic_soft_sign */ "\u044A", - /* keyspec_symbols_1 ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_east_slavic_row2_2 */ - // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U - /* morekeys_cyrillic_u */ "\u04AF", - // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER - /* morekeys_cyrillic_en */ "\u04A3", - /* morekeys_cyrillic_ghe */ null, - // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O - /* morekeys_cyrillic_o */ "\u04E9", - }; - - /* Locale lo_LA: Lao (Laos) */ - private static final String[] TEXTS_lo_LA = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0E81: "ກ" LAO LETTER KO - // U+0E82: "ຂ" LAO LETTER KHO SUNG - // U+0E84: "ຄ" LAO LETTER KHO TAM - /* keylabel_to_alpha */ "\u0E81\u0E82\u0E84", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+20AD: "₭" KIP SIGN - /* keyspec_currency */ "\u20AD", - }; - - /* Locale lt: Lithuanian */ - private static final String[] TEXTS_lt = { - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+00E6: "æ" LATIN SMALL LETTER AE - /* morekeys_a */ "\u0105,\u00E4,\u0101,\u00E0,\u00E1,\u00E2,\u00E3,\u00E5,\u00E6", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - /* morekeys_o */ "\u00F6,\u00F5,\u00F2,\u00F3,\u00F4,\u0153,\u0151,\u00F8", - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+011B: "ě" LATIN SMALL LETTER E WITH CARON - /* morekeys_e */ "\u0117,\u0119,\u0113,\u00E8,\u00E9,\u00EA,\u00EB,\u011B", - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE - // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE - /* morekeys_u */ "\u016B,\u0173,\u00FC,\u016B,\u00F9,\u00FA,\u00FB,\u016F,\u0171", - /* keylabel_to_alpha */ null, - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+0131: "ı" LATIN SMALL LETTER DOTLESS I - /* morekeys_i */ "\u012F,\u012B,\u00EC,\u00ED,\u00EE,\u00EF,\u0131", - // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u0146,\u00F1,\u0144", - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - /* morekeys_c */ "\u010D,\u00E7,\u0107", - /* double_quotes */ "!text/double_9qm_lqm", - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA - /* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F", - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS - /* morekeys_y */ "\u00FD,\u00FF", - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - /* morekeys_z */ "\u017E,\u017C,\u017A", - // U+010F: "ď" LATIN SMALL LETTER D WITH CARON - /* morekeys_d */ "\u010F", - // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA - // U+0165: "ť" LATIN SMALL LETTER T WITH CARON - /* morekeys_t */ "\u0163,\u0165", - // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA - // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE - // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE - // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON - /* morekeys_l */ "\u013C,\u0142,\u013A,\u013E", - // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA - // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE - /* morekeys_g */ "\u0123,\u011F", - /* single_angle_quotes */ null, - /* double_angle_quotes */ null, - // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA - // U+0159: "ř" LATIN SMALL LETTER R WITH CARON - // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE - /* morekeys_r */ "\u0157,\u0159,\u0155", - // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA - /* morekeys_k */ "\u0137", - }; - - /* Locale lv: Latvian */ - private static final String[] TEXTS_lv = { - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - /* morekeys_a */ "\u0101,\u00E0,\u00E1,\u00E2,\u00E3,\u00E4,\u00E5,\u00E6,\u0105", - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - /* morekeys_o */ "\u00F2,\u00F3,\u00F4,\u00F5,\u00F6,\u0153,\u0151,\u00F8", - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+011B: "ě" LATIN SMALL LETTER E WITH CARON - /* morekeys_e */ "\u0113,\u0117,\u00E8,\u00E9,\u00EA,\u00EB,\u0119,\u011B", - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE - // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE - /* morekeys_u */ "\u016B,\u0173,\u00F9,\u00FA,\u00FB,\u00FC,\u016F,\u0171", - /* keylabel_to_alpha */ null, - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+0131: "ı" LATIN SMALL LETTER DOTLESS I - /* morekeys_i */ "\u012B,\u012F,\u00EC,\u00ED,\u00EE,\u00EF,\u0131", - // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u0146,\u00F1,\u0144", - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - /* morekeys_c */ "\u010D,\u00E7,\u0107", - /* double_quotes */ "!text/double_9qm_lqm", - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA - /* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F", - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS - /* morekeys_y */ "\u00FD,\u00FF", - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - /* morekeys_z */ "\u017E,\u017C,\u017A", - // U+010F: "ď" LATIN SMALL LETTER D WITH CARON - /* morekeys_d */ "\u010F", - // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA - // U+0165: "ť" LATIN SMALL LETTER T WITH CARON - /* morekeys_t */ "\u0163,\u0165", - // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA - // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE - // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE - // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON - /* morekeys_l */ "\u013C,\u0142,\u013A,\u013E", - // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA - // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE - /* morekeys_g */ "\u0123,\u011F", - /* single_angle_quotes */ null, - /* double_angle_quotes */ null, - // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA - // U+0159: "ř" LATIN SMALL LETTER R WITH CARON - // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE - /* morekeys_r */ "\u0157,\u0159,\u0155", - // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA - /* morekeys_k */ "\u0137", - }; - - /* Locale mk: Macedonian */ - private static final String[] TEXTS_mk = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0410: "А" CYRILLIC CAPITAL LETTER A - // U+0411: "Б" CYRILLIC CAPITAL LETTER BE - // U+0412: "В" CYRILLIC CAPITAL LETTER VE - /* keylabel_to_alpha */ "\u0410\u0411\u0412", - /* morekeys_i ~ */ - null, null, null, - /* ~ morekeys_c */ - /* double_quotes */ "!text/double_9qm_lqm", - /* morekeys_s */ null, - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency ~ */ - null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_k */ - // U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE - /* morekeys_cyrillic_ie */ "\u0450", - /* keyspec_nordic_row1_11 ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, - /* ~ morekeys_cyrillic_o */ - // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE - /* morekeys_cyrillic_i */ "\u045D", - // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE - /* keyspec_south_slavic_row1_6 */ "\u0455", - // U+045C: "ќ" CYRILLIC SMALL LETTER KJE - /* keyspec_south_slavic_row2_11 */ "\u045C", - // U+0437: "з" CYRILLIC SMALL LETTER ZE - /* keyspec_south_slavic_row3_1 */ "\u0437", - // U+0453: "ѓ" CYRILLIC SMALL LETTER GJE - /* keyspec_south_slavic_row3_8 */ "\u0453", - }; - - /* Locale ml_IN: Malayalam (India) */ - private static final String[] TEXTS_ml_IN = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0D05: "അ" MALAYALAM LETTER A - /* keylabel_to_alpha */ "\u0D05", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+20B9: "₹" INDIAN RUPEE SIGN - /* keyspec_currency */ "\u20B9", - }; - - /* Locale mn_MN: Mongolian (Mongolia) */ - private static final String[] TEXTS_mn_MN = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0410: "А" CYRILLIC CAPITAL LETTER A - // U+0411: "Б" CYRILLIC CAPITAL LETTER BE - // U+0412: "В" CYRILLIC CAPITAL LETTER VE - /* keylabel_to_alpha */ "\u0410\u0411\u0412", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+20AE: "₮" TUGRIK SIGN - /* keyspec_currency */ "\u20AE", - }; - - /* Locale mr_IN: Marathi (India) */ - private static final String[] TEXTS_mr_IN = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0915: "क" DEVANAGARI LETTER KA - // U+0916: "ख" DEVANAGARI LETTER KHA - // U+0917: "ग" DEVANAGARI LETTER GA - /* keylabel_to_alpha */ "\u0915\u0916\u0917", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+20B9: "₹" INDIAN RUPEE SIGN - /* keyspec_currency */ "\u20B9", - /* morekeys_y ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, - /* ~ morekeys_cyrillic_soft_sign */ - // U+0967: "१" DEVANAGARI DIGIT ONE - /* keyspec_symbols_1 */ "\u0967", - // U+0968: "२" DEVANAGARI DIGIT TWO - /* keyspec_symbols_2 */ "\u0968", - // U+0969: "३" DEVANAGARI DIGIT THREE - /* keyspec_symbols_3 */ "\u0969", - // U+096A: "४" DEVANAGARI DIGIT FOUR - /* keyspec_symbols_4 */ "\u096A", - // U+096B: "५" DEVANAGARI DIGIT FIVE - /* keyspec_symbols_5 */ "\u096B", - // U+096C: "६" DEVANAGARI DIGIT SIX - /* keyspec_symbols_6 */ "\u096C", - // U+096D: "७" DEVANAGARI DIGIT SEVEN - /* keyspec_symbols_7 */ "\u096D", - // U+096E: "८" DEVANAGARI DIGIT EIGHT - /* keyspec_symbols_8 */ "\u096E", - // U+096F: "९" DEVANAGARI DIGIT NINE - /* keyspec_symbols_9 */ "\u096F", - // U+0966: "०" DEVANAGARI DIGIT ZERO - /* keyspec_symbols_0 */ "\u0966", - // Label for "switch to symbols" key. - /* keylabel_to_symbol */ "?\u0967\u0968\u0969", - /* additional_morekeys_symbols_1 */ "1", - /* additional_morekeys_symbols_2 */ "2", - /* additional_morekeys_symbols_3 */ "3", - /* additional_morekeys_symbols_4 */ "4", - /* additional_morekeys_symbols_5 */ "5", - /* additional_morekeys_symbols_6 */ "6", - /* additional_morekeys_symbols_7 */ "7", - /* additional_morekeys_symbols_8 */ "8", - /* additional_morekeys_symbols_9 */ "9", - /* additional_morekeys_symbols_0 */ "0", - }; - - /* Locale nb: Norwegian Bokmål */ - private static final String[] TEXTS_nb = { - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E5,\u00E6,\u00E4,\u00E0,\u00E1,\u00E2,\u00E3,\u0101", - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F8,\u00F6,\u00F4,\u00F2,\u00F3,\u00F5,\u0153,\u014D", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113", - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B", - /* keylabel_to_alpha ~ */ - null, null, null, null, - /* ~ morekeys_c */ - /* double_quotes */ "!text/double_9qm_rqm", - /* morekeys_s */ null, - /* single_quotes */ "!text/single_9qm_rqm", - /* keyspec_currency ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_cyrillic_ie */ - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - /* keyspec_nordic_row1_11 */ "\u00E5", - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - /* keyspec_nordic_row2_10 */ "\u00F8", - // U+00E6: "æ" LATIN SMALL LETTER AE - /* keyspec_nordic_row2_11 */ "\u00E6", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - /* morekeys_nordic_row2_10 */ "\u00F6", - /* keyspec_east_slavic_row1_9 ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_tablet_period */ - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - /* morekeys_nordic_row2_11 */ "\u00E4", - }; - - /* Locale ne_NP: Nepali (Nepal) */ - private static final String[] TEXTS_ne_NP = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0915: "क" DEVANAGARI LETTER KA - // U+0916: "ख" DEVANAGARI LETTER KHA - // U+0917: "ग" DEVANAGARI LETTER GA - /* keylabel_to_alpha */ "\u0915\u0916\u0917", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN - /* keyspec_currency */ "\u0930\u0941.", - /* morekeys_y ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, - /* ~ morekeys_cyrillic_soft_sign */ - // U+0967: "१" DEVANAGARI DIGIT ONE - /* keyspec_symbols_1 */ "\u0967", - // U+0968: "२" DEVANAGARI DIGIT TWO - /* keyspec_symbols_2 */ "\u0968", - // U+0969: "३" DEVANAGARI DIGIT THREE - /* keyspec_symbols_3 */ "\u0969", - // U+096A: "४" DEVANAGARI DIGIT FOUR - /* keyspec_symbols_4 */ "\u096A", - // U+096B: "५" DEVANAGARI DIGIT FIVE - /* keyspec_symbols_5 */ "\u096B", - // U+096C: "६" DEVANAGARI DIGIT SIX - /* keyspec_symbols_6 */ "\u096C", - // U+096D: "७" DEVANAGARI DIGIT SEVEN - /* keyspec_symbols_7 */ "\u096D", - // U+096E: "८" DEVANAGARI DIGIT EIGHT - /* keyspec_symbols_8 */ "\u096E", - // U+096F: "९" DEVANAGARI DIGIT NINE - /* keyspec_symbols_9 */ "\u096F", - // U+0966: "०" DEVANAGARI DIGIT ZERO - /* keyspec_symbols_0 */ "\u0966", - // Label for "switch to symbols" key. - /* keylabel_to_symbol */ "?\u0967\u0968\u0969", - /* additional_morekeys_symbols_1 */ "1", - /* additional_morekeys_symbols_2 */ "2", - /* additional_morekeys_symbols_3 */ "3", - /* additional_morekeys_symbols_4 */ "4", - /* additional_morekeys_symbols_5 */ "5", - /* additional_morekeys_symbols_6 */ "6", - /* additional_morekeys_symbols_7 */ "7", - /* additional_morekeys_symbols_8 */ "8", - /* additional_morekeys_symbols_9 */ "9", - /* additional_morekeys_symbols_0 */ "0", - /* morekeys_tablet_period */ "!autoColumnOrder!8,.,\\,,',#,),(,/,;,@,:,-,\",+,\\%,&", - /* morekeys_nordic_row2_11 ~ */ - null, null, null, - /* ~ keyspec_tablet_comma */ - // U+0964: "।" DEVANAGARI DANDA - /* keyspec_period */ "\u0964", - /* morekeys_period */ "!autoColumnOrder!9,.,\\,,?,!,#,),(,/,;,',@,:,-,\",+,\\%,&", - /* keyspec_tablet_period */ "\u0964", - }; - - /* Locale nl: Dutch */ - private static final String[] TEXTS_nl = { - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E1,\u00E4,\u00E2,\u00E0,\u00E6,\u00E3,\u00E5,\u0101", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00EB,\u00EA,\u00E8,\u0119,\u0117,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FC,\u00FB,\u00F9,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - // U+0133: "ij" LATIN SMALL LIGATURE IJ - /* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B,\u0133", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u00F1,\u0144", - /* morekeys_c */ null, - /* double_quotes */ "!text/double_9qm_rqm", - /* morekeys_s */ null, - /* single_quotes */ "!text/single_9qm_rqm", - /* keyspec_currency */ null, - // U+0133: "ij" LATIN SMALL LIGATURE IJ - /* morekeys_y */ "\u0133", - }; - - /* Locale pl: Polish */ - private static final String[] TEXTS_pl = { - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u0105,\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D", - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u0119,\u00E8,\u00E9,\u00EA,\u00EB,\u0117,\u0113", - /* morekeys_u ~ */ - null, null, null, - /* ~ morekeys_i */ - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - /* morekeys_n */ "\u0144,\u00F1", - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u0107,\u00E7,\u010D", - /* double_quotes */ "!text/double_9qm_rqm", - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - /* morekeys_s */ "\u015B,\u00DF,\u0161", - /* single_quotes */ "!text/single_9qm_rqm", - /* keyspec_currency */ null, - /* morekeys_y */ null, - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - /* morekeys_z */ "\u017C,\u017A,\u017E", - /* morekeys_d */ null, - /* morekeys_t */ null, - // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE - /* morekeys_l */ "\u0142", - }; - - /* Locale pt: Portuguese */ - private static final String[] TEXTS_pt = { - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00AA: "ª" FEMININE ORDINAL INDICATOR - /* morekeys_a */ "\u00E1,\u00E3,\u00E0,\u00E2,\u00E4,\u00E5,\u00E6,\u00AA", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+00BA: "º" MASCULINE ORDINAL INDICATOR - /* morekeys_o */ "\u00F3,\u00F5,\u00F4,\u00F2,\u00F6,\u0153,\u00F8,\u014D,\u00BA", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - /* morekeys_e */ "\u00E9,\u00EA,\u00E8,\u0119,\u0117,\u0113,\u00EB", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00ED,\u00EE,\u00EC,\u00EF,\u012F,\u012B", - /* morekeys_n */ null, - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - /* morekeys_c */ "\u00E7,\u010D,\u0107", - }; - - /* Locale rm: Raeto-Romance */ - private static final String[] TEXTS_rm = { - /* morekeys_a */ null, - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - /* morekeys_o */ "\u00F2,\u00F3,\u00F6,\u00F4,\u00F5,\u0153,\u00F8", - }; - - /* Locale ro: Romanian */ - private static final String[] TEXTS_ro = { - // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u0103,\u00E2,\u00E3,\u00E0,\u00E1,\u00E4,\u00E6,\u00E5,\u0101", - /* morekeys_o ~ */ - null, null, null, null, - /* ~ keylabel_to_alpha */ - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B", - /* morekeys_n */ null, - /* morekeys_c */ null, - /* double_quotes */ "!text/double_9qm_rqm", - // U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - /* morekeys_s */ "\u0219,\u00DF,\u015B,\u0161", - /* single_quotes */ "!text/single_9qm_rqm", - /* keyspec_currency ~ */ - null, null, null, null, - /* ~ morekeys_d */ - // U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW - /* morekeys_t */ "\u021B", - }; - - /* Locale ru: Russian */ - private static final String[] TEXTS_ru = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0410: "А" CYRILLIC CAPITAL LETTER A - // U+0411: "Б" CYRILLIC CAPITAL LETTER BE - // U+0412: "В" CYRILLIC CAPITAL LETTER VE - /* keylabel_to_alpha */ "\u0410\u0411\u0412", - /* morekeys_i ~ */ - null, null, null, - /* ~ morekeys_c */ - /* double_quotes */ "!text/double_9qm_lqm", - /* morekeys_s */ null, - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency ~ */ - null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_k */ - // U+0451: "ё" CYRILLIC SMALL LETTER IO - /* morekeys_cyrillic_ie */ "\u0451", - /* keyspec_nordic_row1_11 ~ */ - null, null, null, null, - /* ~ morekeys_nordic_row2_10 */ - // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA - /* keyspec_east_slavic_row1_9 */ "\u0449", - // U+044B: "ы" CYRILLIC SMALL LETTER YERU - /* keyspec_east_slavic_row2_2 */ "\u044B", - // U+044D: "э" CYRILLIC SMALL LETTER E - /* keyspec_east_slavic_row2_11 */ "\u044D", - // U+0438: "и" CYRILLIC SMALL LETTER I - /* keyspec_east_slavic_row3_5 */ "\u0438", - // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN - /* morekeys_cyrillic_soft_sign */ "\u044A", - }; - - /* Locale si_LK: Sinhalese (Sri Lanka) */ - private static final String[] TEXTS_si_LK = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0D85: "අ" SINHALA LETTER AYANNA - // U+0D86: "ආ" SINHALA LETTER AAYANNA - /* keylabel_to_alpha */ "\u0D85,\u0D86", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+0DBB/U+0DD4: "රු" SINHALA LETTER RAYANNA/SINHALA VOWEL SIGN KETTI PAA-PILLA - /* keyspec_currency */ "\u0DBB\u0DD4", - }; - - /* Locale sk: Slovak */ - private static final String[] TEXTS_sk = { - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - /* morekeys_a */ "\u00E1,\u00E4,\u0101,\u00E0,\u00E2,\u00E3,\u00E5,\u00E6,\u0105", - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - /* morekeys_o */ "\u00F4,\u00F3,\u00F6,\u00F2,\u00F5,\u0153,\u0151,\u00F8", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+011B: "ě" LATIN SMALL LETTER E WITH CARON - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - /* morekeys_e */ "\u00E9,\u011B,\u0113,\u0117,\u00E8,\u00EA,\u00EB,\u0119", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE - /* morekeys_u */ "\u00FA,\u016F,\u00FC,\u016B,\u0173,\u00F9,\u00FB,\u0171", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+0131: "ı" LATIN SMALL LETTER DOTLESS I - /* morekeys_i */ "\u00ED,\u012B,\u012F,\u00EC,\u00EE,\u00EF,\u0131", - // U+0148: "ň" LATIN SMALL LETTER N WITH CARON - // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u0148,\u0146,\u00F1,\u0144", - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - /* morekeys_c */ "\u010D,\u00E7,\u0107", - /* double_quotes */ "!text/double_9qm_lqm", - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA - /* morekeys_s */ "\u0161,\u00DF,\u015B,\u015F", - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS - /* morekeys_y */ "\u00FD,\u00FF", - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - /* morekeys_z */ "\u017E,\u017C,\u017A", - // U+010F: "ď" LATIN SMALL LETTER D WITH CARON - /* morekeys_d */ "\u010F", - // U+0165: "ť" LATIN SMALL LETTER T WITH CARON - // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA - /* morekeys_t */ "\u0165,\u0163", - // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON - // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE - // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA - // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE - /* morekeys_l */ "\u013E,\u013A,\u013C,\u0142", - // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA - // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE - /* morekeys_g */ "\u0123,\u011F", - /* single_angle_quotes */ "!text/single_raqm_laqm", - /* double_angle_quotes */ "!text/double_raqm_laqm", - // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE - // U+0159: "ř" LATIN SMALL LETTER R WITH CARON - // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA - /* morekeys_r */ "\u0155,\u0159,\u0157", - // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA - /* morekeys_k */ "\u0137", - }; - - /* Locale sl: Slovenian */ - private static final String[] TEXTS_sl = { - /* morekeys_a ~ */ - null, null, null, null, null, null, null, - /* ~ morekeys_n */ - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - /* morekeys_c */ "\u010D,\u0107", - /* double_quotes */ "!text/double_9qm_lqm", - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - /* morekeys_s */ "\u0161", - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency */ null, - /* morekeys_y */ null, - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - /* morekeys_z */ "\u017E", - // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE - /* morekeys_d */ "\u0111", - /* morekeys_t ~ */ - null, null, null, - /* ~ morekeys_g */ - /* single_angle_quotes */ "!text/single_raqm_laqm", - /* double_angle_quotes */ "!text/double_raqm_laqm", - }; - - /* Locale sr: Serbian */ - private static final String[] TEXTS_sr = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // END: More keys definitions for Serbian (Cyrillic) - // Label for "switch to alphabetic" key. - // U+0410: "А" CYRILLIC CAPITAL LETTER A - // U+0411: "Б" CYRILLIC CAPITAL LETTER BE - // U+0412: "В" CYRILLIC CAPITAL LETTER VE - /* keylabel_to_alpha */ "\u0410\u0411\u0412", - /* morekeys_i ~ */ - null, null, null, - /* ~ morekeys_c */ - /* double_quotes */ "!text/double_9qm_lqm", - /* morekeys_s */ null, - /* single_quotes */ "!text/single_9qm_lqm", - /* keyspec_currency ~ */ - null, null, null, null, null, null, null, - /* ~ morekeys_g */ - /* single_angle_quotes */ "!text/single_raqm_laqm", - /* double_angle_quotes */ "!text/double_raqm_laqm", - /* morekeys_r */ null, - /* morekeys_k */ null, - // U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE - /* morekeys_cyrillic_ie */ "\u0450", - /* keyspec_nordic_row1_11 ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, - /* ~ morekeys_cyrillic_o */ - // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE - /* morekeys_cyrillic_i */ "\u045D", - // TODO: Move these to sr-Latn once we can handle IETF language tag with script name specified. - // BEGIN: More keys definitions for Serbian (Latin) - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // <string name="morekeys_s">š,ß,ś</string> - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // <string name="morekeys_c">č,ç,ć</string> - // U+010F: "ď" LATIN SMALL LETTER D WITH CARON - // <string name="morekeys_d">ď</string> - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - // <string name="morekeys_z">ž,ź,ż</string> - // END: More keys definitions for Serbian (Latin) - // BEGIN: More keys definitions for Serbian (Cyrillic) - // U+0437: "з" CYRILLIC SMALL LETTER ZE - /* keyspec_south_slavic_row1_6 */ "\u0437", - // U+045B: "ћ" CYRILLIC SMALL LETTER TSHE - /* keyspec_south_slavic_row2_11 */ "\u045B", - // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE - /* keyspec_south_slavic_row3_1 */ "\u0455", - // U+0452: "ђ" CYRILLIC SMALL LETTER DJE - /* keyspec_south_slavic_row3_8 */ "\u0452", - }; - - /* Locale sr_ZZ: Serbian (ZZ) */ - private static final String[] TEXTS_sr_ZZ = { - /* morekeys_a */ null, - /* morekeys_o */ null, - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - /* morekeys_e */ "\u00E8", - /* morekeys_u */ null, - /* keylabel_to_alpha */ null, - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - /* morekeys_i */ "\u00EC", - /* morekeys_n */ null, - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - /* morekeys_c */ "\u010D,\u0107,%", - /* double_quotes */ null, - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - /* morekeys_s */ "\u0161,%", - /* single_quotes ~ */ - null, null, null, - /* ~ morekeys_y */ - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - /* morekeys_z */ "\u017E,%", - // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE - /* morekeys_d */ "\u0111,%", - /* morekeys_t ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, - /* ~ morekeys_symbols_percent */ - /* label_go_key */ "Idi", - /* label_send_key */ "\u0160alji", - /* label_next_key */ "Sled", - /* label_done_key */ "Gotov", - /* label_search_key */ "Tra\u017Ei", - /* label_previous_key */ "Preth", - /* label_pause_key */ "Pauza", - /* label_wait_key */ "\u010Cekaj", - }; - - /* Locale sv: Swedish */ - private static final String[] TEXTS_sv = { - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E5: "å" LATIN SMALL LETTER A WITH RING - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - /* morekeys_a */ "\u00E4,\u00E5,\u00E6,\u00E1,\u00E0,\u00E2,\u0105,\u00E3", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F6,\u00F8,\u0153,\u00F3,\u00F2,\u00F4,\u00F5,\u014D", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0119", - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FC,\u00FA,\u00F9,\u00FB,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - /* morekeys_i */ "\u00ED,\u00EC,\u00EE,\u00EF", - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0148: "ň" LATIN SMALL LETTER N WITH CARON - /* morekeys_n */ "\u0144,\u00F1,\u0148", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u00E7,\u0107,\u010D", - /* double_quotes */ null, - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - /* morekeys_s */ "\u015B,\u0161,\u015F,\u00DF", - /* single_quotes */ null, - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS - /* morekeys_y */ "\u00FD,\u00FF", - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - /* morekeys_z */ "\u017A,\u017E,\u017C", - // U+00F0: "ð" LATIN SMALL LETTER ETH - // U+010F: "ď" LATIN SMALL LETTER D WITH CARON - /* morekeys_d */ "\u00F0,\u010F", - // U+0165: "ť" LATIN SMALL LETTER T WITH CARON - // U+00FE: "þ" LATIN SMALL LETTER THORN - /* morekeys_t */ "\u0165,\u00FE", - // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE - /* morekeys_l */ "\u0142", - /* morekeys_g */ null, - /* single_angle_quotes */ "!text/single_raqm_laqm", - /* double_angle_quotes */ "!text/double_raqm_laqm", - // U+0159: "ř" LATIN SMALL LETTER R WITH CARON - /* morekeys_r */ "\u0159", - /* morekeys_k */ null, - /* morekeys_cyrillic_ie */ null, - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - /* keyspec_nordic_row1_11 */ "\u00E5", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - /* keyspec_nordic_row2_10 */ "\u00F6", - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - /* keyspec_nordic_row2_11 */ "\u00E4", - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+0153: "œ" LATIN SMALL LIGATURE OE - /* morekeys_nordic_row2_10 */ "\u00F8,\u0153", - /* keyspec_east_slavic_row1_9 ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_tablet_period */ - // U+00E6: "æ" LATIN SMALL LETTER AE - /* morekeys_nordic_row2_11 */ "\u00E6", - }; - - /* Locale sw: Swahili */ - private static final String[] TEXTS_sw = { - // This is the same as English except morekeys_g. - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101", - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - /* morekeys_o */ "\u00F4,\u00F6,\u00F2,\u00F3,\u0153,\u00F8,\u014D,\u00F5", - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113", - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B", - /* keylabel_to_alpha */ null, - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - /* morekeys_i */ "\u00EE,\u00EF,\u00ED,\u012B,\u00EC", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - /* morekeys_n */ "\u00F1", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - /* morekeys_c */ "\u00E7", - /* double_quotes */ null, - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - /* morekeys_s */ "\u00DF", - /* single_quotes ~ */ - null, null, null, null, null, null, null, - /* ~ morekeys_l */ - /* morekeys_g */ "g\'", - }; - - /* Locale ta_IN: Tamil (India) */ - private static final String[] TEXTS_ta_IN = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0BA4: "த" TAMIL LETTER TA - // U+0BAE/U+0BBF: "மி" TAMIL LETTER MA/TAMIL VOWEL SIGN I - // U+0BB4/U+0BCD: "ழ்" TAMIL LETTER LLLA/TAMIL SIGN VIRAMA - /* keylabel_to_alpha */ "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+0BF9: "௹" TAMIL RUPEE SIGN - /* keyspec_currency */ "\u0BF9", - }; - - /* Locale ta_LK: Tamil (Sri Lanka) */ - private static final String[] TEXTS_ta_LK = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0BA4: "த" TAMIL LETTER TA - // U+0BAE/U+0BBF: "மி" TAMIL LETTER MA/TAMIL VOWEL SIGN I - // U+0BB4/U+0BCD: "ழ்" TAMIL LETTER LLLA/TAMIL SIGN VIRAMA - /* keylabel_to_alpha */ "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+0DBB/U+0DD4: "රු" SINHALA LETTER RAYANNA/SINHALA VOWEL SIGN KETTI PAA-PILLA - /* keyspec_currency */ "\u0DBB\u0DD4", - }; - - /* Locale ta_SG: Tamil (Singapore) */ - private static final String[] TEXTS_ta_SG = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0BA4: "த" TAMIL LETTER TA - // U+0BAE/U+0BBF: "மி" TAMIL LETTER MA/TAMIL VOWEL SIGN I - // U+0BB4/U+0BCD: "ழ்" TAMIL LETTER LLLA/TAMIL SIGN VIRAMA - /* keylabel_to_alpha */ "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD", - }; - - /* Locale te_IN: Telugu (India) */ - private static final String[] TEXTS_te_IN = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0C05: "అ" TELUGU LETTER A - // U+0C06: "ఆ" TELUGU LETTER AA - // U+0C07: "ఇ" TELUGU LETTER I - /* keylabel_to_alpha */ "\u0C05\u0C06\u0C07", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+20B9: "₹" INDIAN RUPEE SIGN - /* keyspec_currency */ "\u20B9", - }; - - /* Locale th: Thai */ - private static final String[] TEXTS_th = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0E01: "ก" THAI CHARACTER KO KAI - // U+0E02: "ข" THAI CHARACTER KHO KHAI - // U+0E04: "ค" THAI CHARACTER KHO KHWAI - /* keylabel_to_alpha */ "\u0E01\u0E02\u0E04", - /* morekeys_i ~ */ - null, null, null, null, null, null, - /* ~ single_quotes */ - // U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT - /* keyspec_currency */ "\u0E3F", - }; - - /* Locale tl: Tagalog */ - private static final String[] TEXTS_tl = { - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+00AA: "ª" FEMININE ORDINAL INDICATOR - /* morekeys_a */ "\u00E1,\u00E0,\u00E4,\u00E2,\u00E3,\u00E5,\u0105,\u00E6,\u0101,\u00AA", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+00BA: "º" MASCULINE ORDINAL INDICATOR - /* morekeys_o */ "\u00F3,\u00F2,\u00F6,\u00F4,\u00F5,\u00F8,\u0153,\u014D,\u00BA", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00E8,\u00EB,\u00EA,\u0119,\u0117,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FC,\u00F9,\u00FB,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u00ED,\u00EF,\u00EC,\u00EE,\u012F,\u012B", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - /* morekeys_n */ "\u00F1,\u0144", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u00E7,\u0107,\u010D", - }; - - /* Locale tr: Turkish */ - private static final String[] TEXTS_tr = { - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - /* morekeys_a */ "\u00E2,\u00E4,\u00E1", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D", - // U+0259: "ə" LATIN SMALL LETTER SCHWA - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - /* morekeys_e */ "\u0259,\u00E9", - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B", - /* keylabel_to_alpha */ null, - // U+0131: "ı" LATIN SMALL LETTER DOTLESS I - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B", - // U+0148: "ň" LATIN SMALL LETTER N WITH CARON - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - /* morekeys_n */ "\u0148,\u00F1", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u00E7,\u0107,\u010D", - /* double_quotes */ null, - // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - /* morekeys_s */ "\u015F,\u00DF,\u015B,\u0161", - /* single_quotes */ null, - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - /* morekeys_y */ "\u00FD", - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - /* morekeys_z */ "\u017E", - /* morekeys_d ~ */ - null, null, null, - /* ~ morekeys_l */ - // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE - /* morekeys_g */ "\u011F", - }; - - /* Locale uk: Ukrainian */ - private static final String[] TEXTS_uk = { - /* morekeys_a ~ */ - null, null, null, null, - /* ~ morekeys_u */ - // Label for "switch to alphabetic" key. - // U+0410: "А" CYRILLIC CAPITAL LETTER A - // U+0411: "Б" CYRILLIC CAPITAL LETTER BE - // U+0412: "В" CYRILLIC CAPITAL LETTER VE - /* keylabel_to_alpha */ "\u0410\u0411\u0412", - /* morekeys_i ~ */ - null, null, null, - /* ~ morekeys_c */ - /* double_quotes */ "!text/double_9qm_lqm", - /* morekeys_s */ null, - /* single_quotes */ "!text/single_9qm_lqm", - // U+20B4: "₴" HRYVNIA SIGN - /* keyspec_currency */ "\u20B4", - /* morekeys_y ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_nordic_row2_10 */ - // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA - /* keyspec_east_slavic_row1_9 */ "\u0449", - // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I - /* keyspec_east_slavic_row2_2 */ "\u0456", - // U+0454: "є" CYRILLIC SMALL LETTER UKRAINIAN IE - /* keyspec_east_slavic_row2_11 */ "\u0454", - // U+0438: "и" CYRILLIC SMALL LETTER I - /* keyspec_east_slavic_row3_5 */ "\u0438", - // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN - /* morekeys_cyrillic_soft_sign */ "\u044A", - /* keyspec_symbols_1 ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, - /* ~ morekeys_w */ - // U+0457: "ї" CYRILLIC SMALL LETTER YI - /* morekeys_east_slavic_row2_2 */ "\u0457", - /* morekeys_cyrillic_u */ null, - /* morekeys_cyrillic_en */ null, - // U+0491: "ґ" CYRILLIC SMALL LETTER GHE WITH UPTURN - /* morekeys_cyrillic_ghe */ "\u0491", - }; - - /* Locale uz_UZ: Uzbek (Uzbekistan) */ - private static final String[] TEXTS_uz_UZ = { - // This is the same as Turkish - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - /* morekeys_a */ "\u00E2,\u00E4,\u00E1", - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - /* morekeys_o */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D", - // U+0259: "ə" LATIN SMALL LETTER SCHWA - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - /* morekeys_e */ "\u0259,\u00E9", - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B", - /* keylabel_to_alpha */ null, - // U+0131: "ı" LATIN SMALL LETTER DOTLESS I - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - /* morekeys_i */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B", - // U+0148: "ň" LATIN SMALL LETTER N WITH CARON - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - /* morekeys_n */ "\u0148,\u00F1", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u00E7,\u0107,\u010D", - /* double_quotes */ null, - // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - /* morekeys_s */ "\u015F,\u00DF,\u015B,\u0161", - /* single_quotes */ null, - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - /* morekeys_y */ "\u00FD", - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - /* morekeys_z */ "\u017E", - /* morekeys_d ~ */ - null, null, null, - /* ~ morekeys_l */ - // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE - /* morekeys_g */ "\u011F", - }; - - /* Locale vi: Vietnamese */ - private static final String[] TEXTS_vi = { - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+1EA3: "ả" LATIN SMALL LETTER A WITH HOOK ABOVE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+1EA1: "ạ" LATIN SMALL LETTER A WITH DOT BELOW - // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE - // U+1EB1: "ằ" LATIN SMALL LETTER A WITH BREVE AND GRAVE - // U+1EAF: "ắ" LATIN SMALL LETTER A WITH BREVE AND ACUTE - // U+1EB3: "ẳ" LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE - // U+1EB5: "ẵ" LATIN SMALL LETTER A WITH BREVE AND TILDE - // U+1EB7: "ặ" LATIN SMALL LETTER A WITH BREVE AND DOT BELOW - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+1EA7: "ầ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE - // U+1EA5: "ấ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE - // U+1EA9: "ẩ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE - // U+1EAB: "ẫ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE - // U+1EAD: "ậ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW - /* morekeys_a */ "\u00E0,\u00E1,\u1EA3,\u00E3,\u1EA1,\u0103,\u1EB1,\u1EAF,\u1EB3,\u1EB5,\u1EB7,\u00E2,\u1EA7,\u1EA5,\u1EA9,\u1EAB,\u1EAD", - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+1ECF: "ỏ" LATIN SMALL LETTER O WITH HOOK ABOVE - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+1ECD: "ọ" LATIN SMALL LETTER O WITH DOT BELOW - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+1ED3: "ồ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE - // U+1ED1: "ố" LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE - // U+1ED5: "ổ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE - // U+1ED7: "ỗ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE - // U+1ED9: "ộ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW - // U+01A1: "ơ" LATIN SMALL LETTER O WITH HORN - // U+1EDD: "ờ" LATIN SMALL LETTER O WITH HORN AND GRAVE - // U+1EDB: "ớ" LATIN SMALL LETTER O WITH HORN AND ACUTE - // U+1EDF: "ở" LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE - // U+1EE1: "ỡ" LATIN SMALL LETTER O WITH HORN AND TILDE - // U+1EE3: "ợ" LATIN SMALL LETTER O WITH HORN AND DOT BELOW - /* morekeys_o */ "\u00F2,\u00F3,\u1ECF,\u00F5,\u1ECD,\u00F4,\u1ED3,\u1ED1,\u1ED5,\u1ED7,\u1ED9,\u01A1,\u1EDD,\u1EDB,\u1EDF,\u1EE1,\u1EE3", - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+1EBB: "ẻ" LATIN SMALL LETTER E WITH HOOK ABOVE - // U+1EBD: "ẽ" LATIN SMALL LETTER E WITH TILDE - // U+1EB9: "ẹ" LATIN SMALL LETTER E WITH DOT BELOW - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+1EC1: "ề" LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE - // U+1EBF: "ế" LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE - // U+1EC3: "ể" LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE - // U+1EC5: "ễ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE - // U+1EC7: "ệ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW - /* morekeys_e */ "\u00E8,\u00E9,\u1EBB,\u1EBD,\u1EB9,\u00EA,\u1EC1,\u1EBF,\u1EC3,\u1EC5,\u1EC7", - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+1EE7: "ủ" LATIN SMALL LETTER U WITH HOOK ABOVE - // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE - // U+1EE5: "ụ" LATIN SMALL LETTER U WITH DOT BELOW - // U+01B0: "ư" LATIN SMALL LETTER U WITH HORN - // U+1EEB: "ừ" LATIN SMALL LETTER U WITH HORN AND GRAVE - // U+1EE9: "ứ" LATIN SMALL LETTER U WITH HORN AND ACUTE - // U+1EED: "ử" LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE - // U+1EEF: "ữ" LATIN SMALL LETTER U WITH HORN AND TILDE - // U+1EF1: "ự" LATIN SMALL LETTER U WITH HORN AND DOT BELOW - /* morekeys_u */ "\u00F9,\u00FA,\u1EE7,\u0169,\u1EE5,\u01B0,\u1EEB,\u1EE9,\u1EED,\u1EEF,\u1EF1", - /* keylabel_to_alpha */ null, - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+1EC9: "ỉ" LATIN SMALL LETTER I WITH HOOK ABOVE - // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE - // U+1ECB: "ị" LATIN SMALL LETTER I WITH DOT BELOW - /* morekeys_i */ "\u00EC,\u00ED,\u1EC9,\u0129,\u1ECB", - /* morekeys_n ~ */ - null, null, null, null, null, - /* ~ single_quotes */ - // U+20AB: "₫" DONG SIGN - /* keyspec_currency */ "\u20AB", - // U+1EF3: "ỳ" LATIN SMALL LETTER Y WITH GRAVE - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+1EF7: "ỷ" LATIN SMALL LETTER Y WITH HOOK ABOVE - // U+1EF9: "ỹ" LATIN SMALL LETTER Y WITH TILDE - // U+1EF5: "ỵ" LATIN SMALL LETTER Y WITH DOT BELOW - /* morekeys_y */ "\u1EF3,\u00FD,\u1EF7,\u1EF9,\u1EF5", - /* morekeys_z */ null, - // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE - /* morekeys_d */ "\u0111", - }; - - /* Locale zu: Zulu */ - private static final String[] TEXTS_zu = { - // This is the same as English - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101", - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - /* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u0153,\u00F8,\u014D,\u00F5", - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113", - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - /* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B", - /* keylabel_to_alpha */ null, - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - /* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u012B,\u00EC", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - /* morekeys_n */ "\u00F1", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - /* morekeys_c */ "\u00E7", - /* double_quotes */ null, - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - /* morekeys_s */ "\u00DF", - }; - - /* Locale zz: Alphabet */ - private static final String[] TEXTS_zz = { - // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE - // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE - // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX - // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS - // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE - // U+00E6: "æ" LATIN SMALL LETTER AE - // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE - // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK - // U+00AA: "ª" FEMININE ORDINAL INDICATOR - /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E3,\u00E4,\u00E5,\u00E6,\u0101,\u0103,\u0105,\u00AA", - // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE - // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE - // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX - // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE - // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS - // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE - // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON - // U+014F: "ŏ" LATIN SMALL LETTER O WITH BREVE - // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE - // U+0153: "œ" LATIN SMALL LIGATURE OE - // U+00BA: "º" MASCULINE ORDINAL INDICATOR - /* morekeys_o */ "\u00F2,\u00F3,\u00F4,\u00F5,\u00F6,\u00F8,\u014D,\u014F,\u0151,\u0153,\u00BA", - // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE - // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE - // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX - // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS - // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON - // U+0115: "ĕ" LATIN SMALL LETTER E WITH BREVE - // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE - // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK - // U+011B: "ě" LATIN SMALL LETTER E WITH CARON - /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113,\u0115,\u0117,\u0119,\u011B", - // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE - // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE - // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX - // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS - // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE - // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON - // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE - // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE - // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE - // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK - /* morekeys_u */ "\u00F9,\u00FA,\u00FB,\u00FC,\u0169,\u016B,\u016D,\u016F,\u0171,\u0173", - /* keylabel_to_alpha */ null, - // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE - // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE - // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX - // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS - // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE - // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON - // U+012D: "ĭ" LATIN SMALL LETTER I WITH BREVE - // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK - // U+0131: "ı" LATIN SMALL LETTER DOTLESS I - // U+0133: "ij" LATIN SMALL LIGATURE IJ - /* morekeys_i */ "\u00EC,\u00ED,\u00EE,\u00EF,\u0129,\u012B,\u012D,\u012F,\u0131,\u0133", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE - // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA - // U+0148: "ň" LATIN SMALL LETTER N WITH CARON - // U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE - // U+014B: "ŋ" LATIN SMALL LETTER ENG - /* morekeys_n */ "\u00F1,\u0144,\u0146,\u0148,\u0149,\u014B", - // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA - // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE - // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX - // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE - // U+010D: "č" LATIN SMALL LETTER C WITH CARON - /* morekeys_c */ "\u00E7,\u0107,\u0109,\u010B,\u010D", - /* double_quotes */ null, - // U+00DF: "ß" LATIN SMALL LETTER SHARP S - // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE - // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX - // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA - // U+0161: "š" LATIN SMALL LETTER S WITH CARON - // U+017F: "ſ" LATIN SMALL LETTER LONG S - /* morekeys_s */ "\u00DF,\u015B,\u015D,\u015F,\u0161,\u017F", - /* single_quotes */ null, - /* keyspec_currency */ null, - // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE - // U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX - // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS - // U+0133: "ij" LATIN SMALL LIGATURE IJ - /* morekeys_y */ "\u00FD,\u0177,\u00FF,\u0133", - // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE - // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE - // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON - /* morekeys_z */ "\u017A,\u017C,\u017E", - // U+010F: "ď" LATIN SMALL LETTER D WITH CARON - // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE - // U+00F0: "ð" LATIN SMALL LETTER ETH - /* morekeys_d */ "\u010F,\u0111,\u00F0", - // U+00FE: "þ" LATIN SMALL LETTER THORN - // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA - // U+0165: "ť" LATIN SMALL LETTER T WITH CARON - // U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE - /* morekeys_t */ "\u00FE,\u0163,\u0165,\u0167", - // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE - // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA - // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON - // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT - // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE - /* morekeys_l */ "\u013A,\u013C,\u013E,\u0140,\u0142", - // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX - // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE - // U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE - // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA - /* morekeys_g */ "\u011D,\u011F,\u0121,\u0123", - /* single_angle_quotes */ null, - /* double_angle_quotes */ null, - // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE - // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA - // U+0159: "ř" LATIN SMALL LETTER R WITH CARON - /* morekeys_r */ "\u0155,\u0157,\u0159", - // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA - // U+0138: "ĸ" LATIN SMALL LETTER KRA - /* morekeys_k */ "\u0137,\u0138", - /* morekeys_cyrillic_ie ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, - /* ~ morekeys_question */ - // U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX - /* morekeys_h */ "\u0125", - // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX - /* morekeys_w */ "\u0175", - /* morekeys_east_slavic_row2_2 ~ */ - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, - /* ~ morekeys_v */ - // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX - /* morekeys_j */ "\u0135", - }; - - private static final Object[] LOCALES_AND_TEXTS = { - // "locale", TEXT_ARRAY, /* numberOfNonNullText/lengthOf_TEXT_ARRAY localeName */ - "DEFAULT", TEXTS_DEFAULT, /* 176/176 DEFAULT */ - "af" , TEXTS_af, /* 7/ 13 Afrikaans */ - "ar" , TEXTS_ar, /* 55/110 Arabic */ - "az_AZ" , TEXTS_az_AZ, /* 11/ 18 Azerbaijani (Azerbaijan) */ - "be_BY" , TEXTS_be_BY, /* 9/ 32 Belarusian (Belarus) */ - "bg" , TEXTS_bg, /* 2/ 9 Bulgarian */ - "bn_BD" , TEXTS_bn_BD, /* 2/ 12 Bengali (Bangladesh) */ - "bn_IN" , TEXTS_bn_IN, /* 2/ 12 Bengali (India) */ - "ca" , TEXTS_ca, /* 11/ 99 Catalan */ - "cs" , TEXTS_cs, /* 17/ 21 Czech */ - "da" , TEXTS_da, /* 19/ 55 Danish */ - "de" , TEXTS_de, /* 16/ 66 German */ - "el" , TEXTS_el, /* 1/ 5 Greek */ - "en" , TEXTS_en, /* 8/ 10 English */ - "eo" , TEXTS_eo, /* 26/126 Esperanto */ - "es" , TEXTS_es, /* 8/ 56 Spanish */ - "et_EE" , TEXTS_et_EE, /* 22/ 27 Estonian (Estonia) */ - "eu_ES" , TEXTS_eu_ES, /* 7/ 8 Basque (Spain) */ - "fa" , TEXTS_fa, /* 58/133 Persian */ - "fi" , TEXTS_fi, /* 10/ 55 Finnish */ - "fr" , TEXTS_fr, /* 13/ 66 French */ - "gl_ES" , TEXTS_gl_ES, /* 7/ 8 Gallegan (Spain) */ - "hi" , TEXTS_hi, /* 27/ 60 Hindi */ - "hi_ZZ" , TEXTS_hi_ZZ, /* 9/118 Hindi (ZZ) */ - "hr" , TEXTS_hr, /* 9/ 20 Croatian */ - "hu" , TEXTS_hu, /* 9/ 20 Hungarian */ - "hy_AM" , TEXTS_hy_AM, /* 9/134 Armenian (Armenia) */ - "is" , TEXTS_is, /* 10/ 16 Icelandic */ - "it" , TEXTS_it, /* 11/ 66 Italian */ - "iw" , TEXTS_iw, /* 20/131 Hebrew */ - "ka_GE" , TEXTS_ka_GE, /* 3/ 11 Georgian (Georgia) */ - "kk" , TEXTS_kk, /* 15/129 Kazakh */ - "km_KH" , TEXTS_km_KH, /* 2/130 Khmer (Cambodia) */ - "kn_IN" , TEXTS_kn_IN, /* 2/ 12 Kannada (India) */ - "ky" , TEXTS_ky, /* 10/ 92 Kirghiz */ - "lo_LA" , TEXTS_lo_LA, /* 2/ 12 Lao (Laos) */ - "lt" , TEXTS_lt, /* 18/ 22 Lithuanian */ - "lv" , TEXTS_lv, /* 18/ 22 Latvian */ - "mk" , TEXTS_mk, /* 9/ 97 Macedonian */ - "ml_IN" , TEXTS_ml_IN, /* 2/ 12 Malayalam (India) */ - "mn_MN" , TEXTS_mn_MN, /* 2/ 12 Mongolian (Mongolia) */ - "mr_IN" , TEXTS_mr_IN, /* 23/ 53 Marathi (India) */ - "nb" , TEXTS_nb, /* 11/ 55 Norwegian Bokmål */ - "ne_NP" , TEXTS_ne_NP, /* 27/ 60 Nepali (Nepal) */ - "nl" , TEXTS_nl, /* 9/ 13 Dutch */ - "pl" , TEXTS_pl, /* 10/ 17 Polish */ - "pt" , TEXTS_pt, /* 6/ 8 Portuguese */ - "rm" , TEXTS_rm, /* 1/ 2 Raeto-Romance */ - "ro" , TEXTS_ro, /* 6/ 16 Romanian */ - "ru" , TEXTS_ru, /* 9/ 32 Russian */ - "si_LK" , TEXTS_si_LK, /* 2/ 12 Sinhalese (Sri Lanka) */ - "sk" , TEXTS_sk, /* 20/ 22 Slovak */ - "sl" , TEXTS_sl, /* 8/ 20 Slovenian */ - "sr" , TEXTS_sr, /* 11/ 97 Serbian */ - "sr_ZZ" , TEXTS_sr_ZZ, /* 14/118 Serbian (ZZ) */ - "sv" , TEXTS_sv, /* 21/ 55 Swedish */ - "sw" , TEXTS_sw, /* 9/ 18 Swahili */ - "ta_IN" , TEXTS_ta_IN, /* 2/ 12 Tamil (India) */ - "ta_LK" , TEXTS_ta_LK, /* 2/ 12 Tamil (Sri Lanka) */ - "ta_SG" , TEXTS_ta_SG, /* 1/ 5 Tamil (Singapore) */ - "te_IN" , TEXTS_te_IN, /* 2/ 12 Telugu (India) */ - "th" , TEXTS_th, /* 2/ 12 Thai */ - "tl" , TEXTS_tl, /* 7/ 8 Tagalog */ - "tr" , TEXTS_tr, /* 11/ 18 Turkish */ - "uk" , TEXTS_uk, /* 11/ 91 Ukrainian */ - "uz_UZ" , TEXTS_uz_UZ, /* 11/ 18 Uzbek (Uzbekistan) */ - "vi" , TEXTS_vi, /* 8/ 15 Vietnamese */ - "zu" , TEXTS_zu, /* 8/ 10 Zulu */ - "zz" , TEXTS_zz, /* 19/120 Alphabet */ - }; - - static { - for (int index = 0; index < NAMES.length; index++) { - sNameToIndexesMap.put(NAMES[index], index); - } - - for (int i = 0; i < LOCALES_AND_TEXTS.length; i += 2) { - final String locale = (String)LOCALES_AND_TEXTS[i]; - final String[] textsTable = (String[])LOCALES_AND_TEXTS[i + 1]; - sLocaleToTextsTableMap.put(locale, textsTable); - sTextsTableToLocaleMap.put(textsTable, locale); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java deleted file mode 100644 index d927cc362..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import com.android.inputmethod.annotations.UsedForTesting; - -import android.util.Log; - -import java.util.Arrays; - -/** - * Utilities for matrix operations. Don't instantiate objects inside this class to prevent - * unexpected performance regressions. - */ -@UsedForTesting -public class MatrixUtils { - static final String TAG = MatrixUtils.class.getSimpleName(); - - public static class MatrixOperationFailedException extends Exception { - private static final long serialVersionUID = 4384485606788583829L; - - public MatrixOperationFailedException(String msg) { - super(msg); - Log.d(TAG, msg); - } - } - - /** - * A utility function to inverse matrix. - * Find a pivot and swap the row of squareMatrix0 and squareMatrix1 - */ - private static void findPivotAndSwapRow(final int row, final float[][] squareMatrix0, - final float[][] squareMatrix1, final int size) { - int ip = row; - float pivot = Math.abs(squareMatrix0[row][row]); - for (int i = row + 1; i < size; ++i) { - if (pivot < Math.abs(squareMatrix0[i][row])) { - ip = i; - pivot = Math.abs(squareMatrix0[i][row]); - } - } - if (ip != row) { - for (int j = 0; j < size; ++j) { - final float temp0 = squareMatrix0[ip][j]; - squareMatrix0[ip][j] = squareMatrix0[row][j]; - squareMatrix0[row][j] = temp0; - final float temp1 = squareMatrix1[ip][j]; - squareMatrix1[ip][j] = squareMatrix1[row][j]; - squareMatrix1[row][j] = temp1; - } - } - } - - /** - * A utility function to inverse matrix. This function calculates answer for each row by - * sweeping method of Gauss Jordan elimination - */ - private static void sweep(final int row, final float[][] squareMatrix0, - final float[][] squareMatrix1, final int size) throws MatrixOperationFailedException { - final float pivot = squareMatrix0[row][row]; - if (pivot == 0) { - throw new MatrixOperationFailedException("Inverse failed. Invalid pivot"); - } - for (int j = 0; j < size; ++j) { - squareMatrix0[row][j] /= pivot; - squareMatrix1[row][j] /= pivot; - } - for (int i = 0; i < size; i++) { - final float sweepTargetValue = squareMatrix0[i][row]; - if (i != row) { - for (int j = row; j < size; ++j) { - squareMatrix0[i][j] -= sweepTargetValue * squareMatrix0[row][j]; - } - for (int j = 0; j < size; ++j) { - squareMatrix1[i][j] -= sweepTargetValue * squareMatrix1[row][j]; - } - } - } - } - - /** - * A function to inverse matrix. - * The inverse matrix of squareMatrix will be output to inverseMatrix. Please notice that - * the value of squareMatrix is modified in this function and can't be resuable. - */ - @UsedForTesting - public static void inverse(final float[][] squareMatrix, - final float[][] inverseMatrix) throws MatrixOperationFailedException { - final int size = squareMatrix.length; - if (squareMatrix[0].length != size || inverseMatrix.length != size - || inverseMatrix[0].length != size) { - throw new MatrixOperationFailedException( - "--- invalid length. column should be 2 times larger than row."); - } - for (int i = 0; i < size; ++i) { - Arrays.fill(inverseMatrix[i], 0.0f); - inverseMatrix[i][i] = 1.0f; - } - for (int i = 0; i < size; ++i) { - findPivotAndSwapRow(i, squareMatrix, inverseMatrix, size); - sweep(i, squareMatrix, inverseMatrix, size); - } - } - - /** - * A matrix operation to multiply m0 and m1. - */ - @UsedForTesting - public static void multiply(final float[][] m0, final float[][] m1, - final float[][] retval) throws MatrixOperationFailedException { - if (m0[0].length != m1.length) { - throw new MatrixOperationFailedException( - "--- invalid length for multiply " + m0[0].length + ", " + m1.length); - } - final int m0h = m0.length; - final int m0w = m0[0].length; - final int m1w = m1[0].length; - if (retval.length != m0h || retval[0].length != m1w) { - throw new MatrixOperationFailedException( - "--- invalid length of retval " + retval.length + ", " + retval[0].length); - } - - for (int i = 0; i < m0h; i++) { - Arrays.fill(retval[i], 0); - for (int j = 0; j < m1w; j++) { - for (int k = 0; k < m0w; k++) { - retval[i][j] += m0[i][k] * m1[k][j]; - } - } - } - } - - /** - * A utility function to dump the specified matrix in a readable way - */ - @UsedForTesting - public static void dump(final String title, final float[][] a) { - final int column = a[0].length; - final int row = a.length; - Log.d(TAG, "Dump matrix: " + title); - Log.d(TAG, "/*---------------------"); - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < row; ++i) { - sb.setLength(0); - for (int j = 0; j < column; ++j) { - sb.append(String.format("%4f", a[i][j])).append(' '); - } - Log.d(TAG, sb.toString()); - } - Log.d(TAG, "---------------------*/"); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java b/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java deleted file mode 100644 index b254ab8d4..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard.internal; - -import android.util.Log; - -/* package */ class ModifierKeyState { - protected static final String TAG = ModifierKeyState.class.getSimpleName(); - protected static final boolean DEBUG = false; - - protected static final int RELEASING = 0; - protected static final int PRESSING = 1; - protected static final int CHORDING = 2; - - protected final String mName; - protected int mState = RELEASING; - - public ModifierKeyState(String name) { - mName = name; - } - - public void onPress() { - final int oldState = mState; - mState = PRESSING; - if (DEBUG) - Log.d(TAG, mName + ".onPress: " + toString(oldState) + " > " + this); - } - - public void onRelease() { - final int oldState = mState; - mState = RELEASING; - if (DEBUG) - Log.d(TAG, mName + ".onRelease: " + toString(oldState) + " > " + this); - } - - public void onOtherKeyPressed() { - final int oldState = mState; - if (oldState == PRESSING) - mState = CHORDING; - if (DEBUG) - Log.d(TAG, mName + ".onOtherKeyPressed: " + toString(oldState) + " > " + this); - } - - public boolean isPressing() { - return mState == PRESSING; - } - - public boolean isReleasing() { - return mState == RELEASING; - } - - public boolean isChording() { - return mState == CHORDING; - } - - @Override - public String toString() { - return toString(mState); - } - - protected String toString(int state) { - switch (state) { - case RELEASING: return "RELEASING"; - case PRESSING: return "PRESSING"; - case CHORDING: return "CHORDING"; - default: return "UNKNOWN"; - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java deleted file mode 100644 index 0bd42fc13..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.text.TextUtils; -import android.util.SparseIntArray; - -import com.android.inputmethod.compat.CharacterCompat; -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.latin.common.CollectionUtils; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Locale; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * The more key specification object. The more keys are an array of {@link MoreKeySpec}. - * - * The more keys specification is comma separated "key specification" each of which represents one - * "more key". - * The key specification might have label or string resource reference in it. These references are - * expanded before parsing comma. - * Special character, comma ',' backslash '\' can be escaped by '\' character. - * Note that the '\' is also parsed by XML parser and {@link MoreKeySpec#splitKeySpecs(String)} - * as well. - */ -// TODO: Should extend the key specification object. -public final class MoreKeySpec { - public final int mCode; - @Nullable - public final String mLabel; - @Nullable - public final String mOutputText; - public final int mIconId; - - public MoreKeySpec(@Nonnull final String moreKeySpec, boolean needsToUpperCase, - @Nonnull final Locale locale) { - if (moreKeySpec.isEmpty()) { - throw new KeySpecParser.KeySpecParserError("Empty more key spec"); - } - final String label = KeySpecParser.getLabel(moreKeySpec); - mLabel = needsToUpperCase ? StringUtils.toTitleCaseOfKeyLabel(label, locale) : label; - final int codeInSpec = KeySpecParser.getCode(moreKeySpec); - final int code = needsToUpperCase ? StringUtils.toTitleCaseOfKeyCode(codeInSpec, locale) - : codeInSpec; - if (code == Constants.CODE_UNSPECIFIED) { - // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters - // upper case representation ("SS"). - mCode = Constants.CODE_OUTPUT_TEXT; - mOutputText = mLabel; - } else { - mCode = code; - final String outputText = KeySpecParser.getOutputText(moreKeySpec); - mOutputText = needsToUpperCase - ? StringUtils.toTitleCaseOfKeyLabel(outputText, locale) : outputText; - } - mIconId = KeySpecParser.getIconId(moreKeySpec); - } - - @Nonnull - public Key buildKey(final int x, final int y, final int labelFlags, - @Nonnull final KeyboardParams params) { - return new Key(mLabel, mIconId, mCode, mOutputText, null /* hintLabel */, labelFlags, - Key.BACKGROUND_TYPE_NORMAL, x, y, params.mDefaultKeyWidth, params.mDefaultRowHeight, - params.mHorizontalGap, params.mVerticalGap); - } - - @Override - public int hashCode() { - int hashCode = 1; - hashCode = 31 + mCode; - hashCode = hashCode * 31 + mIconId; - final String label = mLabel; - hashCode = hashCode * 31 + (label == null ? 0 : label.hashCode()); - final String outputText = mOutputText; - hashCode = hashCode * 31 + (outputText == null ? 0 : outputText.hashCode()); - return hashCode; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o instanceof MoreKeySpec) { - final MoreKeySpec other = (MoreKeySpec)o; - return mCode == other.mCode - && mIconId == other.mIconId - && TextUtils.equals(mLabel, other.mLabel) - && TextUtils.equals(mOutputText, other.mOutputText); - } - return false; - } - - @Override - public String toString() { - final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel - : KeyboardIconsSet.PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId)); - final String output = (mCode == Constants.CODE_OUTPUT_TEXT ? mOutputText - : Constants.printableCode(mCode)); - if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) { - return output; - } - return label + "|" + output; - } - - public static class LettersOnBaseLayout { - private final SparseIntArray mCodes = new SparseIntArray(); - private final HashSet<String> mTexts = new HashSet<>(); - - public void addLetter(@Nonnull final Key key) { - final int code = key.getCode(); - if (CharacterCompat.isAlphabetic(code)) { - mCodes.put(code, 0); - } else if (code == Constants.CODE_OUTPUT_TEXT) { - mTexts.add(key.getOutputText()); - } - } - - public boolean contains(@Nonnull final MoreKeySpec moreKey) { - final int code = moreKey.mCode; - if (CharacterCompat.isAlphabetic(code) && mCodes.indexOfKey(code) >= 0) { - return true; - } else if (code == Constants.CODE_OUTPUT_TEXT && mTexts.contains(moreKey.mOutputText)) { - return true; - } - return false; - } - } - - @Nullable - public static MoreKeySpec[] removeRedundantMoreKeys(@Nullable final MoreKeySpec[] moreKeys, - @Nonnull final LettersOnBaseLayout lettersOnBaseLayout) { - if (moreKeys == null) { - return null; - } - final ArrayList<MoreKeySpec> filteredMoreKeys = new ArrayList<>(); - for (final MoreKeySpec moreKey : moreKeys) { - if (!lettersOnBaseLayout.contains(moreKey)) { - filteredMoreKeys.add(moreKey); - } - } - final int size = filteredMoreKeys.size(); - if (size == moreKeys.length) { - return moreKeys; - } - if (size == 0) { - return null; - } - return filteredMoreKeys.toArray(new MoreKeySpec[size]); - } - - // Constants for parsing. - private static final char COMMA = Constants.CODE_COMMA; - private static final char BACKSLASH = Constants.CODE_BACKSLASH; - private static final String ADDITIONAL_MORE_KEY_MARKER = - StringUtils.newSingleCodePointString(Constants.CODE_PERCENT); - - /** - * Split the text containing multiple key specifications separated by commas into an array of - * key specifications. - * A key specification can contain a character escaped by the backslash character, including a - * comma character. - * Note that an empty key specification will be eliminated from the result array. - * - * @param text the text containing multiple key specifications. - * @return an array of key specification text. Null if the specified <code>text</code> is empty - * or has no key specifications. - */ - @Nullable - public static String[] splitKeySpecs(@Nullable final String text) { - if (TextUtils.isEmpty(text)) { - return null; - } - final int size = text.length(); - // Optimization for one-letter key specification. - if (size == 1) { - return text.charAt(0) == COMMA ? null : new String[] { text }; - } - - ArrayList<String> list = null; - int start = 0; - // The characters in question in this loop are COMMA and BACKSLASH. These characters never - // match any high or low surrogate character. So it is OK to iterate through with char - // index. - for (int pos = 0; pos < size; pos++) { - final char c = text.charAt(pos); - if (c == COMMA) { - // Skip empty entry. - if (pos - start > 0) { - if (list == null) { - list = new ArrayList<>(); - } - list.add(text.substring(start, pos)); - } - // Skip comma - start = pos + 1; - } else if (c == BACKSLASH) { - // Skip escape character and escaped character. - pos++; - } - } - final String remain = (size - start > 0) ? text.substring(start) : null; - if (list == null) { - return remain != null ? new String[] { remain } : null; - } - if (remain != null) { - list.add(remain); - } - return list.toArray(new String[list.size()]); - } - - @Nonnull - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - - @Nonnull - private static String[] filterOutEmptyString(@Nullable final String[] array) { - if (array == null) { - return EMPTY_STRING_ARRAY; - } - ArrayList<String> out = null; - for (int i = 0; i < array.length; i++) { - final String entry = array[i]; - if (TextUtils.isEmpty(entry)) { - if (out == null) { - out = CollectionUtils.arrayAsList(array, 0, i); - } - } else if (out != null) { - out.add(entry); - } - } - if (out == null) { - return array; - } - return out.toArray(new String[out.size()]); - } - - public static String[] insertAdditionalMoreKeys(@Nullable final String[] moreKeySpecs, - @Nullable final String[] additionalMoreKeySpecs) { - final String[] moreKeys = filterOutEmptyString(moreKeySpecs); - final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs); - final int moreKeysCount = moreKeys.length; - final int additionalCount = additionalMoreKeys.length; - ArrayList<String> out = null; - int additionalIndex = 0; - for (int moreKeyIndex = 0; moreKeyIndex < moreKeysCount; moreKeyIndex++) { - final String moreKeySpec = moreKeys[moreKeyIndex]; - if (moreKeySpec.equals(ADDITIONAL_MORE_KEY_MARKER)) { - if (additionalIndex < additionalCount) { - // Replace '%' marker with additional more key specification. - final String additionalMoreKey = additionalMoreKeys[additionalIndex]; - if (out != null) { - out.add(additionalMoreKey); - } else { - moreKeys[moreKeyIndex] = additionalMoreKey; - } - additionalIndex++; - } else { - // Filter out excessive '%' marker. - if (out == null) { - out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeyIndex); - } - } - } else { - if (out != null) { - out.add(moreKeySpec); - } - } - } - if (additionalCount > 0 && additionalIndex == 0) { - // No '%' marker is found in more keys. - // Insert all additional more keys to the head of more keys. - out = CollectionUtils.arrayAsList(additionalMoreKeys, additionalIndex, additionalCount); - for (int i = 0; i < moreKeysCount; i++) { - out.add(moreKeys[i]); - } - } else if (additionalIndex < additionalCount) { - // The number of '%' markers are less than additional more keys. - // Append remained additional more keys to the tail of more keys. - out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeysCount); - for (int i = additionalIndex; i < additionalCount; i++) { - out.add(additionalMoreKeys[additionalIndex]); - } - } - if (out == null && moreKeysCount > 0) { - return moreKeys; - } else if (out != null && out.size() > 0) { - return out.toArray(new String[out.size()]); - } else { - return null; - } - } - - public static int getIntValue(@Nullable final String[] moreKeys, final String key, - final int defaultValue) { - if (moreKeys == null) { - return defaultValue; - } - final int keyLen = key.length(); - boolean foundValue = false; - int value = defaultValue; - for (int i = 0; i < moreKeys.length; i++) { - final String moreKeySpec = moreKeys[i]; - if (moreKeySpec == null || !moreKeySpec.startsWith(key)) { - continue; - } - moreKeys[i] = null; - try { - if (!foundValue) { - value = Integer.parseInt(moreKeySpec.substring(keyLen)); - foundValue = true; - } - } catch (NumberFormatException e) { - throw new RuntimeException( - "integer should follow after " + key + ": " + moreKeySpec); - } - } - return value; - } - - public static boolean getBooleanValue(@Nullable final String[] moreKeys, final String key) { - if (moreKeys == null) { - return false; - } - boolean value = false; - for (int i = 0; i < moreKeys.length; i++) { - final String moreKeySpec = moreKeys[i]; - if (moreKeySpec == null || !moreKeySpec.equals(key)) { - continue; - } - moreKeys[i] = null; - value = true; - } - return value; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java deleted file mode 100644 index 8a375c620..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import android.util.Log; -import android.view.MotionEvent; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.KeyDetector; -import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.common.CoordinateUtils; - -public final class NonDistinctMultitouchHelper { - private static final String TAG = NonDistinctMultitouchHelper.class.getSimpleName(); - - private static final int MAIN_POINTER_TRACKER_ID = 0; - private int mOldPointerCount = 1; - private Key mOldKey; - private int[] mLastCoords = CoordinateUtils.newInstance(); - - public void processMotionEvent(final MotionEvent me, final KeyDetector keyDetector) { - final int pointerCount = me.getPointerCount(); - final int oldPointerCount = mOldPointerCount; - mOldPointerCount = pointerCount; - // Ignore continuous multi-touch events because we can't trust the coordinates - // in multi-touch events. - if (pointerCount > 1 && oldPointerCount > 1) { - return; - } - - // Use only main pointer tracker. - final PointerTracker mainTracker = PointerTracker.getPointerTracker( - MAIN_POINTER_TRACKER_ID); - final int action = me.getActionMasked(); - final int index = me.getActionIndex(); - final long eventTime = me.getEventTime(); - final long downTime = me.getDownTime(); - - // In single-touch. - if (oldPointerCount == 1 && pointerCount == 1) { - if (me.getPointerId(index) == mainTracker.mPointerId) { - mainTracker.processMotionEvent(me, keyDetector); - return; - } - // Inject a copied event. - injectMotionEvent(action, me.getX(index), me.getY(index), downTime, eventTime, - mainTracker, keyDetector); - return; - } - - // Single-touch to multi-touch transition. - if (oldPointerCount == 1 && pointerCount == 2) { - // Send an up event for the last pointer, be cause we can't trust the coordinates of - // this multi-touch event. - mainTracker.getLastCoordinates(mLastCoords); - final int x = CoordinateUtils.x(mLastCoords); - final int y = CoordinateUtils.y(mLastCoords); - mOldKey = mainTracker.getKeyOn(x, y); - // Inject an artifact up event for the old key. - injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime, - mainTracker, keyDetector); - return; - } - - // Multi-touch to single-touch transition. - if (oldPointerCount == 2 && pointerCount == 1) { - // Send a down event for the latest pointer if the key is different from the previous - // key. - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - final Key newKey = mainTracker.getKeyOn(x, y); - if (mOldKey != newKey) { - // Inject an artifact down event for the new key. - // An artifact up event for the new key will usually be injected as a single-touch. - injectMotionEvent(MotionEvent.ACTION_DOWN, x, y, downTime, eventTime, - mainTracker, keyDetector); - if (action == MotionEvent.ACTION_UP) { - // Inject an artifact up event for the new key also. - injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime, - mainTracker, keyDetector); - } - } - return; - } - - Log.w(TAG, "Unknown touch panel behavior: pointer count is " - + pointerCount + " (previously " + oldPointerCount + ")"); - } - - private static void injectMotionEvent(final int action, final float x, final float y, - final long downTime, final long eventTime, final PointerTracker tracker, - final KeyDetector keyDetector) { - final MotionEvent me = MotionEvent.obtain( - downTime, eventTime, action, x, y, 0 /* metaState */); - try { - tracker.processMotionEvent(me, keyDetector); - } finally { - me.recycle(); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java deleted file mode 100644 index 556d74f4b..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard.internal; - -import android.util.Log; - -import java.util.ArrayList; - -public final class PointerTrackerQueue { - private static final String TAG = PointerTrackerQueue.class.getSimpleName(); - private static final boolean DEBUG = false; - - public interface Element { - public boolean isModifier(); - public boolean isInDraggingFinger(); - public void onPhantomUpEvent(long eventTime); - public void cancelTrackingForAction(); - } - - private static final int INITIAL_CAPACITY = 10; - // Note: {@link #mExpandableArrayOfActivePointers} and {@link #mArraySize} are synchronized by - // {@link #mExpandableArrayOfActivePointers} - private final ArrayList<Element> mExpandableArrayOfActivePointers = - new ArrayList<>(INITIAL_CAPACITY); - private int mArraySize = 0; - - public int size() { - synchronized (mExpandableArrayOfActivePointers) { - return mArraySize; - } - } - - public void add(final Element pointer) { - synchronized (mExpandableArrayOfActivePointers) { - if (DEBUG) { - Log.d(TAG, "add: " + pointer + " " + this); - } - final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; - final int arraySize = mArraySize; - if (arraySize < expandableArray.size()) { - expandableArray.set(arraySize, pointer); - } else { - expandableArray.add(pointer); - } - mArraySize = arraySize + 1; - } - } - - public void remove(final Element pointer) { - synchronized (mExpandableArrayOfActivePointers) { - if (DEBUG) { - Log.d(TAG, "remove: " + pointer + " " + this); - } - final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; - final int arraySize = mArraySize; - int newIndex = 0; - for (int index = 0; index < arraySize; index++) { - final Element element = expandableArray.get(index); - if (element == pointer) { - if (newIndex != index) { - Log.w(TAG, "Found duplicated element in remove: " + pointer); - } - continue; // Remove this element from the expandableArray. - } - if (newIndex != index) { - // Shift this element toward the beginning of the expandableArray. - expandableArray.set(newIndex, element); - } - newIndex++; - } - mArraySize = newIndex; - } - } - - public Element getOldestElement() { - synchronized (mExpandableArrayOfActivePointers) { - return (mArraySize == 0) ? null : mExpandableArrayOfActivePointers.get(0); - } - } - - public void releaseAllPointersOlderThan(final Element pointer, final long eventTime) { - synchronized (mExpandableArrayOfActivePointers) { - if (DEBUG) { - Log.d(TAG, "releaseAllPointerOlderThan: " + pointer + " " + this); - } - final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; - final int arraySize = mArraySize; - int newIndex, index; - for (newIndex = index = 0; index < arraySize; index++) { - final Element element = expandableArray.get(index); - if (element == pointer) { - break; // Stop releasing elements. - } - if (!element.isModifier()) { - element.onPhantomUpEvent(eventTime); - continue; // Remove this element from the expandableArray. - } - if (newIndex != index) { - // Shift this element toward the beginning of the expandableArray. - expandableArray.set(newIndex, element); - } - newIndex++; - } - // Shift rest of the expandableArray. - int count = 0; - for (; index < arraySize; index++) { - final Element element = expandableArray.get(index); - if (element == pointer) { - count++; - if (count > 1) { - Log.w(TAG, "Found duplicated element in releaseAllPointersOlderThan: " - + pointer); - } - } - if (newIndex != index) { - // Shift this element toward the beginning of the expandableArray. - expandableArray.set(newIndex, expandableArray.get(index)); - } - newIndex++; - } - mArraySize = newIndex; - } - } - - public void releaseAllPointers(final long eventTime) { - releaseAllPointersExcept(null, eventTime); - } - - public void releaseAllPointersExcept(final Element pointer, final long eventTime) { - synchronized (mExpandableArrayOfActivePointers) { - if (DEBUG) { - if (pointer == null) { - Log.d(TAG, "releaseAllPointers: " + this); - } else { - Log.d(TAG, "releaseAllPointerExcept: " + pointer + " " + this); - } - } - final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; - final int arraySize = mArraySize; - int newIndex = 0, count = 0; - for (int index = 0; index < arraySize; index++) { - final Element element = expandableArray.get(index); - if (element == pointer) { - count++; - if (count > 1) { - Log.w(TAG, "Found duplicated element in releaseAllPointersExcept: " - + pointer); - } - } else { - element.onPhantomUpEvent(eventTime); - continue; // Remove this element from the expandableArray. - } - if (newIndex != index) { - // Shift this element toward the beginning of the expandableArray. - expandableArray.set(newIndex, element); - } - newIndex++; - } - mArraySize = newIndex; - } - } - - public boolean hasModifierKeyOlderThan(final Element pointer) { - synchronized (mExpandableArrayOfActivePointers) { - final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; - final int arraySize = mArraySize; - for (int index = 0; index < arraySize; index++) { - final Element element = expandableArray.get(index); - if (element == pointer) { - return false; // Stop searching modifier key. - } - if (element.isModifier()) { - return true; - } - } - return false; - } - } - - public boolean isAnyInDraggingFinger() { - synchronized (mExpandableArrayOfActivePointers) { - final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; - final int arraySize = mArraySize; - for (int index = 0; index < arraySize; index++) { - final Element element = expandableArray.get(index); - if (element.isInDraggingFinger()) { - return true; - } - } - return false; - } - } - - public void cancelAllPointerTrackers() { - synchronized (mExpandableArrayOfActivePointers) { - if (DEBUG) { - Log.d(TAG, "cancelAllPointerTracker: " + this); - } - final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; - final int arraySize = mArraySize; - for (int index = 0; index < arraySize; index++) { - final Element element = expandableArray.get(index); - element.cancelTrackingForAction(); - } - } - } - - @Override - public String toString() { - synchronized (mExpandableArrayOfActivePointers) { - final StringBuilder sb = new StringBuilder(); - final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; - final int arraySize = mArraySize; - for (int index = 0; index < arraySize; index++) { - final Element element = expandableArray.get(index); - if (sb.length() > 0) { - sb.append(" "); - } - sb.append(element.toString()); - } - return "[" + sb.toString() + "]"; - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java deleted file mode 100644 index 211ef5f8b..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import android.graphics.Path; -import android.graphics.Rect; -import android.graphics.RectF; - -public final class RoundedLine { - private final RectF mArc1 = new RectF(); - private final RectF mArc2 = new RectF(); - private final Path mPath = new Path(); - - private static final double RADIAN_TO_DEGREE = 180.0d / Math.PI; - private static final double RIGHT_ANGLE = Math.PI / 2.0d; - - /** - * Make a rounded line path - * - * @param p1x the x-coordinate of the start point. - * @param p1y the y-coordinate of the start point. - * @param r1 the radius at the start point - * @param p2x the x-coordinate of the end point. - * @param p2y the y-coordinate of the end point. - * @param r2 the radius at the end point - * @return an instance of {@link Path} that holds the result rounded line, or an instance of - * {@link Path} that holds an empty path if the start and end points are equal. - */ - public Path makePath(final float p1x, final float p1y, final float r1, - final float p2x, final float p2y, final float r2) { - mPath.rewind(); - final double dx = p2x - p1x; - final double dy = p2y - p1y; - // Distance of the points. - final double l = Math.hypot(dx, dy); - if (Double.compare(0.0d, l) == 0) { - return mPath; // Return an empty path - } - // Angle of the line p1-p2 - final double a = Math.atan2(dy, dx); - // Difference of trail cap radius. - final double dr = r2 - r1; - // Variation of angle at trail cap. - final double ar = Math.asin(dr / l); - // The start angle of trail cap arc at P1. - final double aa = a - (RIGHT_ANGLE + ar); - // The end angle of trail cap arc at P2. - final double ab = a + (RIGHT_ANGLE + ar); - final float cosa = (float)Math.cos(aa); - final float sina = (float)Math.sin(aa); - final float cosb = (float)Math.cos(ab); - final float sinb = (float)Math.sin(ab); - // Closing point of arc at P1. - final float p1ax = p1x + r1 * cosa; - final float p1ay = p1y + r1 * sina; - // Opening point of arc at P1. - final float p1bx = p1x + r1 * cosb; - final float p1by = p1y + r1 * sinb; - // Opening point of arc at P2. - final float p2ax = p2x + r2 * cosa; - final float p2ay = p2y + r2 * sina; - // Closing point of arc at P2. - final float p2bx = p2x + r2 * cosb; - final float p2by = p2y + r2 * sinb; - // Start angle of the trail arcs. - final float angle = (float)(aa * RADIAN_TO_DEGREE); - final float ar2degree = (float)(ar * 2.0d * RADIAN_TO_DEGREE); - // Sweep angle of the trail arc at P1. - final float a1 = -180.0f + ar2degree; - // Sweep angle of the trail arc at P2. - final float a2 = 180.0f + ar2degree; - mArc1.set(p1x, p1y, p1x, p1y); - mArc1.inset(-r1, -r1); - mArc2.set(p2x, p2y, p2x, p2y); - mArc2.inset(-r2, -r2); - - // Trail cap at P1. - mPath.moveTo(p1x, p1y); - mPath.arcTo(mArc1, angle, a1); - // Trail cap at P2. - mPath.moveTo(p2x, p2y); - mPath.arcTo(mArc2, angle, a2); - // Two trapezoids connecting P1 and P2. - mPath.moveTo(p1ax, p1ay); - mPath.lineTo(p1x, p1y); - mPath.lineTo(p1bx, p1by); - mPath.lineTo(p2bx, p2by); - mPath.lineTo(p2x, p2y); - mPath.lineTo(p2ax, p2ay); - mPath.close(); - return mPath; - } - - public void getBounds(final Rect outBounds) { - // Reuse mArc1 as working variable - mPath.computeBounds(mArc1, true /* unused */); - mArc1.roundOut(outBounds); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java b/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java deleted file mode 100644 index a497b2eb3..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.keyboard.internal; - -import android.util.Log; - -/* package */ final class ShiftKeyState extends ModifierKeyState { - private static final int PRESSING_ON_SHIFTED = 3; // both temporary shifted & shift locked - private static final int IGNORING = 4; - - public ShiftKeyState(String name) { - super(name); - } - - @Override - public void onOtherKeyPressed() { - int oldState = mState; - if (oldState == PRESSING) { - mState = CHORDING; - } else if (oldState == PRESSING_ON_SHIFTED) { - mState = IGNORING; - } - if (DEBUG) - Log.d(TAG, mName + ".onOtherKeyPressed: " + toString(oldState) + " > " + this); - } - - public void onPressOnShifted() { - int oldState = mState; - mState = PRESSING_ON_SHIFTED; - if (DEBUG) - Log.d(TAG, mName + ".onPressOnShifted: " + toString(oldState) + " > " + this); - } - - public boolean isPressingOnShifted() { - return mState == PRESSING_ON_SHIFTED; - } - - public boolean isIgnoring() { - return mState == IGNORING; - } - - @Override - public String toString() { - return toString(mState); - } - - @Override - protected String toString(int state) { - switch (state) { - case PRESSING_ON_SHIFTED: return "PRESSING_ON_SHIFTED"; - case IGNORING: return "IGNORING"; - default: return super.toString(state); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java deleted file mode 100644 index 6837f0fcb..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; - -import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.CoordinateUtils; - -/** - * Draw rubber band preview graphics during sliding key input. - * - * @attr ref android.R.styleable#MainKeyboardView_slidingKeyInputPreviewColor - * @attr ref android.R.styleable#MainKeyboardView_slidingKeyInputPreviewWidth - * @attr ref android.R.styleable#MainKeyboardView_slidingKeyInputPreviewBodyRatio - * @attr ref android.R.styleable#MainKeyboardView_slidingKeyInputPreviewShadowRatio - */ -public final class SlidingKeyInputDrawingPreview extends AbstractDrawingPreview { - private final float mPreviewBodyRadius; - - private boolean mShowsSlidingKeyInputPreview; - private final int[] mPreviewFrom = CoordinateUtils.newInstance(); - private final int[] mPreviewTo = CoordinateUtils.newInstance(); - - // TODO: Finalize the rubber band preview implementation. - private final RoundedLine mRoundedLine = new RoundedLine(); - private final Paint mPaint = new Paint(); - - public SlidingKeyInputDrawingPreview(final TypedArray mainKeyboardViewAttr) { - final int previewColor = mainKeyboardViewAttr.getColor( - R.styleable.MainKeyboardView_slidingKeyInputPreviewColor, 0); - final float previewRadius = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_slidingKeyInputPreviewWidth, 0) / 2.0f; - final int PERCENTAGE_INT = 100; - final float previewBodyRatio = (float)mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_slidingKeyInputPreviewBodyRatio, PERCENTAGE_INT) - / (float)PERCENTAGE_INT; - mPreviewBodyRadius = previewRadius * previewBodyRatio; - final int previewShadowRatioInt = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_slidingKeyInputPreviewShadowRatio, 0); - if (previewShadowRatioInt > 0) { - final float previewShadowRatio = (float)previewShadowRatioInt / (float)PERCENTAGE_INT; - final float shadowRadius = previewRadius * previewShadowRatio; - mPaint.setShadowLayer(shadowRadius, 0.0f, 0.0f, previewColor); - } - mPaint.setColor(previewColor); - } - - @Override - public void onDeallocateMemory() { - // Nothing to do here. - } - - public void dismissSlidingKeyInputPreview() { - mShowsSlidingKeyInputPreview = false; - invalidateDrawingView(); - } - - /** - * Draws the preview - * @param canvas The canvas where the preview is drawn. - */ - @Override - public void drawPreview(final Canvas canvas) { - if (!isPreviewEnabled() || !mShowsSlidingKeyInputPreview) { - return; - } - - // TODO: Finalize the rubber band preview implementation. - final float radius = mPreviewBodyRadius; - final Path path = mRoundedLine.makePath( - CoordinateUtils.x(mPreviewFrom), CoordinateUtils.y(mPreviewFrom), radius, - CoordinateUtils.x(mPreviewTo), CoordinateUtils.y(mPreviewTo), radius); - canvas.drawPath(path, mPaint); - } - - /** - * Set the position of the preview. - * @param tracker The new location of the preview is based on the points in PointerTracker. - */ - @Override - public void setPreviewPosition(final PointerTracker tracker) { - tracker.getDownCoordinates(mPreviewFrom); - tracker.getLastCoordinates(mPreviewTo); - mShowsSlidingKeyInputPreview = true; - invalidateDrawingView(); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java b/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java deleted file mode 100644 index 10847f62d..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.keyboard.internal.MatrixUtils.MatrixOperationFailedException; - -import android.util.Log; - -import java.util.Arrays; - -/** - * Utilities to smooth coordinates. Currently, we calculate 3d least squares formula by using - * Lagrangian smoothing - */ -@UsedForTesting -public class SmoothingUtils { - private static final String TAG = SmoothingUtils.class.getSimpleName(); - private static final boolean DEBUG = false; - - private SmoothingUtils() { - // not allowed to instantiate publicly - } - - /** - * Find a most likely 3d least squares formula for specified coordinates. - * "retval" should be a 1x4 size matrix. - */ - @UsedForTesting - public static void get3DParameters(final float[] xs, final float[] ys, - final float[][] retval) throws MatrixOperationFailedException { - final int COEFF_COUNT = 4; // Coefficient count for 3d smoothing - if (retval.length != COEFF_COUNT || retval[0].length != 1) { - Log.d(TAG, "--- invalid length of 3d retval " + retval.length + ", " - + retval[0].length); - return; - } - final int N = xs.length; - // TODO: Never isntantiate the matrix - final float[][] m0 = new float[COEFF_COUNT][COEFF_COUNT]; - final float[][] m0Inv = new float[COEFF_COUNT][COEFF_COUNT]; - final float[][] m1 = new float[COEFF_COUNT][N]; - final float[][] m2 = new float[N][1]; - - // m0 - for (int i = 0; i < COEFF_COUNT; ++i) { - Arrays.fill(m0[i], 0); - for (int j = 0; j < COEFF_COUNT; ++j) { - final int pow = i + j; - for (int k = 0; k < N; ++k) { - m0[i][j] += (float) Math.pow(xs[k], pow); - } - } - } - // m0Inv - MatrixUtils.inverse(m0, m0Inv); - if (DEBUG) { - MatrixUtils.dump("m0-1", m0Inv); - } - - // m1 - for (int i = 0; i < COEFF_COUNT; ++i) { - for (int j = 0; j < N; ++j) { - m1[i][j] = (i == 0) ? 1.0f : m1[i - 1][j] * xs[j]; - } - } - - // m2 - for (int i = 0; i < N; ++i) { - m2[i][0] = ys[i]; - } - - final float[][] m0Invxm1 = new float[COEFF_COUNT][N]; - if (DEBUG) { - MatrixUtils.dump("a0", m0Inv); - MatrixUtils.dump("a1", m1); - } - MatrixUtils.multiply(m0Inv, m1, m0Invxm1); - if (DEBUG) { - MatrixUtils.dump("a2", m0Invxm1); - MatrixUtils.dump("a3", m2); - } - MatrixUtils.multiply(m0Invxm1, m2, retval); - if (DEBUG) { - MatrixUtils.dump("result", retval); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java deleted file mode 100644 index 91f3558eb..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -import android.os.Message; -import android.os.SystemClock; -import android.view.ViewConfiguration; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; - -import javax.annotation.Nonnull; - -public final class TimerHandler extends LeakGuardHandlerWrapper<DrawingProxy> - implements TimerProxy { - private static final int MSG_TYPING_STATE_EXPIRED = 0; - private static final int MSG_REPEAT_KEY = 1; - private static final int MSG_LONGPRESS_KEY = 2; - private static final int MSG_LONGPRESS_SHIFT_KEY = 3; - private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 4; - private static final int MSG_UPDATE_BATCH_INPUT = 5; - private static final int MSG_DISMISS_KEY_PREVIEW = 6; - private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 7; - - private final int mIgnoreAltCodeKeyTimeout; - private final int mGestureRecognitionUpdateTime; - - public TimerHandler(@Nonnull final DrawingProxy ownerInstance, - final int ignoreAltCodeKeyTimeout, final int gestureRecognitionUpdateTime) { - super(ownerInstance); - mIgnoreAltCodeKeyTimeout = ignoreAltCodeKeyTimeout; - mGestureRecognitionUpdateTime = gestureRecognitionUpdateTime; - } - - @Override - public void handleMessage(final Message msg) { - final DrawingProxy drawingProxy = getOwnerInstance(); - if (drawingProxy == null) { - return; - } - switch (msg.what) { - case MSG_TYPING_STATE_EXPIRED: - drawingProxy.startWhileTypingAnimation(DrawingProxy.FADE_IN); - break; - case MSG_REPEAT_KEY: - final PointerTracker tracker1 = (PointerTracker) msg.obj; - tracker1.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */); - break; - case MSG_LONGPRESS_KEY: - case MSG_LONGPRESS_SHIFT_KEY: - cancelLongPressTimers(); - final PointerTracker tracker2 = (PointerTracker) msg.obj; - tracker2.onLongPressed(); - break; - case MSG_UPDATE_BATCH_INPUT: - final PointerTracker tracker3 = (PointerTracker) msg.obj; - tracker3.updateBatchInputByTimer(SystemClock.uptimeMillis()); - startUpdateBatchInputTimer(tracker3); - break; - case MSG_DISMISS_KEY_PREVIEW: - drawingProxy.onKeyReleased((Key) msg.obj, false /* withAnimation */); - break; - case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT: - drawingProxy.dismissGestureFloatingPreviewTextWithoutDelay(); - break; - } - } - - @Override - public void startKeyRepeatTimerOf(@Nonnull final PointerTracker tracker, final int repeatCount, - final int delay) { - final Key key = tracker.getKey(); - if (key == null || delay == 0) { - return; - } - sendMessageDelayed( - obtainMessage(MSG_REPEAT_KEY, key.getCode(), repeatCount, tracker), delay); - } - - private void cancelKeyRepeatTimerOf(final PointerTracker tracker) { - removeMessages(MSG_REPEAT_KEY, tracker); - } - - public void cancelKeyRepeatTimers() { - removeMessages(MSG_REPEAT_KEY); - } - - // TODO: Suppress layout changes in key repeat mode - public boolean isInKeyRepeat() { - return hasMessages(MSG_REPEAT_KEY); - } - - @Override - public void startLongPressTimerOf(@Nonnull final PointerTracker tracker, final int delay) { - final Key key = tracker.getKey(); - if (key == null) { - return; - } - // Use a separate message id for long pressing shift key, because long press shift key - // timers should be canceled when other key is pressed. - final int messageId = (key.getCode() == Constants.CODE_SHIFT) - ? MSG_LONGPRESS_SHIFT_KEY : MSG_LONGPRESS_KEY; - sendMessageDelayed(obtainMessage(messageId, tracker), delay); - } - - @Override - public void cancelLongPressTimersOf(@Nonnull final PointerTracker tracker) { - removeMessages(MSG_LONGPRESS_KEY, tracker); - removeMessages(MSG_LONGPRESS_SHIFT_KEY, tracker); - } - - @Override - public void cancelLongPressShiftKeyTimer() { - removeMessages(MSG_LONGPRESS_SHIFT_KEY); - } - - public void cancelLongPressTimers() { - removeMessages(MSG_LONGPRESS_KEY); - removeMessages(MSG_LONGPRESS_SHIFT_KEY); - } - - @Override - public void startTypingStateTimer(@Nonnull final Key typedKey) { - if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) { - return; - } - - final boolean isTyping = isTypingState(); - removeMessages(MSG_TYPING_STATE_EXPIRED); - final DrawingProxy drawingProxy = getOwnerInstance(); - if (drawingProxy == null) { - return; - } - - // When user hits the space or the enter key, just cancel the while-typing timer. - final int typedCode = typedKey.getCode(); - if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) { - if (isTyping) { - drawingProxy.startWhileTypingAnimation(DrawingProxy.FADE_IN); - } - return; - } - - sendMessageDelayed( - obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout); - if (isTyping) { - return; - } - drawingProxy.startWhileTypingAnimation(DrawingProxy.FADE_OUT); - } - - @Override - public boolean isTypingState() { - return hasMessages(MSG_TYPING_STATE_EXPIRED); - } - - @Override - public void startDoubleTapShiftKeyTimer() { - sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP_SHIFT_KEY), - ViewConfiguration.getDoubleTapTimeout()); - } - - @Override - public void cancelDoubleTapShiftKeyTimer() { - removeMessages(MSG_DOUBLE_TAP_SHIFT_KEY); - } - - @Override - public boolean isInDoubleTapShiftKeyTimeout() { - return hasMessages(MSG_DOUBLE_TAP_SHIFT_KEY); - } - - @Override - public void cancelKeyTimersOf(@Nonnull final PointerTracker tracker) { - cancelKeyRepeatTimerOf(tracker); - cancelLongPressTimersOf(tracker); - } - - public void cancelAllKeyTimers() { - cancelKeyRepeatTimers(); - cancelLongPressTimers(); - } - - @Override - public void startUpdateBatchInputTimer(@Nonnull final PointerTracker tracker) { - if (mGestureRecognitionUpdateTime <= 0) { - return; - } - removeMessages(MSG_UPDATE_BATCH_INPUT, tracker); - sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker), - mGestureRecognitionUpdateTime); - } - - @Override - public void cancelUpdateBatchInputTimer(@Nonnull final PointerTracker tracker) { - removeMessages(MSG_UPDATE_BATCH_INPUT, tracker); - } - - @Override - public void cancelAllUpdateBatchInputTimers() { - removeMessages(MSG_UPDATE_BATCH_INPUT); - } - - public void postDismissKeyPreview(@Nonnull final Key key, final long delay) { - sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, key), delay); - } - - public void postDismissGestureFloatingPreviewText(final long delay) { - sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay); - } - - public void cancelAllMessages() { - cancelAllKeyTimers(); - cancelAllUpdateBatchInputTimers(); - removeMessages(MSG_DISMISS_KEY_PREVIEW); - removeMessages(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/TimerProxy.java b/java/src/com/android/inputmethod/keyboard/internal/TimerProxy.java deleted file mode 100644 index 0ce3de8d9..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/TimerProxy.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.keyboard.internal; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.PointerTracker; - -import javax.annotation.Nonnull; - -public interface TimerProxy { - /** - * Start a timer to detect if a user is typing keys. - * @param typedKey the key that is typed. - */ - public void startTypingStateTimer(@Nonnull Key typedKey); - - /** - * Check if a user is key typing. - * @return true if a user is in typing. - */ - public boolean isTypingState(); - - /** - * Start a timer to simulate repeated key presses while a user keep pressing a key. - * @param tracker the {@link PointerTracker} that points the key to be repeated. - * @param repeatCount the number of times that the key is repeating. Starting from 1. - * @param delay the interval delay to the next key repeat, in millisecond. - */ - public void startKeyRepeatTimerOf(@Nonnull PointerTracker tracker, int repeatCount, int delay); - - /** - * Start a timer to detect a long pressed key. - * If a key pointed by <code>tracker</code> is a shift key, start another timer to detect - * long pressed shift key. - * @param tracker the {@link PointerTracker} that starts long pressing. - * @param delay the delay to fire the long press timer, in millisecond. - */ - public void startLongPressTimerOf(@Nonnull PointerTracker tracker, int delay); - - /** - * Cancel timers for detecting a long pressed key and a long press shift key. - * @param tracker cancel long press timers of this {@link PointerTracker}. - */ - public void cancelLongPressTimersOf(@Nonnull PointerTracker tracker); - - /** - * Cancel a timer for detecting a long pressed shift key. - */ - public void cancelLongPressShiftKeyTimer(); - - /** - * Cancel timers for detecting repeated key press, long pressed key, and long pressed shift key. - * @param tracker the {@link PointerTracker} that starts timers to be canceled. - */ - public void cancelKeyTimersOf(@Nonnull PointerTracker tracker); - - /** - * Start a timer to detect double tapped shift key. - */ - public void startDoubleTapShiftKeyTimer(); - - /** - * Cancel a timer of detecting double tapped shift key. - */ - public void cancelDoubleTapShiftKeyTimer(); - - /** - * Check if a timer of detecting double tapped shift key is running. - * @return true if detecting double tapped shift key is on going. - */ - public boolean isInDoubleTapShiftKeyTimeout(); - - /** - * Start a timer to fire updating batch input while <code>tracker</code> is on hold. - * @param tracker the {@link PointerTracker} that stops moving. - */ - public void startUpdateBatchInputTimer(@Nonnull PointerTracker tracker); - - /** - * Cancel a timer of firing updating batch input. - * @param tracker the {@link PointerTracker} that resumes moving or ends gesture input. - */ - public void cancelUpdateBatchInputTimer(@Nonnull PointerTracker tracker); - - /** - * Cancel all timers of firing updating batch input. - */ - public void cancelAllUpdateBatchInputTimers(); - - public static class Adapter implements TimerProxy { - @Override - public void startTypingStateTimer(@Nonnull Key typedKey) {} - @Override - public boolean isTypingState() { return false; } - @Override - public void startKeyRepeatTimerOf(@Nonnull PointerTracker tracker, int repeatCount, - int delay) {} - @Override - public void startLongPressTimerOf(@Nonnull PointerTracker tracker, int delay) {} - @Override - public void cancelLongPressTimersOf(@Nonnull PointerTracker tracker) {} - @Override - public void cancelLongPressShiftKeyTimer() {} - @Override - public void cancelKeyTimersOf(@Nonnull PointerTracker tracker) {} - @Override - public void startDoubleTapShiftKeyTimer() {} - @Override - public void cancelDoubleTapShiftKeyTimer() {} - @Override - public boolean isInDoubleTapShiftKeyTimeout() { return false; } - @Override - public void startUpdateBatchInputTimer(@Nonnull PointerTracker tracker) {} - @Override - public void cancelUpdateBatchInputTimer(@Nonnull PointerTracker tracker) {} - @Override - public void cancelAllUpdateBatchInputTimers() {} - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java deleted file mode 100644 index d8f0114e1..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.keyboard.internal; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.define.DebugFlags; - -public final class TouchPositionCorrection { - private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3; - - private boolean mEnabled; - private float[] mXs; - private float[] mYs; - private float[] mRadii; - - public void load(final String[] data) { - final int dataLength = data.length; - if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) { - if (DebugFlags.DEBUG_ENABLED) { - throw new RuntimeException( - "the size of touch position correction data is invalid"); - } - return; - } - - final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE; - mXs = new float[length]; - mYs = new float[length]; - mRadii = new float[length]; - try { - for (int i = 0; i < dataLength; ++i) { - final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE; - final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE; - final float value = Float.parseFloat(data[i]); - if (type == 0) { - mXs[index] = value; - } else if (type == 1) { - mYs[index] = value; - } else { - mRadii[index] = value; - } - } - mEnabled = dataLength > 0; - } catch (NumberFormatException e) { - if (DebugFlags.DEBUG_ENABLED) { - throw new RuntimeException( - "the number format for touch position correction data is invalid"); - } - mEnabled = false; - mXs = null; - mYs = null; - mRadii = null; - } - } - - @UsedForTesting - public void setEnabled(final boolean enabled) { - mEnabled = enabled; - } - - public boolean isValid() { - return mEnabled; - } - - public int getRows() { - return mRadii.length; - } - - @SuppressWarnings({ "static-method", "unused" }) - public float getX(final int row) { - return 0.0f; - // Touch position correction data for X coordinate is obsolete. - // return mXs[row]; - } - - public float getY(final int row) { - return mYs[row]; - } - - public float getRadius(final int row) { - return mRadii[row]; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/TypingTimeRecorder.java b/java/src/com/android/inputmethod/keyboard/internal/TypingTimeRecorder.java deleted file mode 100644 index 9593f715c..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/TypingTimeRecorder.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal; - -public final class TypingTimeRecorder { - private final int mStaticTimeThresholdAfterFastTyping; // msec - private final int mSuppressKeyPreviewAfterBatchInputDuration; - private long mLastTypingTime; - private long mLastLetterTypingTime; - private long mLastBatchInputTime; - - public TypingTimeRecorder(final int staticTimeThresholdAfterFastTyping, - final int suppressKeyPreviewAfterBatchInputDuration) { - mStaticTimeThresholdAfterFastTyping = staticTimeThresholdAfterFastTyping; - mSuppressKeyPreviewAfterBatchInputDuration = suppressKeyPreviewAfterBatchInputDuration; - } - - public boolean isInFastTyping(final long eventTime) { - final long elapsedTimeSinceLastLetterTyping = eventTime - mLastLetterTypingTime; - return elapsedTimeSinceLastLetterTyping < mStaticTimeThresholdAfterFastTyping; - } - - private boolean wasLastInputTyping() { - return mLastTypingTime >= mLastBatchInputTime; - } - - public void onCodeInput(final int code, final long eventTime) { - // Record the letter typing time when - // 1. Letter keys are typed successively without any batch input in between. - // 2. A letter key is typed within the threshold time since the last any key typing. - // 3. A non-letter key is typed within the threshold time since the last letter key typing. - if (Character.isLetter(code)) { - if (wasLastInputTyping() - || eventTime - mLastTypingTime < mStaticTimeThresholdAfterFastTyping) { - mLastLetterTypingTime = eventTime; - } - } else { - if (eventTime - mLastLetterTypingTime < mStaticTimeThresholdAfterFastTyping) { - // This non-letter typing should be treated as a part of fast typing. - mLastLetterTypingTime = eventTime; - } - } - mLastTypingTime = eventTime; - } - - public void onEndBatchInput(final long eventTime) { - mLastBatchInputTime = eventTime; - } - - public long getLastLetterTypingTime() { - return mLastLetterTypingTime; - } - - public boolean needsToSuppressKeyPreviewPopup(final long eventTime) { - return !wasLastInputTyping() - && eventTime - mLastBatchInputTime < mSuppressKeyPreviewAfterBatchInputDuration; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/UniqueKeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/UniqueKeysCache.java deleted file mode 100644 index 5b329dce4..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/UniqueKeysCache.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.keyboard.internal; - -import com.android.inputmethod.keyboard.Key; - -import java.util.HashMap; - -import javax.annotation.Nonnull; - -public abstract class UniqueKeysCache { - public abstract void setEnabled(boolean enabled); - public abstract void clear(); - public abstract @Nonnull Key getUniqueKey(@Nonnull Key key); - - @Nonnull - public static final UniqueKeysCache NO_CACHE = new UniqueKeysCache() { - @Override - public void setEnabled(boolean enabled) {} - - @Override - public void clear() {} - - @Override - public Key getUniqueKey(Key key) { return key; } - }; - - @Nonnull - public static UniqueKeysCache newInstance() { - return new UniqueKeysCacheImpl(); - } - - private static final class UniqueKeysCacheImpl extends UniqueKeysCache { - private final HashMap<Key, Key> mCache; - - private boolean mEnabled; - - UniqueKeysCacheImpl() { - mCache = new HashMap<>(); - } - - @Override - public void setEnabled(final boolean enabled) { - mEnabled = enabled; - } - - @Override - public void clear() { - mCache.clear(); - } - - @Override - public Key getUniqueKey(final Key key) { - if (!mEnabled) { - return key; - } - final Key existingKey = mCache.get(key); - if (existingKey != null) { - // Reuse the existing object that equals to "key" without adding "key" to - // the cache. - return existingKey; - } - mCache.put(key, key); - return key; - } - } -} diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java deleted file mode 100644 index f8d02d6ea..000000000 --- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin; - -import com.android.inputmethod.latin.common.FileUtils; - -import java.io.File; - -/** - * Immutable class to hold the address of an asset. - * As opposed to a normal file, an asset is usually represented as a contiguous byte array in - * the package file. Open it correctly thus requires the name of the package it is in, but - * also the offset in the file and the length of this data. This class encapsulates these three. - */ -public final class AssetFileAddress { - public final String mFilename; - public final long mOffset; - public final long mLength; - - public AssetFileAddress(final String filename, final long offset, final long length) { - mFilename = filename; - mOffset = offset; - mLength = length; - } - - public static AssetFileAddress makeFromFile(final File file) { - if (!file.isFile()) return null; - return new AssetFileAddress(file.getAbsolutePath(), 0L, file.length()); - } - - public static AssetFileAddress makeFromFileName(final String filename) { - if (null == filename) return null; - return makeFromFile(new File(filename)); - } - - public static AssetFileAddress makeFromFileNameAndOffset(final String filename, - final long offset, final long length) { - if (null == filename) return null; - final File f = new File(filename); - if (!f.isFile()) return null; - return new AssetFileAddress(filename, offset, length); - } - - public boolean pointsToPhysicalFile() { - return 0 == mOffset; - } - - public void deleteUnderlyingFile() { - FileUtils.deleteRecursively(new File(mFilename)); - } - - @Override - public String toString() { - return String.format("%s (offset=%d, length=%d)", mFilename, mOffset, mLength); - } -} diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java deleted file mode 100644 index 5e6e4ab25..000000000 --- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin; - -import android.content.Context; -import android.media.AudioManager; -import android.os.Vibrator; -import android.view.HapticFeedbackConstants; -import android.view.View; - -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.settings.SettingsValues; - -/** - * This class gathers audio feedback and haptic feedback functions. - * - * It offers a consistent and simple interface that allows LatinIME to forget about the - * complexity of settings and the like. - */ -public final class AudioAndHapticFeedbackManager { - private AudioManager mAudioManager; - private Vibrator mVibrator; - - private SettingsValues mSettingsValues; - private boolean mSoundOn; - - private static final AudioAndHapticFeedbackManager sInstance = - new AudioAndHapticFeedbackManager(); - - public static AudioAndHapticFeedbackManager getInstance() { - return sInstance; - } - - private AudioAndHapticFeedbackManager() { - // Intentional empty constructor for singleton. - } - - public static void init(final Context context) { - sInstance.initInternal(context); - } - - private void initInternal(final Context context) { - mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - } - - public void performHapticAndAudioFeedback(final int code, - final View viewToPerformHapticFeedbackOn) { - performHapticFeedback(viewToPerformHapticFeedbackOn); - performAudioFeedback(code); - } - - public boolean hasVibrator() { - return mVibrator != null && mVibrator.hasVibrator(); - } - - public void vibrate(final long milliseconds) { - if (mVibrator == null) { - return; - } - mVibrator.vibrate(milliseconds); - } - - private boolean reevaluateIfSoundIsOn() { - if (mSettingsValues == null || !mSettingsValues.mSoundOn || mAudioManager == null) { - return false; - } - return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL; - } - - public void performAudioFeedback(final int code) { - // if mAudioManager is null, we can't play a sound anyway, so return - if (mAudioManager == null) { - return; - } - if (!mSoundOn) { - return; - } - final int sound; - switch (code) { - case Constants.CODE_DELETE: - sound = AudioManager.FX_KEYPRESS_DELETE; - break; - case Constants.CODE_ENTER: - sound = AudioManager.FX_KEYPRESS_RETURN; - break; - case Constants.CODE_SPACE: - sound = AudioManager.FX_KEYPRESS_SPACEBAR; - break; - default: - sound = AudioManager.FX_KEYPRESS_STANDARD; - break; - } - mAudioManager.playSoundEffect(sound, mSettingsValues.mKeypressSoundVolume); - } - - public void performHapticFeedback(final View viewToPerformHapticFeedbackOn) { - if (!mSettingsValues.mVibrateOn) { - return; - } - if (mSettingsValues.mKeypressVibrationDuration >= 0) { - vibrate(mSettingsValues.mKeypressVibrationDuration); - return; - } - // Go ahead with the system default - if (viewToPerformHapticFeedbackOn != null) { - viewToPerformHapticFeedbackOn.performHapticFeedback( - HapticFeedbackConstants.KEYBOARD_TAP); - } - } - - public void onSettingsChanged(final SettingsValues settingsValues) { - mSettingsValues = settingsValues; - mSoundOn = reevaluateIfSoundIsOn(); - } - - public void onRingerModeChanged() { - mSoundOn = reevaluateIfSoundIsOn(); - } -} diff --git a/java/src/com/android/inputmethod/latin/BackupAgent.java b/java/src/com/android/inputmethod/latin/BackupAgent.java deleted file mode 100644 index b2d92b30c..000000000 --- a/java/src/com/android/inputmethod/latin/BackupAgent.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.inputmethod.latin; - -import android.app.backup.BackupAgentHelper; -import android.app.backup.BackupDataInput; -import android.app.backup.SharedPreferencesBackupHelper; -import android.content.SharedPreferences; -import android.os.ParcelFileDescriptor; - -import com.android.inputmethod.latin.settings.LocalSettingsConstants; - -import java.io.IOException; - -/** - * Backup/restore agent for LatinIME. - * Currently it backs up the default shared preferences. - */ -public final class BackupAgent extends BackupAgentHelper { - private static final String PREF_SUFFIX = "_preferences"; - - @Override - public void onCreate() { - addHelper("shared_pref", new SharedPreferencesBackupHelper(this, - getPackageName() + PREF_SUFFIX)); - } - - @Override - public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) - throws IOException { - // Let the restore operation go through - super.onRestore(data, appVersionCode, newState); - - // Remove the preferences that we don't want restored. - final SharedPreferences.Editor prefEditor = getSharedPreferences( - getPackageName() + PREF_SUFFIX, MODE_PRIVATE).edit(); - for (final String key : LocalSettingsConstants.PREFS_TO_SKIP_RESTORING) { - prefEditor.remove(key); - } - // Flush the changes to disk. - prefEditor.commit(); - } -} diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java deleted file mode 100644 index 9a3ac674e..000000000 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ /dev/null @@ -1,669 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.inputmethod.latin; - -import android.text.TextUtils; -import android.util.Log; -import android.util.SparseArray; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.ComposedData; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.FileUtils; -import com.android.inputmethod.latin.common.InputPointers; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.makedict.DictionaryHeader; -import com.android.inputmethod.latin.makedict.FormatSpec; -import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions; -import com.android.inputmethod.latin.makedict.UnsupportedFormatException; -import com.android.inputmethod.latin.makedict.WordProperty; -import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; -import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.JniUtils; -import com.android.inputmethod.latin.utils.WordInputEventForPersonalization; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -import javax.annotation.Nonnull; - -/** - * Implements a static, compacted, binary dictionary of standard words. - */ -// TODO: All methods which should be locked need to have a suffix "Locked". -public final class BinaryDictionary extends Dictionary { - private static final String TAG = BinaryDictionary.class.getSimpleName(); - - // The cutoff returned by native for auto-commit confidence. - // Must be equal to CONFIDENCE_TO_AUTO_COMMIT in native/jni/src/defines.h - private static final int CONFIDENCE_TO_AUTO_COMMIT = 1000000; - - public static final int DICTIONARY_MAX_WORD_LENGTH = 48; - public static final int MAX_PREV_WORD_COUNT_FOR_N_GRAM = 3; - - @UsedForTesting - public static final String UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT"; - @UsedForTesting - public static final String BIGRAM_COUNT_QUERY = "BIGRAM_COUNT"; - @UsedForTesting - public static final String MAX_UNIGRAM_COUNT_QUERY = "MAX_UNIGRAM_COUNT"; - @UsedForTesting - public static final String MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT"; - - public static final int NOT_A_VALID_TIMESTAMP = -1; - - // Format to get unigram flags from native side via getWordPropertyNative(). - private static final int FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT = 5; - private static final int FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX = 0; - private static final int FORMAT_WORD_PROPERTY_IS_POSSIBLY_OFFENSIVE_INDEX = 1; - private static final int FORMAT_WORD_PROPERTY_HAS_NGRAMS_INDEX = 2; - private static final int FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX = 3; // DEPRECATED - private static final int FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX = 4; - - // Format to get probability and historical info from native side via getWordPropertyNative(). - public static final int FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT = 4; - public static final int FORMAT_WORD_PROPERTY_PROBABILITY_INDEX = 0; - public static final int FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX = 1; - public static final int FORMAT_WORD_PROPERTY_LEVEL_INDEX = 2; - public static final int FORMAT_WORD_PROPERTY_COUNT_INDEX = 3; - - public static final String DICT_FILE_NAME_SUFFIX_FOR_MIGRATION = ".migrate"; - public static final String DIR_NAME_SUFFIX_FOR_RECORD_MIGRATION = ".migrating"; - - private long mNativeDict; - private final long mDictSize; - private final String mDictFilePath; - private final boolean mUseFullEditDistance; - private final boolean mIsUpdatable; - private boolean mHasUpdated; - - private final SparseArray<DicTraverseSession> mDicTraverseSessions = new SparseArray<>(); - - // TODO: There should be a way to remove used DicTraverseSession objects from - // {@code mDicTraverseSessions}. - private DicTraverseSession getTraverseSession(final int traverseSessionId) { - synchronized(mDicTraverseSessions) { - DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId); - if (traverseSession == null) { - traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize); - mDicTraverseSessions.put(traverseSessionId, traverseSession); - } - return traverseSession; - } - } - - /** - * Constructs binary dictionary using existing dictionary file. - * @param filename the name of the file to read through native code. - * @param offset the offset of the dictionary data within the file. - * @param length the length of the binary data. - * @param useFullEditDistance whether to use the full edit distance in suggestions - * @param dictType the dictionary type, as a human-readable string - * @param isUpdatable whether to open the dictionary file in writable mode. - */ - public BinaryDictionary(final String filename, final long offset, final long length, - final boolean useFullEditDistance, final Locale locale, final String dictType, - final boolean isUpdatable) { - super(dictType, locale); - mDictSize = length; - mDictFilePath = filename; - mIsUpdatable = isUpdatable; - mHasUpdated = false; - mUseFullEditDistance = useFullEditDistance; - loadDictionary(filename, offset, length, isUpdatable); - } - - /** - * Constructs binary dictionary on memory. - * @param filename the name of the file used to flush. - * @param useFullEditDistance whether to use the full edit distance in suggestions - * @param dictType the dictionary type, as a human-readable string - * @param formatVersion the format version of the dictionary - * @param attributeMap the attributes of the dictionary - */ - public BinaryDictionary(final String filename, final boolean useFullEditDistance, - final Locale locale, final String dictType, final long formatVersion, - final Map<String, String> attributeMap) { - super(dictType, locale); - mDictSize = 0; - mDictFilePath = filename; - // On memory dictionary is always updatable. - mIsUpdatable = true; - mHasUpdated = false; - mUseFullEditDistance = useFullEditDistance; - final String[] keyArray = new String[attributeMap.size()]; - final String[] valueArray = new String[attributeMap.size()]; - int index = 0; - for (final String key : attributeMap.keySet()) { - keyArray[index] = key; - valueArray[index] = attributeMap.get(key); - index++; - } - mNativeDict = createOnMemoryNative(formatVersion, locale.toString(), keyArray, valueArray); - } - - - static { - JniUtils.loadNativeLibrary(); - } - - private static native long openNative(String sourceDir, long dictOffset, long dictSize, - boolean isUpdatable); - private static native long createOnMemoryNative(long formatVersion, - String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray); - private static native void getHeaderInfoNative(long dict, int[] outHeaderSize, - int[] outFormatVersion, ArrayList<int[]> outAttributeKeys, - ArrayList<int[]> outAttributeValues); - private static native boolean flushNative(long dict, String filePath); - private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC); - private static native boolean flushWithGCNative(long dict, String filePath); - private static native void closeNative(long dict); - private static native int getFormatVersionNative(long dict); - private static native int getProbabilityNative(long dict, int[] word); - private static native int getMaxProbabilityOfExactMatchesNative(long dict, int[] word); - private static native int getNgramProbabilityNative(long dict, int[][] prevWordCodePointArrays, - boolean[] isBeginningOfSentenceArray, int[] word); - private static native void getWordPropertyNative(long dict, int[] word, - boolean isBeginningOfSentence, int[] outCodePoints, boolean[] outFlags, - int[] outProbabilityInfo, ArrayList<int[][]> outNgramPrevWordsArray, - ArrayList<boolean[]> outNgramPrevWordIsBeginningOfSentenceArray, - ArrayList<int[]> outNgramTargets, ArrayList<int[]> outNgramProbabilityInfo, - ArrayList<int[]> outShortcutTargets, ArrayList<Integer> outShortcutProbabilities); - private static native int getNextWordNative(long dict, int token, int[] outCodePoints, - boolean[] outIsBeginningOfSentence); - private static native void getSuggestionsNative(long dict, long proximityInfo, - long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times, - int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions, - int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray, - int prevWordCount, int[] outputSuggestionCount, int[] outputCodePoints, - int[] outputScores, int[] outputIndices, int[] outputTypes, - int[] outputAutoCommitFirstWordConfidence, - float[] inOutWeightOfLangModelVsSpatialModel); - private static native boolean addUnigramEntryNative(long dict, int[] word, int probability, - int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence, - boolean isNotAWord, boolean isPossiblyOffensive, int timestamp); - private static native boolean removeUnigramEntryNative(long dict, int[] word); - private static native boolean addNgramEntryNative(long dict, - int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray, - int[] word, int probability, int timestamp); - private static native boolean removeNgramEntryNative(long dict, - int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray, int[] word); - private static native boolean updateEntriesForWordWithNgramContextNative(long dict, - int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray, - int[] word, boolean isValidWord, int count, int timestamp); - private static native int updateEntriesForInputEventsNative(long dict, - WordInputEventForPersonalization[] inputEvents, int startIndex); - private static native String getPropertyNative(long dict, String query); - private static native boolean isCorruptedNative(long dict); - private static native boolean migrateNative(long dict, String dictFilePath, - long newFormatVersion); - - // TODO: Move native dict into session - private void loadDictionary(final String path, final long startOffset, - final long length, final boolean isUpdatable) { - mHasUpdated = false; - mNativeDict = openNative(path, startOffset, length, isUpdatable); - } - - // TODO: Check isCorrupted() for main dictionaries. - public boolean isCorrupted() { - if (!isValidDictionary()) { - return false; - } - if (!isCorruptedNative(mNativeDict)) { - return false; - } - // TODO: Record the corruption. - Log.e(TAG, "BinaryDictionary (" + mDictFilePath + ") is corrupted."); - Log.e(TAG, "locale: " + mLocale); - Log.e(TAG, "dict size: " + mDictSize); - Log.e(TAG, "updatable: " + mIsUpdatable); - return true; - } - - public DictionaryHeader getHeader() throws UnsupportedFormatException { - if (mNativeDict == 0) { - return null; - } - final int[] outHeaderSize = new int[1]; - final int[] outFormatVersion = new int[1]; - final ArrayList<int[]> outAttributeKeys = new ArrayList<>(); - final ArrayList<int[]> outAttributeValues = new ArrayList<>(); - getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys, - outAttributeValues); - final HashMap<String, String> attributes = new HashMap<>(); - for (int i = 0; i < outAttributeKeys.size(); i++) { - final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray( - outAttributeKeys.get(i)); - final String attributeValue = StringUtils.getStringFromNullTerminatedCodePointArray( - outAttributeValues.get(i)); - attributes.put(attributeKey, attributeValue); - } - final boolean hasHistoricalInfo = DictionaryHeader.ATTRIBUTE_VALUE_TRUE.equals( - attributes.get(DictionaryHeader.HAS_HISTORICAL_INFO_KEY)); - return new DictionaryHeader(outHeaderSize[0], new DictionaryOptions(attributes), - new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo)); - } - - @Override - public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, - final NgramContext ngramContext, final long proximityInfoHandle, - final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int sessionId, final float weightForLocale, - final float[] inOutWeightOfLangModelVsSpatialModel) { - if (!isValidDictionary()) { - return null; - } - final DicTraverseSession session = getTraverseSession(sessionId); - Arrays.fill(session.mInputCodePoints, Constants.NOT_A_CODE); - ngramContext.outputToArray(session.mPrevWordCodePointArrays, - session.mIsBeginningOfSentenceArray); - final InputPointers inputPointers = composedData.mInputPointers; - final boolean isGesture = composedData.mIsBatchMode; - final int inputSize; - if (!isGesture) { - inputSize = - composedData.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount( - session.mInputCodePoints); - if (inputSize < 0) { - return null; - } - } else { - inputSize = inputPointers.getPointerSize(); - } - session.mNativeSuggestOptions.setUseFullEditDistance(mUseFullEditDistance); - session.mNativeSuggestOptions.setIsGesture(isGesture); - session.mNativeSuggestOptions.setBlockOffensiveWords( - settingsValuesForSuggestion.mBlockPotentiallyOffensive); - session.mNativeSuggestOptions.setWeightForLocale(weightForLocale); - if (inOutWeightOfLangModelVsSpatialModel != null) { - session.mInputOutputWeightOfLangModelVsSpatialModel[0] = - inOutWeightOfLangModelVsSpatialModel[0]; - } else { - session.mInputOutputWeightOfLangModelVsSpatialModel[0] = - Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL; - } - // TOOD: Pass multiple previous words information for n-gram. - getSuggestionsNative(mNativeDict, proximityInfoHandle, - getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(), - inputPointers.getYCoordinates(), inputPointers.getTimes(), - inputPointers.getPointerIds(), session.mInputCodePoints, inputSize, - session.mNativeSuggestOptions.getOptions(), session.mPrevWordCodePointArrays, - session.mIsBeginningOfSentenceArray, ngramContext.getPrevWordCount(), - session.mOutputSuggestionCount, session.mOutputCodePoints, session.mOutputScores, - session.mSpaceIndices, session.mOutputTypes, - session.mOutputAutoCommitFirstWordConfidence, - session.mInputOutputWeightOfLangModelVsSpatialModel); - if (inOutWeightOfLangModelVsSpatialModel != null) { - inOutWeightOfLangModelVsSpatialModel[0] = - session.mInputOutputWeightOfLangModelVsSpatialModel[0]; - } - final int count = session.mOutputSuggestionCount[0]; - final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>(); - for (int j = 0; j < count; ++j) { - final int start = j * DICTIONARY_MAX_WORD_LENGTH; - int len = 0; - while (len < DICTIONARY_MAX_WORD_LENGTH - && session.mOutputCodePoints[start + len] != 0) { - ++len; - } - if (len > 0) { - suggestions.add(new SuggestedWordInfo( - new String(session.mOutputCodePoints, start, len), - "" /* prevWordsContext */, - (int)(session.mOutputScores[j] * weightForLocale), - session.mOutputTypes[j], - this /* sourceDict */, - session.mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */, - session.mOutputAutoCommitFirstWordConfidence[0])); - } - } - return suggestions; - } - - public boolean isValidDictionary() { - return mNativeDict != 0; - } - - public int getFormatVersion() { - return getFormatVersionNative(mNativeDict); - } - - @Override - public boolean isInDictionary(final String word) { - return getFrequency(word) != NOT_A_PROBABILITY; - } - - @Override - public int getFrequency(final String word) { - if (TextUtils.isEmpty(word)) { - return NOT_A_PROBABILITY; - } - final int[] codePoints = StringUtils.toCodePointArray(word); - return getProbabilityNative(mNativeDict, codePoints); - } - - @Override - public int getMaxFrequencyOfExactMatches(final String word) { - if (TextUtils.isEmpty(word)) { - return NOT_A_PROBABILITY; - } - final int[] codePoints = StringUtils.toCodePointArray(word); - return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints); - } - - @UsedForTesting - public boolean isValidNgram(final NgramContext ngramContext, final String word) { - return getNgramProbability(ngramContext, word) != NOT_A_PROBABILITY; - } - - public int getNgramProbability(final NgramContext ngramContext, final String word) { - if (!ngramContext.isValid() || TextUtils.isEmpty(word)) { - return NOT_A_PROBABILITY; - } - final int[][] prevWordCodePointArrays = new int[ngramContext.getPrevWordCount()][]; - final boolean[] isBeginningOfSentenceArray = new boolean[ngramContext.getPrevWordCount()]; - ngramContext.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); - final int[] wordCodePoints = StringUtils.toCodePointArray(word); - return getNgramProbabilityNative(mNativeDict, prevWordCodePointArrays, - isBeginningOfSentenceArray, wordCodePoints); - } - - public WordProperty getWordProperty(final String word, final boolean isBeginningOfSentence) { - if (word == null) { - return null; - } - final int[] codePoints = StringUtils.toCodePointArray(word); - final int[] outCodePoints = new int[DICTIONARY_MAX_WORD_LENGTH]; - final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT]; - final int[] outProbabilityInfo = - new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT]; - final ArrayList<int[][]> outNgramPrevWordsArray = new ArrayList<>(); - final ArrayList<boolean[]> outNgramPrevWordIsBeginningOfSentenceArray = - new ArrayList<>(); - final ArrayList<int[]> outNgramTargets = new ArrayList<>(); - final ArrayList<int[]> outNgramProbabilityInfo = new ArrayList<>(); - final ArrayList<int[]> outShortcutTargets = new ArrayList<>(); - final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>(); - getWordPropertyNative(mNativeDict, codePoints, isBeginningOfSentence, outCodePoints, - outFlags, outProbabilityInfo, outNgramPrevWordsArray, - outNgramPrevWordIsBeginningOfSentenceArray, outNgramTargets, - outNgramProbabilityInfo, outShortcutTargets, outShortcutProbabilities); - return new WordProperty(codePoints, - outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX], - outFlags[FORMAT_WORD_PROPERTY_IS_POSSIBLY_OFFENSIVE_INDEX], - outFlags[FORMAT_WORD_PROPERTY_HAS_NGRAMS_INDEX], - outFlags[FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX], outProbabilityInfo, - outNgramPrevWordsArray, outNgramPrevWordIsBeginningOfSentenceArray, - outNgramTargets, outNgramProbabilityInfo); - } - - public static class GetNextWordPropertyResult { - public WordProperty mWordProperty; - public int mNextToken; - - public GetNextWordPropertyResult(final WordProperty wordProperty, final int nextToken) { - mWordProperty = wordProperty; - mNextToken = nextToken; - } - } - - /** - * Method to iterate all words in the dictionary for makedict. - * If token is 0, this method newly starts iterating the dictionary. - */ - public GetNextWordPropertyResult getNextWordProperty(final int token) { - final int[] codePoints = new int[DICTIONARY_MAX_WORD_LENGTH]; - final boolean[] isBeginningOfSentence = new boolean[1]; - final int nextToken = getNextWordNative(mNativeDict, token, codePoints, - isBeginningOfSentence); - final String word = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints); - return new GetNextWordPropertyResult( - getWordProperty(word, isBeginningOfSentence[0]), nextToken); - } - - // Add a unigram entry to binary dictionary with unigram attributes in native code. - public boolean addUnigramEntry( - final String word, final int probability, final boolean isBeginningOfSentence, - final boolean isNotAWord, final boolean isPossiblyOffensive, final int timestamp) { - if (word == null || (word.isEmpty() && !isBeginningOfSentence)) { - return false; - } - final int[] codePoints = StringUtils.toCodePointArray(word); - if (!addUnigramEntryNative(mNativeDict, codePoints, probability, - null /* shortcutTargetCodePoints */, 0 /* shortcutProbability */, - isBeginningOfSentence, isNotAWord, isPossiblyOffensive, timestamp)) { - return false; - } - mHasUpdated = true; - return true; - } - - // Remove a unigram entry from the binary dictionary in native code. - public boolean removeUnigramEntry(final String word) { - if (TextUtils.isEmpty(word)) { - return false; - } - final int[] codePoints = StringUtils.toCodePointArray(word); - if (!removeUnigramEntryNative(mNativeDict, codePoints)) { - return false; - } - mHasUpdated = true; - return true; - } - - // Add an n-gram entry to the binary dictionary with timestamp in native code. - public boolean addNgramEntry(final NgramContext ngramContext, final String word, - final int probability, final int timestamp) { - if (!ngramContext.isValid() || TextUtils.isEmpty(word)) { - return false; - } - final int[][] prevWordCodePointArrays = new int[ngramContext.getPrevWordCount()][]; - final boolean[] isBeginningOfSentenceArray = new boolean[ngramContext.getPrevWordCount()]; - ngramContext.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); - final int[] wordCodePoints = StringUtils.toCodePointArray(word); - if (!addNgramEntryNative(mNativeDict, prevWordCodePointArrays, - isBeginningOfSentenceArray, wordCodePoints, probability, timestamp)) { - return false; - } - mHasUpdated = true; - return true; - } - - // Update entries for the word occurrence with the ngramContext. - public boolean updateEntriesForWordWithNgramContext(@Nonnull final NgramContext ngramContext, - final String word, final boolean isValidWord, final int count, final int timestamp) { - if (TextUtils.isEmpty(word)) { - return false; - } - final int[][] prevWordCodePointArrays = new int[ngramContext.getPrevWordCount()][]; - final boolean[] isBeginningOfSentenceArray = new boolean[ngramContext.getPrevWordCount()]; - ngramContext.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); - final int[] wordCodePoints = StringUtils.toCodePointArray(word); - if (!updateEntriesForWordWithNgramContextNative(mNativeDict, prevWordCodePointArrays, - isBeginningOfSentenceArray, wordCodePoints, isValidWord, count, timestamp)) { - return false; - } - mHasUpdated = true; - return true; - } - - @UsedForTesting - public void updateEntriesForInputEvents(final WordInputEventForPersonalization[] inputEvents) { - if (!isValidDictionary()) { - return; - } - int processedEventCount = 0; - while (processedEventCount < inputEvents.length) { - if (needsToRunGC(true /* mindsBlockByGC */)) { - flushWithGC(); - } - processedEventCount = updateEntriesForInputEventsNative(mNativeDict, inputEvents, - processedEventCount); - mHasUpdated = true; - if (processedEventCount <= 0) { - return; - } - } - } - - private void reopen() { - close(); - final File dictFile = new File(mDictFilePath); - // WARNING: Because we pass 0 as the offset and file.length() as the length, this can - // only be called for actual files. Right now it's only called by the flush() family of - // functions, which require an updatable dictionary, so it's okay. But beware. - loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */, - dictFile.length(), mIsUpdatable); - } - - // Flush to dict file if the dictionary has been updated. - public boolean flush() { - if (!isValidDictionary()) { - return false; - } - if (mHasUpdated) { - if (!flushNative(mNativeDict, mDictFilePath)) { - return false; - } - reopen(); - } - return true; - } - - // Run GC and flush to dict file if the dictionary has been updated. - public boolean flushWithGCIfHasUpdated() { - if (mHasUpdated) { - return flushWithGC(); - } - return true; - } - - // Run GC and flush to dict file. - public boolean flushWithGC() { - if (!isValidDictionary()) { - return false; - } - if (!flushWithGCNative(mNativeDict, mDictFilePath)) { - return false; - } - reopen(); - return true; - } - - /** - * Checks whether GC is needed to run or not. - * @param mindsBlockByGC Whether to mind operations blocked by GC. We don't need to care about - * the blocking in some situations such as in idle time or just before closing. - * @return whether GC is needed to run or not. - */ - public boolean needsToRunGC(final boolean mindsBlockByGC) { - if (!isValidDictionary()) { - return false; - } - return needsToRunGCNative(mNativeDict, mindsBlockByGC); - } - - public boolean migrateTo(final int newFormatVersion) { - if (!isValidDictionary()) { - return false; - } - final File isMigratingDir = - new File(mDictFilePath + DIR_NAME_SUFFIX_FOR_RECORD_MIGRATION); - if (isMigratingDir.exists()) { - isMigratingDir.delete(); - Log.e(TAG, "Previous migration attempt failed probably due to a crash. " - + "Giving up using the old dictionary (" + mDictFilePath + ")."); - return false; - } - if (!isMigratingDir.mkdir()) { - Log.e(TAG, "Cannot create a dir (" + isMigratingDir.getAbsolutePath() - + ") to record migration."); - return false; - } - try { - final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION; - if (!migrateNative(mNativeDict, tmpDictFilePath, newFormatVersion)) { - return false; - } - close(); - final File dictFile = new File(mDictFilePath); - final File tmpDictFile = new File(tmpDictFilePath); - if (!FileUtils.deleteRecursively(dictFile)) { - return false; - } - if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) { - return false; - } - loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */, - dictFile.length(), mIsUpdatable); - return true; - } finally { - isMigratingDir.delete(); - } - } - - @UsedForTesting - public String getPropertyForGettingStats(final String query) { - if (!isValidDictionary()) { - return ""; - } - return getPropertyNative(mNativeDict, query); - } - - @Override - public boolean shouldAutoCommit(final SuggestedWordInfo candidate) { - return candidate.mAutoCommitFirstWordConfidence > CONFIDENCE_TO_AUTO_COMMIT; - } - - @Override - public void close() { - synchronized (mDicTraverseSessions) { - final int sessionsSize = mDicTraverseSessions.size(); - for (int index = 0; index < sessionsSize; ++index) { - final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index); - if (traverseSession != null) { - traverseSession.close(); - } - } - mDicTraverseSessions.clear(); - } - closeInternalLocked(); - } - - private synchronized void closeInternalLocked() { - if (mNativeDict != 0) { - closeNative(mNativeDict); - mNativeDict = 0; - } - } - - // TODO: Manage BinaryDictionary instances without using WeakReference or something. - @Override - protected void finalize() throws Throwable { - try { - closeInternalLocked(); - } finally { - super.finalize(); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java deleted file mode 100644 index 648610c86..000000000 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ /dev/null @@ -1,569 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin; - -import android.content.ContentProviderClient; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.database.Cursor; -import android.net.Uri; -import android.os.RemoteException; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.dictionarypack.DictionaryPackConstants; -import com.android.inputmethod.dictionarypack.MD5Calculator; -import com.android.inputmethod.dictionarypack.UpdateHandler; -import com.android.inputmethod.latin.common.FileUtils; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; -import com.android.inputmethod.latin.utils.DictionaryInfoUtils; -import com.android.inputmethod.latin.utils.DictionaryInfoUtils.DictionaryInfo; -import com.android.inputmethod.latin.utils.FileTransforms; -import com.android.inputmethod.latin.utils.MetadataFileUriGetter; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -/** - * Group class for static methods to help with creation and getting of the binary dictionary - * file from the dictionary provider - */ -public final class BinaryDictionaryFileDumper { - private static final String TAG = BinaryDictionaryFileDumper.class.getSimpleName(); - private static final boolean DEBUG = false; - - /** - * The size of the temporary buffer to copy files. - */ - private static final int FILE_READ_BUFFER_SIZE = 8192; - // TODO: make the following data common with the native code - private static final byte[] MAGIC_NUMBER_VERSION_1 = - new byte[] { (byte)0x78, (byte)0xB1, (byte)0x00, (byte)0x00 }; - private static final byte[] MAGIC_NUMBER_VERSION_2 = - new byte[] { (byte)0x9B, (byte)0xC1, (byte)0x3A, (byte)0xFE }; - - private static final boolean SHOULD_VERIFY_MAGIC_NUMBER = - DecoderSpecificConstants.SHOULD_VERIFY_MAGIC_NUMBER; - private static final boolean SHOULD_VERIFY_CHECKSUM = - DecoderSpecificConstants.SHOULD_VERIFY_CHECKSUM; - - private static final String DICTIONARY_PROJECTION[] = { "id" }; - - private static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt"; - private static final String QUERY_PARAMETER_TRUE = "true"; - private static final String QUERY_PARAMETER_DELETE_RESULT = "result"; - private static final String QUERY_PARAMETER_SUCCESS = "success"; - private static final String QUERY_PARAMETER_FAILURE = "failure"; - - // Using protocol version 2 to communicate with the dictionary pack - private static final String QUERY_PARAMETER_PROTOCOL = "protocol"; - private static final String QUERY_PARAMETER_PROTOCOL_VALUE = "2"; - - // The path fragment to append after the client ID for dictionary info requests. - private static final String QUERY_PATH_DICT_INFO = "dict"; - // The path fragment to append after the client ID for dictionary datafile requests. - private static final String QUERY_PATH_DATAFILE = "datafile"; - // The path fragment to append after the client ID for updating the metadata URI. - private static final String QUERY_PATH_METADATA = "metadata"; - private static final String INSERT_METADATA_CLIENT_ID_COLUMN = "clientid"; - private static final String INSERT_METADATA_METADATA_URI_COLUMN = "uri"; - private static final String INSERT_METADATA_METADATA_ADDITIONAL_ID_COLUMN = "additionalid"; - - // Prevents this class to be accidentally instantiated. - private BinaryDictionaryFileDumper() { - } - - /** - * Returns a URI builder pointing to the dictionary pack. - * - * This creates a URI builder able to build a URI pointing to the dictionary - * pack content provider for a specific dictionary id. - */ - public static Uri.Builder getProviderUriBuilder(final String path) { - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) - .authority(DictionaryPackConstants.AUTHORITY).appendPath(path); - } - - /** - * Gets the content URI builder for a specified type. - * - * Supported types include QUERY_PATH_DICT_INFO, which takes the locale as - * the extraPath argument, and QUERY_PATH_DATAFILE, which needs a wordlist ID - * as the extraPath argument. - * - * @param clientId the clientId to use - * @param contentProviderClient the instance of content provider client - * @param queryPathType the path element encoding the type - * @param extraPath optional extra argument for this type (typically word list id) - * @return a builder that can build the URI for the best supported protocol version - * @throws RemoteException if the client can't be contacted - */ - private static Uri.Builder getContentUriBuilderForType(final String clientId, - final ContentProviderClient contentProviderClient, final String queryPathType, - final String extraPath) throws RemoteException { - // Check whether protocol v2 is supported by building a v2 URI and calling getType() - // on it. If this returns null, v2 is not supported. - final Uri.Builder uriV2Builder = getProviderUriBuilder(clientId); - uriV2Builder.appendPath(queryPathType); - uriV2Builder.appendPath(extraPath); - uriV2Builder.appendQueryParameter(QUERY_PARAMETER_PROTOCOL, - QUERY_PARAMETER_PROTOCOL_VALUE); - if (null != contentProviderClient.getType(uriV2Builder.build())) return uriV2Builder; - // Protocol v2 is not supported, so create and return the protocol v1 uri. - return getProviderUriBuilder(extraPath); - } - - /** - * Queries a content provider for the list of word lists for a specific locale - * available to copy into Latin IME. - */ - private static List<WordListInfo> getWordListWordListInfos(final Locale locale, - final Context context, final boolean hasDefaultWordList) { - final String clientId = context.getString(R.string.dictionary_pack_client_id); - final ContentProviderClient client = context.getContentResolver(). - acquireContentProviderClient(getProviderUriBuilder("").build()); - if (null == client) return Collections.<WordListInfo>emptyList(); - Cursor cursor = null; - try { - final Uri.Builder builder = getContentUriBuilderForType(clientId, client, - QUERY_PATH_DICT_INFO, locale.toString()); - if (!hasDefaultWordList) { - builder.appendQueryParameter(QUERY_PARAMETER_MAY_PROMPT_USER, - QUERY_PARAMETER_TRUE); - } - final Uri queryUri = builder.build(); - final boolean isProtocolV2 = (QUERY_PARAMETER_PROTOCOL_VALUE.equals( - queryUri.getQueryParameter(QUERY_PARAMETER_PROTOCOL))); - - cursor = client.query(queryUri, DICTIONARY_PROJECTION, null, null, null); - if (isProtocolV2 && null == cursor) { - reinitializeClientRecordInDictionaryContentProvider(context, client, clientId); - cursor = client.query(queryUri, DICTIONARY_PROJECTION, null, null, null); - } - if (null == cursor) return Collections.<WordListInfo>emptyList(); - if (cursor.getCount() <= 0 || !cursor.moveToFirst()) { - return Collections.<WordListInfo>emptyList(); - } - final ArrayList<WordListInfo> list = new ArrayList<>(); - do { - final String wordListId = cursor.getString(0); - final String wordListLocale = cursor.getString(1); - final String wordListRawChecksum = cursor.getString(2); - if (TextUtils.isEmpty(wordListId)) continue; - list.add(new WordListInfo(wordListId, wordListLocale, wordListRawChecksum)); - } while (cursor.moveToNext()); - return list; - } catch (RemoteException e) { - // The documentation is unclear as to in which cases this may happen, but it probably - // happens when the content provider got suddenly killed because it crashed or because - // the user disabled it through Settings. - Log.e(TAG, "RemoteException: communication with the dictionary pack cut", e); - return Collections.<WordListInfo>emptyList(); - } catch (Exception e) { - // A crash here is dangerous because crashing here would brick any encrypted device - - // we need the keyboard to be up and working to enter the password, so we don't want - // to die no matter what. So let's be as safe as possible. - Log.e(TAG, "Unexpected exception communicating with the dictionary pack", e); - return Collections.<WordListInfo>emptyList(); - } finally { - if (null != cursor) { - cursor.close(); - } - client.release(); - } - } - - - /** - * Helper method to encapsulate exception handling. - */ - private static AssetFileDescriptor openAssetFileDescriptor( - final ContentProviderClient providerClient, final Uri uri) { - try { - return providerClient.openAssetFile(uri, "r"); - } catch (FileNotFoundException e) { - // I don't want to log the word list URI here for security concerns. The exception - // contains the name of the file, so let's not pass it to Log.e here. - Log.e(TAG, "Could not find a word list from the dictionary provider." - /* intentionally don't pass the exception (see comment above) */); - return null; - } catch (RemoteException e) { - Log.e(TAG, "Can't communicate with the dictionary pack", e); - return null; - } - } - - /** - * Stages a word list the id of which is passed as an argument. This will write the file - * to the cache file name designated by its id and locale, overwriting it if already present - * and creating it (and its containing directory) if necessary. - */ - private static void installWordListToStaging(final String wordlistId, final String locale, - final String rawChecksum, final ContentProviderClient providerClient, - final Context context) { - final int COMPRESSED_CRYPTED_COMPRESSED = 0; - final int CRYPTED_COMPRESSED = 1; - final int COMPRESSED_CRYPTED = 2; - final int COMPRESSED_ONLY = 3; - final int CRYPTED_ONLY = 4; - final int NONE = 5; - final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED; - final int MODE_MAX = NONE; - - final String clientId = context.getString(R.string.dictionary_pack_client_id); - final Uri.Builder wordListUriBuilder; - try { - wordListUriBuilder = getContentUriBuilderForType(clientId, - providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */); - } catch (RemoteException e) { - Log.e(TAG, "Can't communicate with the dictionary pack", e); - return; - } - final String finalFileName = - DictionaryInfoUtils.getStagingFileName(wordlistId, locale, context); - String tempFileName; - try { - tempFileName = BinaryDictionaryGetter.getTempFileName(wordlistId, context); - } catch (IOException e) { - Log.e(TAG, "Can't open the temporary file", e); - return; - } - - for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) { - final InputStream originalSourceStream; - InputStream inputStream = null; - InputStream uncompressedStream = null; - InputStream decryptedStream = null; - BufferedInputStream bufferedInputStream = null; - File outputFile = null; - BufferedOutputStream bufferedOutputStream = null; - AssetFileDescriptor afd = null; - final Uri wordListUri = wordListUriBuilder.build(); - try { - // Open input. - afd = openAssetFileDescriptor(providerClient, wordListUri); - // If we can't open it at all, don't even try a number of times. - if (null == afd) return; - originalSourceStream = afd.createInputStream(); - // Open output. - outputFile = new File(tempFileName); - // Just to be sure, delete the file. This may fail silently, and return false: this - // is the right thing to do, as we just want to continue anyway. - outputFile.delete(); - // Get the appropriate decryption method for this try - switch (mode) { - case COMPRESSED_CRYPTED_COMPRESSED: - uncompressedStream = - FileTransforms.getUncompressedStream(originalSourceStream); - decryptedStream = FileTransforms.getDecryptedStream(uncompressedStream); - inputStream = FileTransforms.getUncompressedStream(decryptedStream); - break; - case CRYPTED_COMPRESSED: - decryptedStream = FileTransforms.getDecryptedStream(originalSourceStream); - inputStream = FileTransforms.getUncompressedStream(decryptedStream); - break; - case COMPRESSED_CRYPTED: - uncompressedStream = - FileTransforms.getUncompressedStream(originalSourceStream); - inputStream = FileTransforms.getDecryptedStream(uncompressedStream); - break; - case COMPRESSED_ONLY: - inputStream = FileTransforms.getUncompressedStream(originalSourceStream); - break; - case CRYPTED_ONLY: - inputStream = FileTransforms.getDecryptedStream(originalSourceStream); - break; - case NONE: - inputStream = originalSourceStream; - break; - } - bufferedInputStream = new BufferedInputStream(inputStream); - bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(outputFile)); - checkMagicAndCopyFileTo(bufferedInputStream, bufferedOutputStream); - bufferedOutputStream.flush(); - bufferedOutputStream.close(); - - if (SHOULD_VERIFY_CHECKSUM) { - final String actualRawChecksum = MD5Calculator.checksum( - new BufferedInputStream(new FileInputStream(outputFile))); - Log.i(TAG, "Computed checksum for downloaded dictionary. Expected = " - + rawChecksum + " ; actual = " + actualRawChecksum); - if (!TextUtils.isEmpty(rawChecksum) && !rawChecksum.equals(actualRawChecksum)) { - throw new IOException( - "Could not decode the file correctly : checksum differs"); - } - } - - // move the output file to the final staging file. - final File finalFile = new File(finalFileName); - if (!FileUtils.renameTo(outputFile, finalFile)) { - Log.e(TAG, String.format("Failed to rename from %s to %s.", - outputFile.getAbsoluteFile(), finalFile.getAbsoluteFile())); - } - - wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT, - QUERY_PARAMETER_SUCCESS); - if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) { - Log.e(TAG, "Could not have the dictionary pack delete a word list"); - } - Log.d(TAG, "Successfully copied file for wordlist ID " + wordlistId); - // Success! Close files (through the finally{} clause) and return. - return; - } catch (Exception e) { - if (DEBUG) { - Log.e(TAG, "Can't open word list in mode " + mode, e); - } - if (null != outputFile) { - // This may or may not fail. The file may not have been created if the - // exception was thrown before it could be. Hence, both failure and - // success are expected outcomes, so we don't check the return value. - outputFile.delete(); - } - // Try the next method. - } finally { - // Ignore exceptions while closing files. - closeAssetFileDescriptorAndReportAnyException(afd); - closeCloseableAndReportAnyException(inputStream); - closeCloseableAndReportAnyException(uncompressedStream); - closeCloseableAndReportAnyException(decryptedStream); - closeCloseableAndReportAnyException(bufferedInputStream); - closeCloseableAndReportAnyException(bufferedOutputStream); - } - } - - // We could not copy the file at all. This is very unexpected. - // I'd rather not print the word list ID to the log out of security concerns - Log.e(TAG, "Could not copy a word list. Will not be able to use it."); - // If we can't copy it we should warn the dictionary provider so that it can mark it - // as invalid. - reportBrokenFileToDictionaryProvider(providerClient, clientId, wordlistId); - } - - public static boolean reportBrokenFileToDictionaryProvider( - final ContentProviderClient providerClient, final String clientId, - final String wordlistId) { - try { - final Uri.Builder wordListUriBuilder = getContentUriBuilderForType(clientId, - providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */); - wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT, - QUERY_PARAMETER_FAILURE); - if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) { - Log.e(TAG, "Unable to delete a word list."); - } - } catch (RemoteException e) { - Log.e(TAG, "Communication with the dictionary provider was cut", e); - return false; - } - return true; - } - - // Ideally the two following methods should be merged, but AssetFileDescriptor does not - // implement Closeable although it does implement #close(), and Java does not have - // structural typing. - private static void closeAssetFileDescriptorAndReportAnyException( - final AssetFileDescriptor file) { - try { - if (null != file) file.close(); - } catch (Exception e) { - Log.e(TAG, "Exception while closing a file", e); - } - } - - private static void closeCloseableAndReportAnyException(final Closeable file) { - try { - if (null != file) file.close(); - } catch (Exception e) { - Log.e(TAG, "Exception while closing a file", e); - } - } - - /** - * Queries a content provider for word list data for some locale and stage the returned files - * - * This will query a content provider for word list data for a given locale, and copy the - * files locally so that they can be mmap'ed. This may overwrite previously cached word lists - * with newer versions if a newer version is made available by the content provider. - * @throw FileNotFoundException if the provider returns non-existent data. - * @throw IOException if the provider-returned data could not be read. - */ - public static void installDictToStagingFromContentProvider(final Locale locale, - final Context context, final boolean hasDefaultWordList) { - final ContentProviderClient providerClient; - try { - providerClient = context.getContentResolver(). - acquireContentProviderClient(getProviderUriBuilder("").build()); - } catch (final SecurityException e) { - Log.e(TAG, "No permission to communicate with the dictionary provider", e); - return; - } - if (null == providerClient) { - Log.e(TAG, "Can't establish communication with the dictionary provider"); - return; - } - try { - final List<WordListInfo> idList = getWordListWordListInfos(locale, context, - hasDefaultWordList); - for (WordListInfo id : idList) { - installWordListToStaging(id.mId, id.mLocale, id.mRawChecksum, providerClient, - context); - } - } finally { - providerClient.release(); - } - } - - /** - * Downloads the dictionary if it was never requested/used. - * - * @param locale locale to download - * @param context the context for resources and providers. - * @param hasDefaultWordList whether the default wordlist exists in the resources. - */ - public static void downloadDictIfNeverRequested(final Locale locale, - final Context context, final boolean hasDefaultWordList) { - getWordListWordListInfos(locale, context, hasDefaultWordList); - } - - /** - * Copies the data in an input stream to a target file if the magic number matches. - * - * If the magic number does not match the expected value, this method throws an - * IOException. Other usual conditions for IOException or FileNotFoundException - * also apply. - * - * @param input the stream to be copied. - * @param output an output stream to copy the data to. - */ - public static void checkMagicAndCopyFileTo(final BufferedInputStream input, - final BufferedOutputStream output) throws FileNotFoundException, IOException { - // Check the magic number - final int length = MAGIC_NUMBER_VERSION_2.length; - final byte[] magicNumberBuffer = new byte[length]; - final int readMagicNumberSize = input.read(magicNumberBuffer, 0, length); - if (readMagicNumberSize < length) { - throw new IOException("Less bytes to read than the magic number length"); - } - if (SHOULD_VERIFY_MAGIC_NUMBER) { - if (!Arrays.equals(MAGIC_NUMBER_VERSION_2, magicNumberBuffer)) { - if (!Arrays.equals(MAGIC_NUMBER_VERSION_1, magicNumberBuffer)) { - throw new IOException("Wrong magic number for downloaded file"); - } - } - } - output.write(magicNumberBuffer); - - // Actually copy the file - final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE]; - for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) { - output.write(buffer, 0, readBytes); - } - input.close(); - } - - private static void reinitializeClientRecordInDictionaryContentProvider(final Context context, - final ContentProviderClient client, final String clientId) throws RemoteException { - final String metadataFileUri = MetadataFileUriGetter.getMetadataUri(context); - Log.i(TAG, "reinitializeClientRecordInDictionaryContentProvider() : MetadataFileUri = " - + metadataFileUri); - final String metadataAdditionalId = MetadataFileUriGetter.getMetadataAdditionalId(context); - // Tell the content provider to reset all information about this client id - final Uri metadataContentUri = getProviderUriBuilder(clientId) - .appendPath(QUERY_PATH_METADATA) - .appendQueryParameter(QUERY_PARAMETER_PROTOCOL, QUERY_PARAMETER_PROTOCOL_VALUE) - .build(); - client.delete(metadataContentUri, null, null); - // Update the metadata URI - final ContentValues metadataValues = new ContentValues(); - metadataValues.put(INSERT_METADATA_CLIENT_ID_COLUMN, clientId); - metadataValues.put(INSERT_METADATA_METADATA_URI_COLUMN, metadataFileUri); - metadataValues.put(INSERT_METADATA_METADATA_ADDITIONAL_ID_COLUMN, metadataAdditionalId); - client.insert(metadataContentUri, metadataValues); - - // Update the dictionary list. - final Uri dictionaryContentUriBase = getProviderUriBuilder(clientId) - .appendPath(QUERY_PATH_DICT_INFO) - .appendQueryParameter(QUERY_PARAMETER_PROTOCOL, QUERY_PARAMETER_PROTOCOL_VALUE) - .build(); - final ArrayList<DictionaryInfo> dictionaryList = - DictionaryInfoUtils.getCurrentDictionaryFileNameAndVersionInfo(context); - final int length = dictionaryList.size(); - for (int i = 0; i < length; ++i) { - final DictionaryInfo info = dictionaryList.get(i); - Log.i(TAG, "reinitializeClientRecordInDictionaryContentProvider() : Insert " + info); - client.insert(Uri.withAppendedPath(dictionaryContentUriBase, info.mId), - info.toContentValues()); - } - - // Read from metadata file in resources to get the baseline dictionary info. - // This ensures we start with a valid list of available dictionaries. - final int metadataResourceId = context.getResources().getIdentifier("metadata", - "raw", DictionaryInfoUtils.RESOURCE_PACKAGE_NAME); - if (metadataResourceId == 0) { - Log.w(TAG, "Missing metadata.json resource"); - return; - } - InputStream inputStream = null; - try { - inputStream = context.getResources().openRawResource(metadataResourceId); - UpdateHandler.handleMetadata(context, inputStream, clientId); - } catch (Exception e) { - Log.w(TAG, "Failed to read metadata.json from resources", e); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - Log.w(TAG, "Failed to close metadata.json", e); - } - } - } - } - - /** - * Initialize a client record with the dictionary content provider. - * - * This merely acquires the content provider and calls - * #reinitializeClientRecordInDictionaryContentProvider. - * - * @param context the context for resources and providers. - * @param clientId the client ID to use. - */ - public static void initializeClientRecordHelper(final Context context, final String clientId) { - try { - final ContentProviderClient client = context.getContentResolver(). - acquireContentProviderClient(getProviderUriBuilder("").build()); - if (null == client) return; - reinitializeClientRecordInDictionaryContentProvider(context, client, clientId); - } catch (RemoteException e) { - Log.e(TAG, "Cannot contact the dictionary content provider", e); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java deleted file mode 100644 index c13f0e20a..000000000 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.AssetFileDescriptor; -import android.util.Log; - -import com.android.inputmethod.latin.common.LocaleUtils; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; -import com.android.inputmethod.latin.makedict.DictionaryHeader; -import com.android.inputmethod.latin.makedict.UnsupportedFormatException; -import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.DictionaryInfoUtils; - -import java.io.File; -import java.io.IOException; -import java.nio.BufferUnderflowException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Locale; - -/** - * Helper class to get the address of a mmap'able dictionary file. - */ -final public class BinaryDictionaryGetter { - - /** - * Used for Log actions from this class - */ - private static final String TAG = BinaryDictionaryGetter.class.getSimpleName(); - - /** - * Used to return empty lists - */ - private static final File[] EMPTY_FILE_ARRAY = new File[0]; - - /** - * Name of the common preferences name to know which word list are on and which are off. - */ - private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs"; - - private static final boolean SHOULD_USE_DICT_VERSION = - DecoderSpecificConstants.SHOULD_USE_DICT_VERSION; - - // Name of the category for the main dictionary - public static final String MAIN_DICTIONARY_CATEGORY = "main"; - public static final String ID_CATEGORY_SEPARATOR = ":"; - - // The key considered to read the version attribute in a dictionary file. - private static String VERSION_KEY = "version"; - - // Prevents this from being instantiated - private BinaryDictionaryGetter() {} - - /** - * Generates a unique temporary file name in the app cache directory. - */ - public static String getTempFileName(final String id, final Context context) - throws IOException { - final String safeId = DictionaryInfoUtils.replaceFileNameDangerousCharacters(id); - final File directory = new File(DictionaryInfoUtils.getWordListTempDirectory(context)); - if (!directory.exists()) { - if (!directory.mkdirs()) { - Log.e(TAG, "Could not create the temporary directory"); - } - } - // If the first argument is less than three chars, createTempFile throws a - // RuntimeException. We don't really care about what name we get, so just - // put a three-chars prefix makes us safe. - return File.createTempFile("xxx" + safeId, null, directory).getAbsolutePath(); - } - - /** - * Returns a file address from a resource, or null if it cannot be opened. - */ - public static AssetFileAddress loadFallbackResource(final Context context, - final int fallbackResId) { - AssetFileDescriptor afd = null; - try { - afd = context.getResources().openRawResourceFd(fallbackResId); - } catch (RuntimeException e) { - Log.e(TAG, "Resource not found: " + fallbackResId); - return null; - } - if (afd == null) { - Log.e(TAG, "Resource cannot be opened: " + fallbackResId); - return null; - } - try { - return AssetFileAddress.makeFromFileNameAndOffset( - context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength()); - } finally { - try { - afd.close(); - } catch (IOException ignored) { - } - } - } - - private static final class DictPackSettings { - final SharedPreferences mDictPreferences; - public DictPackSettings(final Context context) { - mDictPreferences = null == context ? null - : context.getSharedPreferences(COMMON_PREFERENCES_NAME, - Context.MODE_MULTI_PROCESS); - } - public boolean isWordListActive(final String dictId) { - if (null == mDictPreferences) { - // If we don't have preferences it basically means we can't find the dictionary - // pack - either it's not installed, or it's disabled, or there is some strange - // bug. Either way, a word list with no settings should be on by default: default - // dictionaries in LatinIME are on if there is no settings at all, and if for some - // reason some dictionaries have been installed BUT the dictionary pack can't be - // found anymore it's safer to actually supply installed dictionaries. - return true; - } - // The default is true here for the same reasons as above. We got the dictionary - // pack but if we don't have any settings for it it means the user has never been - // to the settings yet. So by default, the main dictionaries should be on. - return mDictPreferences.getBoolean(dictId, true); - } - } - - /** - * Utility class for the {@link #getCachedWordLists} method - */ - private static final class FileAndMatchLevel { - final File mFile; - final int mMatchLevel; - public FileAndMatchLevel(final File file, final int matchLevel) { - mFile = file; - mMatchLevel = matchLevel; - } - } - - /** - * Returns the list of cached files for a specific locale, one for each category. - * - * This will return exactly one file for each word list category that matches - * the passed locale. If several files match the locale for any given category, - * this returns the file with the closest match to the locale. For example, if - * the passed word list is en_US, and for a category we have an en and an en_US - * word list available, we'll return only the en_US one. - * Thus, the list will contain as many files as there are categories. - * - * @param locale the locale to find the dictionary files for, as a string. - * @param context the context on which to open the files upon. - * @return an array of binary dictionary files, which may be empty but may not be null. - */ - public static File[] getCachedWordLists(final String locale, final Context context) { - final File[] directoryList = DictionaryInfoUtils.getCachedDirectoryList(context); - if (null == directoryList) return EMPTY_FILE_ARRAY; - final HashMap<String, FileAndMatchLevel> cacheFiles = new HashMap<>(); - for (File directory : directoryList) { - if (!directory.isDirectory()) continue; - final String dirLocale = - DictionaryInfoUtils.getWordListIdFromFileName(directory.getName()); - final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale); - if (LocaleUtils.isMatch(matchLevel)) { - final File[] wordLists = directory.listFiles(); - if (null != wordLists) { - for (File wordList : wordLists) { - final String category = - DictionaryInfoUtils.getCategoryFromFileName(wordList.getName()); - final FileAndMatchLevel currentBestMatch = cacheFiles.get(category); - if (null == currentBestMatch || currentBestMatch.mMatchLevel < matchLevel) { - cacheFiles.put(category, new FileAndMatchLevel(wordList, matchLevel)); - } - } - } - } - } - if (cacheFiles.isEmpty()) return EMPTY_FILE_ARRAY; - final File[] result = new File[cacheFiles.size()]; - int index = 0; - for (final FileAndMatchLevel entry : cacheFiles.values()) { - result[index++] = entry.mFile; - } - return result; - } - - // ## HACK ## we prevent usage of a dictionary before version 18. The reason for this is, since - // those do not include allowlist entries, the new code with an old version of the dictionary - // would lose allowlist functionality. - private static boolean hackCanUseDictionaryFile(final File file) { - if (!SHOULD_USE_DICT_VERSION) { - return true; - } - - try { - // Read the version of the file - final DictionaryHeader header = BinaryDictionaryUtils.getHeader(file); - final String version = header.mDictionaryOptions.mAttributes.get(VERSION_KEY); - if (null == version) { - // No version in the options : the format is unexpected - return false; - } - // Version 18 is the first one to include the allowlist. - // Obviously this is a big ## HACK ## - return Integer.parseInt(version) >= 18; - } catch (java.io.FileNotFoundException e) { - return false; - } catch (java.io.IOException e) { - return false; - } catch (NumberFormatException e) { - return false; - } catch (BufferUnderflowException e) { - return false; - } catch (UnsupportedFormatException e) { - return false; - } - } - - /** - * Returns a list of file addresses for a given locale, trying relevant methods in order. - * - * Tries to get binary dictionaries from various sources, in order: - * - Uses a content provider to get a public dictionary set, as per the protocol described - * in BinaryDictionaryFileDumper. - * If that fails: - * - Gets a file name from the built-in dictionary for this locale, if any. - * If that fails: - * - Returns null. - * @return The list of addresses of valid dictionary files, or null. - */ - public static ArrayList<AssetFileAddress> getDictionaryFiles(final Locale locale, - final Context context, boolean notifyDictionaryPackForUpdates) { - if (notifyDictionaryPackForUpdates) { - final boolean hasDefaultWordList = DictionaryInfoUtils.isDictionaryAvailable( - context, locale); - // It makes sure that the first time keyboard comes up and the dictionaries are reset, - // the DB is populated with the appropriate values for each locale. Helps in downloading - // the dictionaries when the user enables and switches new languages before the - // DictionaryService runs. - BinaryDictionaryFileDumper.downloadDictIfNeverRequested( - locale, context, hasDefaultWordList); - - // Move a staging files to the cache ddirectories if any. - DictionaryInfoUtils.moveStagingFilesIfExists(context); - } - final File[] cachedWordLists = getCachedWordLists(locale.toString(), context); - final String mainDictId = DictionaryInfoUtils.getMainDictId(locale); - final DictPackSettings dictPackSettings = new DictPackSettings(context); - - boolean foundMainDict = false; - final ArrayList<AssetFileAddress> fileList = new ArrayList<>(); - // cachedWordLists may not be null, see doc for getCachedDictionaryList - for (final File f : cachedWordLists) { - final String wordListId = DictionaryInfoUtils.getWordListIdFromFileName(f.getName()); - final boolean canUse = f.canRead() && hackCanUseDictionaryFile(f); - if (canUse && DictionaryInfoUtils.isMainWordListId(wordListId)) { - foundMainDict = true; - } - if (!dictPackSettings.isWordListActive(wordListId)) continue; - if (canUse) { - final AssetFileAddress afa = AssetFileAddress.makeFromFileName(f.getPath()); - if (null != afa) fileList.add(afa); - } else { - Log.e(TAG, "Found a cached dictionary file for " + locale.toString() - + " but cannot read or use it"); - } - } - - if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) { - final int fallbackResId = - DictionaryInfoUtils.getMainDictionaryResourceId(context.getResources(), locale); - final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId); - if (null != fallbackAsset) { - fileList.add(fallbackAsset); - } - } - - return fileList; - } -} diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java deleted file mode 100644 index dbd639fe8..000000000 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin; - -import android.Manifest; -import android.content.Context; -import android.net.Uri; -import android.provider.ContactsContract; -import android.provider.ContactsContract.Contacts; -import android.util.Log; - -import com.android.inputmethod.annotations.ExternallyReferenced; -import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.permissions.PermissionsUtil; -import com.android.inputmethod.latin.personalization.AccountUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -import javax.annotation.Nullable; - -public class ContactsBinaryDictionary extends ExpandableBinaryDictionary - implements ContactsChangedListener { - private static final String TAG = ContactsBinaryDictionary.class.getSimpleName(); - private static final String NAME = "contacts"; - - private static final boolean DEBUG = false; - private static final boolean DEBUG_DUMP = false; - - /** - * Whether to use "firstname lastname" in bigram predictions. - */ - private final boolean mUseFirstLastBigrams; - private final ContactsManager mContactsManager; - - protected ContactsBinaryDictionary(final Context context, final Locale locale, - final File dictFile, final String name) { - super(context, getDictName(name, locale, dictFile), locale, Dictionary.TYPE_CONTACTS, - dictFile); - mUseFirstLastBigrams = ContactsDictionaryUtils.useFirstLastBigramsForLocale(locale); - mContactsManager = new ContactsManager(context); - mContactsManager.registerForUpdates(this /* listener */); - reloadDictionaryIfRequired(); - } - - // Note: This method is called by {@link DictionaryFacilitator} using Java reflection. - @ExternallyReferenced - public static ContactsBinaryDictionary getDictionary(final Context context, final Locale locale, - final File dictFile, final String dictNamePrefix, @Nullable final String account) { - return new ContactsBinaryDictionary(context, locale, dictFile, dictNamePrefix + NAME); - } - - @Override - public synchronized void close() { - mContactsManager.close(); - super.close(); - } - - /** - * Typically called whenever the dictionary is created for the first time or - * recreated when we think that there are updates to the dictionary. - * This is called asynchronously. - */ - @Override - public void loadInitialContentsLocked() { - loadDeviceAccountsEmailAddressesLocked(); - loadDictionaryForUriLocked(ContactsContract.Profile.CONTENT_URI); - // TODO: Switch this URL to the newer ContactsContract too - loadDictionaryForUriLocked(Contacts.CONTENT_URI); - } - - /** - * Loads device accounts to the dictionary. - */ - private void loadDeviceAccountsEmailAddressesLocked() { - final List<String> accountVocabulary = - AccountUtils.getDeviceAccountsEmailAddresses(mContext); - if (accountVocabulary == null || accountVocabulary.isEmpty()) { - return; - } - for (String word : accountVocabulary) { - if (DEBUG) { - Log.d(TAG, "loadAccountVocabulary: " + word); - } - runGCIfRequiredLocked(true /* mindsBlockByGC */); - addUnigramLocked(word, ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS, - false /* isNotAWord */, false /* isPossiblyOffensive */, - BinaryDictionary.NOT_A_VALID_TIMESTAMP); - } - } - - /** - * Loads data within content providers to the dictionary. - */ - private void loadDictionaryForUriLocked(final Uri uri) { - if (!PermissionsUtil.checkAllPermissionsGranted( - mContext, Manifest.permission.READ_CONTACTS)) { - Log.i(TAG, "No permission to read contacts. Not loading the Dictionary."); - } - - final ArrayList<String> validNames = mContactsManager.getValidNames(uri); - for (final String name : validNames) { - addNameLocked(name); - } - if (uri.equals(Contacts.CONTENT_URI)) { - // Since we were able to add content successfully, update the local - // state of the manager. - mContactsManager.updateLocalState(validNames); - } - } - - /** - * Adds the words in a name (e.g., firstname/lastname) to the binary dictionary along with their - * bigrams depending on locale. - */ - private void addNameLocked(final String name) { - int len = StringUtils.codePointCount(name); - NgramContext ngramContext = NgramContext.getEmptyPrevWordsContext( - BinaryDictionary.MAX_PREV_WORD_COUNT_FOR_N_GRAM); - // TODO: Better tokenization for non-Latin writing systems - for (int i = 0; i < len; i++) { - if (Character.isLetter(name.codePointAt(i))) { - int end = ContactsDictionaryUtils.getWordEndPosition(name, len, i); - String word = name.substring(i, end); - if (DEBUG_DUMP) { - Log.d(TAG, "addName word = " + word); - } - i = end - 1; - // Don't add single letter words, possibly confuses - // capitalization of i. - final int wordLen = StringUtils.codePointCount(word); - if (wordLen <= MAX_WORD_LENGTH && wordLen > 1) { - if (DEBUG) { - Log.d(TAG, "addName " + name + ", " + word + ", " + ngramContext); - } - runGCIfRequiredLocked(true /* mindsBlockByGC */); - addUnigramLocked(word, - ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS, false /* isNotAWord */, - false /* isPossiblyOffensive */, - BinaryDictionary.NOT_A_VALID_TIMESTAMP); - if (ngramContext.isValid() && mUseFirstLastBigrams) { - runGCIfRequiredLocked(true /* mindsBlockByGC */); - addNgramEntryLocked(ngramContext, - word, - ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS_BIGRAM, - BinaryDictionary.NOT_A_VALID_TIMESTAMP); - } - ngramContext = ngramContext.getNextNgramContext( - new NgramContext.WordInfo(word)); - } - } - } - } - - @Override - public void onContactsChange() { - setNeedsToRecreate(); - } -} diff --git a/java/src/com/android/inputmethod/latin/ContactsContentObserver.java b/java/src/com/android/inputmethod/latin/ContactsContentObserver.java deleted file mode 100644 index 6103a8296..000000000 --- a/java/src/com/android/inputmethod/latin/ContactsContentObserver.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin; - -import android.Manifest; -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.os.SystemClock; -import android.provider.ContactsContract.Contacts; -import android.util.Log; - -import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener; -import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.permissions.PermissionsUtil; -import com.android.inputmethod.latin.utils.ExecutorUtils; - -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * A content observer that listens to updates to content provider {@link Contacts#CONTENT_URI}. - */ -public class ContactsContentObserver implements Runnable { - private static final String TAG = "ContactsContentObserver"; - - private final Context mContext; - private final ContactsManager mManager; - private final AtomicBoolean mRunning = new AtomicBoolean(false); - - private ContentObserver mContentObserver; - private ContactsChangedListener mContactsChangedListener; - - public ContactsContentObserver(final ContactsManager manager, final Context context) { - mManager = manager; - mContext = context; - } - - public void registerObserver(final ContactsChangedListener listener) { - if (!PermissionsUtil.checkAllPermissionsGranted( - mContext, Manifest.permission.READ_CONTACTS)) { - Log.i(TAG, "No permission to read contacts. Not registering the observer."); - // do nothing if we do not have the permission to read contacts. - return; - } - - if (DebugFlags.DEBUG_ENABLED) { - Log.d(TAG, "registerObserver()"); - } - mContactsChangedListener = listener; - mContentObserver = new ContentObserver(null /* handler */) { - @Override - public void onChange(boolean self) { - ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD) - .execute(ContactsContentObserver.this); - } - }; - final ContentResolver contentResolver = mContext.getContentResolver(); - contentResolver.registerContentObserver(Contacts.CONTENT_URI, true, mContentObserver); - } - - @Override - public void run() { - if (!PermissionsUtil.checkAllPermissionsGranted( - mContext, Manifest.permission.READ_CONTACTS)) { - Log.i(TAG, "No permission to read contacts. Not updating the contacts."); - unregister(); - return; - } - - if (!mRunning.compareAndSet(false /* expect */, true /* update */)) { - if (DebugFlags.DEBUG_ENABLED) { - Log.d(TAG, "run() : Already running. Don't waste time checking again."); - } - return; - } - if (haveContentsChanged()) { - if (DebugFlags.DEBUG_ENABLED) { - Log.d(TAG, "run() : Contacts have changed. Notifying listeners."); - } - mContactsChangedListener.onContactsChange(); - } - mRunning.set(false); - } - - boolean haveContentsChanged() { - if (!PermissionsUtil.checkAllPermissionsGranted( - mContext, Manifest.permission.READ_CONTACTS)) { - Log.i(TAG, "No permission to read contacts. Marking contacts as not changed."); - return false; - } - - final long startTime = SystemClock.uptimeMillis(); - final int contactCount = mManager.getContactCount(); - if (contactCount > ContactsDictionaryConstants.MAX_CONTACTS_PROVIDER_QUERY_LIMIT) { - // If there are too many contacts then return false. In this rare case it is impossible - // to include all of them anyways and the cost of rebuilding the dictionary is too high. - // TODO: Sort and check only the most recent contacts? - return false; - } - if (contactCount != mManager.getContactCountAtLastRebuild()) { - if (DebugFlags.DEBUG_ENABLED) { - Log.d(TAG, "haveContentsChanged() : Count changed from " - + mManager.getContactCountAtLastRebuild() + " to " + contactCount); - } - return true; - } - final ArrayList<String> names = mManager.getValidNames(Contacts.CONTENT_URI); - if (names.hashCode() != mManager.getHashCodeAtLastRebuild()) { - return true; - } - if (DebugFlags.DEBUG_ENABLED) { - Log.d(TAG, "haveContentsChanged() : No change detected in " - + (SystemClock.uptimeMillis() - startTime) + " ms)"); - } - return false; - } - - public void unregister() { - mContext.getContentResolver().unregisterContentObserver(mContentObserver); - } -} diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionaryConstants.java b/java/src/com/android/inputmethod/latin/ContactsDictionaryConstants.java deleted file mode 100644 index 022940910..000000000 --- a/java/src/com/android/inputmethod/latin/ContactsDictionaryConstants.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.inputmethod.latin; - -import android.provider.BaseColumns; -import android.provider.ContactsContract.Contacts; - -/** - * Constants related to Contacts Content Provider. - */ -public class ContactsDictionaryConstants { - /** - * Projections for {@link Contacts.CONTENT_URI} - */ - public static final String[] PROJECTION = { BaseColumns._ID, Contacts.DISPLAY_NAME, - Contacts.TIMES_CONTACTED, Contacts.LAST_TIME_CONTACTED, Contacts.IN_VISIBLE_GROUP }; - public static final String[] PROJECTION_ID_ONLY = { BaseColumns._ID }; - - /** - * Frequency for contacts information into the dictionary - */ - public static final int FREQUENCY_FOR_CONTACTS = 40; - public static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90; - - /** - * Do not attempt to query contacts if there are more than this many entries. - */ - public static final int MAX_CONTACTS_PROVIDER_QUERY_LIMIT = 10000; - - /** - * Index of the column for 'name' in content providers: - * Contacts & ContactsContract.Profile. - */ - public static final int NAME_INDEX = 1; - public static final int TIMES_CONTACTED_INDEX = 2; - public static final int LAST_TIME_CONTACTED_INDEX = 3; - public static final int IN_VISIBLE_GROUP_INDEX = 4; -} diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionaryUtils.java b/java/src/com/android/inputmethod/latin/ContactsDictionaryUtils.java deleted file mode 100644 index b77388434..000000000 --- a/java/src/com/android/inputmethod/latin/ContactsDictionaryUtils.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin; - -import com.android.inputmethod.latin.common.Constants; - -import java.util.Locale; - -/** - * Utility methods related contacts dictionary. - */ -public class ContactsDictionaryUtils { - - /** - * Returns the index of the last letter in the word, starting from position startIndex. - */ - public static int getWordEndPosition(final String string, final int len, - final int startIndex) { - int end; - int cp = 0; - for (end = startIndex + 1; end < len; end += Character.charCount(cp)) { - cp = string.codePointAt(end); - if (cp != Constants.CODE_DASH && cp != Constants.CODE_SINGLE_QUOTE - && !Character.isLetter(cp)) { - break; - } - } - return end; - } - - /** - * Returns true if the locale supports using first name and last name as bigrams. - */ - public static boolean useFirstLastBigramsForLocale(final Locale locale) { - // TODO: Add firstname/lastname bigram rules for other languages. - if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { - return true; - } - return false; - } -} diff --git a/java/src/com/android/inputmethod/latin/ContactsManager.java b/java/src/com/android/inputmethod/latin/ContactsManager.java deleted file mode 100644 index 13503ff45..000000000 --- a/java/src/com/android/inputmethod/latin/ContactsManager.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin; - -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.net.Uri; -import android.provider.ContactsContract.Contacts; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Manages all interactions with Contacts DB. - * - * The manager provides an API for listening to meaning full updates by keeping a - * measure of the current state of the content provider. - */ -public class ContactsManager { - private static final String TAG = "ContactsManager"; - - /** - * Use at most this many of the highest affinity contacts. - */ - public static final int MAX_CONTACT_NAMES = 200; - - protected static class RankedContact { - public final String mName; - public final long mLastContactedTime; - public final int mTimesContacted; - public final boolean mInVisibleGroup; - - private float mAffinity = 0.0f; - - RankedContact(final Cursor cursor) { - mName = cursor.getString( - ContactsDictionaryConstants.NAME_INDEX); - mTimesContacted = cursor.getInt( - ContactsDictionaryConstants.TIMES_CONTACTED_INDEX); - mLastContactedTime = cursor.getLong( - ContactsDictionaryConstants.LAST_TIME_CONTACTED_INDEX); - mInVisibleGroup = cursor.getInt( - ContactsDictionaryConstants.IN_VISIBLE_GROUP_INDEX) == 1; - } - - float getAffinity() { - return mAffinity; - } - - /** - * Calculates the affinity with the contact based on: - * - How many times it has been contacted - * - How long since the last contact. - * - Whether the contact is in the visible group (i.e., Contacts list). - * - * Note: This affinity is limited by the fact that some apps currently do not update the - * LAST_TIME_CONTACTED or TIMES_CONTACTED counters. As a result, a frequently messaged - * contact may still have 0 affinity. - */ - void computeAffinity(final int maxTimesContacted, final long currentTime) { - final float timesWeight = ((float) mTimesContacted + 1) / (maxTimesContacted + 1); - final long timeSinceLastContact = Math.min( - Math.max(0, currentTime - mLastContactedTime), - TimeUnit.MILLISECONDS.convert(180, TimeUnit.DAYS)); - final float lastTimeWeight = (float) Math.pow(0.5, - timeSinceLastContact / (TimeUnit.MILLISECONDS.convert(10, TimeUnit.DAYS))); - final float visibleWeight = mInVisibleGroup ? 1.0f : 0.0f; - mAffinity = (timesWeight + lastTimeWeight + visibleWeight) / 3; - } - } - - private static class AffinityComparator implements Comparator<RankedContact> { - @Override - public int compare(RankedContact contact1, RankedContact contact2) { - return Float.compare(contact2.getAffinity(), contact1.getAffinity()); - } - } - - /** - * Interface to implement for classes interested in getting notified for updates - * to Contacts content provider. - */ - public static interface ContactsChangedListener { - public void onContactsChange(); - } - - /** - * The number of contacts observed in the most recent instance of - * contacts content provider. - */ - private AtomicInteger mContactCountAtLastRebuild = new AtomicInteger(0); - - /** - * The hash code of list of valid contacts names in the most recent dictionary - * rebuild. - */ - private AtomicInteger mHashCodeAtLastRebuild = new AtomicInteger(0); - - private final Context mContext; - private final ContactsContentObserver mObserver; - - public ContactsManager(final Context context) { - mContext = context; - mObserver = new ContactsContentObserver(this /* ContactsManager */, context); - } - - // TODO: This was synchronized in previous version. Why? - public void registerForUpdates(final ContactsChangedListener listener) { - mObserver.registerObserver(listener); - } - - public int getContactCountAtLastRebuild() { - return mContactCountAtLastRebuild.get(); - } - - public int getHashCodeAtLastRebuild() { - return mHashCodeAtLastRebuild.get(); - } - - /** - * Returns all the valid names in the Contacts DB. Callers should also - * call {@link #updateLocalState(ArrayList)} after they are done with result - * so that the manager can cache local state for determining updates. - * - * These names are sorted by their affinity to the user, with favorite - * contacts appearing first. - */ - public ArrayList<String> getValidNames(final Uri uri) { - // Check all contacts since it's not possible to find out which names have changed. - // This is needed because it's possible to receive extraneous onChange events even when no - // name has changed. - final Cursor cursor = mContext.getContentResolver().query(uri, - ContactsDictionaryConstants.PROJECTION, null, null, null); - final ArrayList<RankedContact> contacts = new ArrayList<>(); - int maxTimesContacted = 0; - if (cursor != null) { - try { - if (cursor.moveToFirst()) { - while (!cursor.isAfterLast()) { - final String name = cursor.getString( - ContactsDictionaryConstants.NAME_INDEX); - if (isValidName(name)) { - final int timesContacted = cursor.getInt( - ContactsDictionaryConstants.TIMES_CONTACTED_INDEX); - if (timesContacted > maxTimesContacted) { - maxTimesContacted = timesContacted; - } - contacts.add(new RankedContact(cursor)); - } - cursor.moveToNext(); - } - } - } finally { - cursor.close(); - } - } - final long currentTime = System.currentTimeMillis(); - for (RankedContact contact : contacts) { - contact.computeAffinity(maxTimesContacted, currentTime); - } - Collections.sort(contacts, new AffinityComparator()); - final HashSet<String> names = new HashSet<>(); - for (int i = 0; i < contacts.size() && names.size() < MAX_CONTACT_NAMES; ++i) { - names.add(contacts.get(i).mName); - } - return new ArrayList<>(names); - } - - /** - * Returns the number of contacts in contacts content provider. - */ - public int getContactCount() { - // TODO: consider switching to a rawQuery("select count(*)...") on the database if - // performance is a bottleneck. - Cursor cursor = null; - try { - cursor = mContext.getContentResolver().query(Contacts.CONTENT_URI, - ContactsDictionaryConstants.PROJECTION_ID_ONLY, null, null, null); - if (null == cursor) { - return 0; - } - return cursor.getCount(); - } catch (final SQLiteException e) { - Log.e(TAG, "SQLiteException in the remote Contacts process.", e); - } finally { - if (null != cursor) { - cursor.close(); - } - } - return 0; - } - - private static boolean isValidName(final String name) { - if (TextUtils.isEmpty(name) || name.indexOf(Constants.CODE_COMMERCIAL_AT) != -1) { - return false; - } - final boolean hasSpace = name.indexOf(Constants.CODE_SPACE) != -1; - if (!hasSpace) { - // Only allow an isolated word if it does not contain a hyphen. - // This helps to filter out mailing lists. - return name.indexOf(Constants.CODE_DASH) == -1; - } - return true; - } - - /** - * Updates the local state of the manager. This should be called when the callers - * are done with all the updates of the content provider successfully. - */ - public void updateLocalState(final ArrayList<String> names) { - mContactCountAtLastRebuild.set(getContactCount()); - mHashCodeAtLastRebuild.set(names.hashCode()); - } - - /** - * Performs any necessary cleanup. - */ - public void close() { - mObserver.unregister(); - } -} diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java deleted file mode 100644 index 6816f129a..000000000 --- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2012, 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 com.android.inputmethod.latin; - -import com.android.inputmethod.latin.common.NativeSuggestOptions; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; -import com.android.inputmethod.latin.utils.JniUtils; - -import java.util.Locale; - -public final class DicTraverseSession { - static { - JniUtils.loadNativeLibrary(); - } - // Must be equal to MAX_RESULTS in native/jni/src/defines.h - private static final int MAX_RESULTS = 18; - public final int[] mInputCodePoints = - new int[DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH]; - public final int[][] mPrevWordCodePointArrays = - new int[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; - public final boolean[] mIsBeginningOfSentenceArray = - new boolean[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; - public final int[] mOutputSuggestionCount = new int[1]; - public final int[] mOutputCodePoints = - new int[DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH * MAX_RESULTS]; - public final int[] mSpaceIndices = new int[MAX_RESULTS]; - public final int[] mOutputScores = new int[MAX_RESULTS]; - public final int[] mOutputTypes = new int[MAX_RESULTS]; - // Only one result is ever used - public final int[] mOutputAutoCommitFirstWordConfidence = new int[1]; - public final float[] mInputOutputWeightOfLangModelVsSpatialModel = new float[1]; - - public final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions(); - - private static native long setDicTraverseSessionNative(String locale, long dictSize); - private static native void initDicTraverseSessionNative(long nativeDicTraverseSession, - long dictionary, int[] previousWord, int previousWordLength); - private static native void releaseDicTraverseSessionNative(long nativeDicTraverseSession); - - private long mNativeDicTraverseSession; - - public DicTraverseSession(Locale locale, long dictionary, long dictSize) { - mNativeDicTraverseSession = createNativeDicTraverseSession( - locale != null ? locale.toString() : "", dictSize); - initSession(dictionary); - } - - public long getSession() { - return mNativeDicTraverseSession; - } - - public void initSession(long dictionary) { - initSession(dictionary, null, 0); - } - - public void initSession(long dictionary, int[] previousWord, int previousWordLength) { - initDicTraverseSessionNative( - mNativeDicTraverseSession, dictionary, previousWord, previousWordLength); - } - - private static long createNativeDicTraverseSession(String locale, long dictSize) { - return setDicTraverseSessionNative(locale, dictSize); - } - - private void closeInternal() { - if (mNativeDicTraverseSession != 0) { - releaseDicTraverseSessionNative(mNativeDicTraverseSession); - mNativeDicTraverseSession = 0; - } - } - - public void close() { - closeInternal(); - } - - @Override - protected void finalize() throws Throwable { - try { - closeInternal(); - } finally { - super.finalize(); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java deleted file mode 100644 index e00219c98..000000000 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.inputmethod.latin; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.ComposedData; -import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; - -import java.util.ArrayList; -import java.util.Locale; -import java.util.Arrays; -import java.util.HashSet; - -/** - * Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key - * strokes. - */ -public abstract class Dictionary { - public static final int NOT_A_PROBABILITY = -1; - public static final float NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL = -1.0f; - - // The following types do not actually come from real dictionary instances, so we create - // corresponding instances. - public static final String TYPE_USER_TYPED = "user_typed"; - public static final PhonyDictionary DICTIONARY_USER_TYPED = new PhonyDictionary(TYPE_USER_TYPED); - - public static final String TYPE_USER_SHORTCUT = "user_shortcut"; - public static final PhonyDictionary DICTIONARY_USER_SHORTCUT = - new PhonyDictionary(TYPE_USER_SHORTCUT); - - public static final String TYPE_APPLICATION_DEFINED = "application_defined"; - public static final PhonyDictionary DICTIONARY_APPLICATION_DEFINED = - new PhonyDictionary(TYPE_APPLICATION_DEFINED); - - public static final String TYPE_HARDCODED = "hardcoded"; // punctuation signs and such - public static final PhonyDictionary DICTIONARY_HARDCODED = - new PhonyDictionary(TYPE_HARDCODED); - - // Spawned by resuming suggestions. Comes from a span that was in the TextView. - public static final String TYPE_RESUMED = "resumed"; - public static final PhonyDictionary DICTIONARY_RESUMED = new PhonyDictionary(TYPE_RESUMED); - - // The following types of dictionary have actual functional instances. We don't need final - // phony dictionary instances for them. - public static final String TYPE_MAIN = "main"; - public static final String TYPE_CONTACTS = "contacts"; - // User dictionary, the system-managed one. - public static final String TYPE_USER = "user"; - // User history dictionary internal to LatinIME. - public static final String TYPE_USER_HISTORY = "history"; - public final String mDictType; - // The locale for this dictionary. May be null if unknown (phony dictionary for example). - public final Locale mLocale; - - /** - * Set out of the dictionary types listed above that are based on data specific to the user, - * e.g., the user's contacts. - */ - private static final HashSet<String> sUserSpecificDictionaryTypes = new HashSet<>(Arrays.asList( - TYPE_USER_TYPED, - TYPE_USER, - TYPE_CONTACTS, - TYPE_USER_HISTORY)); - - public Dictionary(final String dictType, final Locale locale) { - mDictType = dictType; - mLocale = locale; - } - - /** - * Searches for suggestions for a given context. - * @param composedData the key sequence to match with coordinate info - * @param ngramContext the context for n-gram. - * @param proximityInfoHandle the handle for key proximity. Is ignored by some implementations. - * @param settingsValuesForSuggestion the settings values used for the suggestion. - * @param sessionId the session id. - * @param weightForLocale the weight given to this locale, to multiply the output scores for - * multilingual input. - * @param inOutWeightOfLangModelVsSpatialModel the weight of the language model as a ratio of - * the spatial model, used for generating suggestions. inOutWeightOfLangModelVsSpatialModel is - * a float array that has only one element. This can be updated when a different value is used. - * @return the list of suggestions (possibly null if none) - */ - abstract public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, - final NgramContext ngramContext, final long proximityInfoHandle, - final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int sessionId, final float weightForLocale, - final float[] inOutWeightOfLangModelVsSpatialModel); - - /** - * Checks if the given word has to be treated as a valid word. Please note that some - * dictionaries have entries that should be treated as invalid words. - * @param word the word to search for. The search should be case-insensitive. - * @return true if the word is valid, false otherwise - */ - public boolean isValidWord(final String word) { - return isInDictionary(word); - } - - /** - * Checks if the given word is in the dictionary regardless of it being valid or not. - */ - abstract public boolean isInDictionary(final String word); - - /** - * Get the frequency of the word. - * @param word the word to get the frequency of. - */ - public int getFrequency(final String word) { - return NOT_A_PROBABILITY; - } - - /** - * Get the maximum frequency of the word. - * @param word the word to get the maximum frequency of. - */ - public int getMaxFrequencyOfExactMatches(final String word) { - return NOT_A_PROBABILITY; - } - - /** - * Compares the contents of the character array with the typed word and returns true if they - * are the same. - * @param word the array of characters that make up the word - * @param length the number of valid characters in the character array - * @param typedWord the word to compare with - * @return true if they are the same, false otherwise. - */ - protected boolean same(final char[] word, final int length, final String typedWord) { - if (typedWord.length() != length) { - return false; - } - for (int i = 0; i < length; i++) { - if (word[i] != typedWord.charAt(i)) { - return false; - } - } - return true; - } - - /** - * Override to clean up any resources. - */ - public void close() { - // empty base implementation - } - - /** - * Subclasses may override to indicate that this Dictionary is not yet properly initialized. - */ - public boolean isInitialized() { - return true; - } - - /** - * Whether we think this suggestion should trigger an auto-commit. prevWord is the word - * before the suggestion, so that we can use n-gram frequencies. - * @param candidate The candidate suggestion, in whole (not only the first part). - * @return whether we should auto-commit or not. - */ - public boolean shouldAutoCommit(final SuggestedWordInfo candidate) { - // If we don't have support for auto-commit, or if we don't know, we return false to - // avoid auto-committing stuff. Implementations of the Dictionary class that know to - // determine whether we should auto-commit will override this. - return false; - } - - /** - * Whether this dictionary is based on data specific to the user, e.g., the user's contacts. - * @return Whether this dictionary is specific to the user. - */ - public boolean isUserSpecific() { - return sUserSpecificDictionaryTypes.contains(mDictType); - } - - /** - * Not a true dictionary. A placeholder used to indicate suggestions that don't come from any - * real dictionary. - */ - @UsedForTesting - static class PhonyDictionary extends Dictionary { - @UsedForTesting - PhonyDictionary(final String type) { - super(type, null); - } - - @Override - public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, - final NgramContext ngramContext, final long proximityInfoHandle, - final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int sessionId, final float weightForLocale, - final float[] inOutWeightOfLangModelVsSpatialModel) { - return null; - } - - @Override - public boolean isInDictionary(String word) { - return false; - } - } -} diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java deleted file mode 100644 index 96575f629..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin; - -import android.util.Log; - -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.ComposedData; -import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Locale; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Class for a collection of dictionaries that behave like one dictionary. - */ -public final class DictionaryCollection extends Dictionary { - private final String TAG = DictionaryCollection.class.getSimpleName(); - protected final CopyOnWriteArrayList<Dictionary> mDictionaries; - - public DictionaryCollection(final String dictType, final Locale locale) { - super(dictType, locale); - mDictionaries = new CopyOnWriteArrayList<>(); - } - - public DictionaryCollection(final String dictType, final Locale locale, - final Dictionary... dictionaries) { - super(dictType, locale); - if (null == dictionaries) { - mDictionaries = new CopyOnWriteArrayList<>(); - } else { - mDictionaries = new CopyOnWriteArrayList<>(dictionaries); - mDictionaries.removeAll(Collections.singleton(null)); - } - } - - public DictionaryCollection(final String dictType, final Locale locale, - final Collection<Dictionary> dictionaries) { - super(dictType, locale); - mDictionaries = new CopyOnWriteArrayList<>(dictionaries); - mDictionaries.removeAll(Collections.singleton(null)); - } - - @Override - public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, - final NgramContext ngramContext, final long proximityInfoHandle, - final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int sessionId, final float weightForLocale, - final float[] inOutWeightOfLangModelVsSpatialModel) { - final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries; - if (dictionaries.isEmpty()) return null; - // To avoid creating unnecessary objects, we get the list out of the first - // dictionary and add the rest to it if not null, hence the get(0) - ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composedData, - ngramContext, proximityInfoHandle, settingsValuesForSuggestion, sessionId, - weightForLocale, inOutWeightOfLangModelVsSpatialModel); - if (null == suggestions) suggestions = new ArrayList<>(); - final int length = dictionaries.size(); - for (int i = 1; i < length; ++ i) { - final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions( - composedData, ngramContext, proximityInfoHandle, settingsValuesForSuggestion, - sessionId, weightForLocale, inOutWeightOfLangModelVsSpatialModel); - if (null != sugg) suggestions.addAll(sugg); - } - return suggestions; - } - - @Override - public boolean isInDictionary(final String word) { - for (int i = mDictionaries.size() - 1; i >= 0; --i) - if (mDictionaries.get(i).isInDictionary(word)) return true; - return false; - } - - @Override - public int getFrequency(final String word) { - int maxFreq = -1; - for (int i = mDictionaries.size() - 1; i >= 0; --i) { - final int tempFreq = mDictionaries.get(i).getFrequency(word); - maxFreq = Math.max(tempFreq, maxFreq); - } - return maxFreq; - } - - @Override - public int getMaxFrequencyOfExactMatches(final String word) { - int maxFreq = -1; - for (int i = mDictionaries.size() - 1; i >= 0; --i) { - final int tempFreq = mDictionaries.get(i).getMaxFrequencyOfExactMatches(word); - maxFreq = Math.max(tempFreq, maxFreq); - } - return maxFreq; - } - - @Override - public boolean isInitialized() { - return !mDictionaries.isEmpty(); - } - - @Override - public void close() { - for (final Dictionary dict : mDictionaries) - dict.close(); - } - - // Warning: this is not thread-safe. Take necessary precaution when calling. - public void addDictionary(final Dictionary newDict) { - if (null == newDict) return; - if (mDictionaries.contains(newDict)) { - Log.w(TAG, "This collection already contains this dictionary: " + newDict); - } - mDictionaries.add(newDict); - } - - // Warning: this is not thread-safe. Take necessary precaution when calling. - public void removeDictionary(final Dictionary dict) { - if (mDictionaries.contains(dict)) { - mDictionaries.remove(dict); - } else { - Log.w(TAG, "This collection does not contain this dictionary: " + dict); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/DictionaryDumpBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryDumpBroadcastReceiver.java deleted file mode 100644 index ee2fdc6c7..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryDumpBroadcastReceiver.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -public class DictionaryDumpBroadcastReceiver extends BroadcastReceiver { - private static final String TAG = DictionaryDumpBroadcastReceiver.class.getSimpleName(); - - private static final String DOMAIN = "com.android.inputmethod.latin"; - public static final String DICTIONARY_DUMP_INTENT_ACTION = DOMAIN + ".DICT_DUMP"; - public static final String DICTIONARY_NAME_KEY = "dictName"; - - final LatinIME mLatinIme; - - public DictionaryDumpBroadcastReceiver(final LatinIME latinIme) { - mLatinIme = latinIme; - } - - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (action.equals(DICTIONARY_DUMP_INTENT_ACTION)) { - final String dictName = intent.getStringExtra(DICTIONARY_NAME_KEY); - if (dictName == null) { - Log.e(TAG, "Received dictionary dump intent action " + - "but the dictionary name is not set."); - return; - } - mLatinIme.dumpDictionaryForDebug(dictName); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java deleted file mode 100644 index 02015da09..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin; - -import android.content.Context; -import android.util.LruCache; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.latin.common.ComposedData; -import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; -import com.android.inputmethod.latin.utils.SuggestionResults; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Interface that facilitates interaction with different kinds of dictionaries. Provides APIs to - * instantiate and select the correct dictionaries (based on language or account), update entries - * and fetch suggestions. Currently AndroidSpellCheckerService and LatinIME both use - * DictionaryFacilitator as a client for interacting with dictionaries. - */ -public interface DictionaryFacilitator { - - public static final String[] ALL_DICTIONARY_TYPES = new String[] { - Dictionary.TYPE_MAIN, - Dictionary.TYPE_CONTACTS, - Dictionary.TYPE_USER_HISTORY, - Dictionary.TYPE_USER}; - - public static final String[] DYNAMIC_DICTIONARY_TYPES = new String[] { - Dictionary.TYPE_CONTACTS, - Dictionary.TYPE_USER_HISTORY, - Dictionary.TYPE_USER}; - - /** - * The facilitator will put words into the cache whenever it decodes them. - * @param cache - */ - void setValidSpellingWordReadCache(final LruCache<String, Boolean> cache); - - /** - * The facilitator will get words from the cache whenever it needs to check their spelling. - * @param cache - */ - void setValidSpellingWordWriteCache(final LruCache<String, Boolean> cache); - - /** - * Returns whether this facilitator is exactly for this locale. - * - * @param locale the locale to test against - */ - boolean isForLocale(final Locale locale); - - /** - * Returns whether this facilitator is exactly for this account. - * - * @param account the account to test against. - */ - boolean isForAccount(@Nullable final String account); - - interface DictionaryInitializationListener { - void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable); - } - - /** - * Called every time {@link LatinIME} starts on a new text field. - * Dot not affect {@link AndroidSpellCheckerService}. - * - * WARNING: The service methods that call start/finish are very spammy. - */ - void onStartInput(); - - /** - * Called every time the {@link LatinIME} finishes with the current text field. - * May be followed by {@link #onStartInput} again in another text field, - * or it may be done for a while. - * Dot not affect {@link AndroidSpellCheckerService}. - * - * WARNING: The service methods that call start/finish are very spammy. - */ - void onFinishInput(Context context); - - boolean isActive(); - - Locale getLocale(); - - boolean usesContacts(); - - String getAccount(); - - void resetDictionaries( - final Context context, - final Locale newLocale, - final boolean useContactsDict, - final boolean usePersonalizedDicts, - final boolean forceReloadMainDictionary, - @Nullable final String account, - final String dictNamePrefix, - @Nullable final DictionaryInitializationListener listener); - - @UsedForTesting - void resetDictionariesForTesting( - final Context context, - final Locale locale, - final ArrayList<String> dictionaryTypes, - final HashMap<String, File> dictionaryFiles, - final Map<String, Map<String, String>> additionalDictAttributes, - @Nullable final String account); - - void closeDictionaries(); - - @UsedForTesting - ExpandableBinaryDictionary getSubDictForTesting(final String dictName); - - // The main dictionaries are loaded asynchronously. Don't cache the return value - // of these methods. - boolean hasAtLeastOneInitializedMainDictionary(); - - boolean hasAtLeastOneUninitializedMainDictionary(); - - void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit) - throws InterruptedException; - - @UsedForTesting - void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit) - throws InterruptedException; - - void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized, - @Nonnull final NgramContext ngramContext, final long timeStampInSeconds, - final boolean blockPotentiallyOffensive); - - void unlearnFromUserHistory(final String word, - @Nonnull final NgramContext ngramContext, final long timeStampInSeconds, - final int eventType); - - // TODO: Revise the way to fusion suggestion results. - @Nonnull SuggestionResults getSuggestionResults(final ComposedData composedData, - final NgramContext ngramContext, @Nonnull final Keyboard keyboard, - final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId, - final int inputStyle); - - boolean isValidSpellingWord(final String word); - - boolean isValidSuggestionWord(final String word); - - boolean clearUserHistoryDictionary(final Context context); - - String dump(final Context context); - - void dumpDictionaryForDebug(final String dictName); - - @Nonnull List<DictionaryStats> getDictionaryStats(final Context context); -} diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java deleted file mode 100644 index b435de867..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java +++ /dev/null @@ -1,736 +0,0 @@ -/* -7 * Copyright (C) 2013 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 com.android.inputmethod.latin; - -import android.Manifest; -import android.content.Context; -import android.text.TextUtils; -import android.util.Log; -import android.util.LruCache; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.latin.NgramContext.WordInfo; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.ComposedData; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.permissions.PermissionsUtil; -import com.android.inputmethod.latin.personalization.UserHistoryDictionary; -import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; -import com.android.inputmethod.latin.utils.ExecutorUtils; -import com.android.inputmethod.latin.utils.SuggestionResults; - -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Facilitates interaction with different kinds of dictionaries. Provides APIs - * to instantiate and select the correct dictionaries (based on language or account), - * update entries and fetch suggestions. - * - * Currently AndroidSpellCheckerService and LatinIME both use DictionaryFacilitator as - * a client for interacting with dictionaries. - */ -public class DictionaryFacilitatorImpl implements DictionaryFacilitator { - // TODO: Consolidate dictionaries in native code. - public static final String TAG = DictionaryFacilitatorImpl.class.getSimpleName(); - - // HACK: This threshold is being used when adding a capitalized entry in the User History - // dictionary. - private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140; - - private DictionaryGroup mDictionaryGroup = new DictionaryGroup(); - private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0); - // To synchronize assigning mDictionaryGroup to ensure closing dictionaries. - private final Object mLock = new Object(); - - public static final Map<String, Class<? extends ExpandableBinaryDictionary>> - DICT_TYPE_TO_CLASS = new HashMap<>(); - - static { - DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class); - DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class); - DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class); - } - - private static final String DICT_FACTORY_METHOD_NAME = "getDictionary"; - private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES = - new Class[] { Context.class, Locale.class, File.class, String.class, String.class }; - - private LruCache<String, Boolean> mValidSpellingWordReadCache; - private LruCache<String, Boolean> mValidSpellingWordWriteCache; - - @Override - public void setValidSpellingWordReadCache(final LruCache<String, Boolean> cache) { - mValidSpellingWordReadCache = cache; - } - - @Override - public void setValidSpellingWordWriteCache(final LruCache<String, Boolean> cache) { - mValidSpellingWordWriteCache = cache; - } - - @Override - public boolean isForLocale(final Locale locale) { - return locale != null && locale.equals(mDictionaryGroup.mLocale); - } - - /** - * Returns whether this facilitator is exactly for this account. - * - * @param account the account to test against. - */ - public boolean isForAccount(@Nullable final String account) { - return TextUtils.equals(mDictionaryGroup.mAccount, account); - } - - /** - * A group of dictionaries that work together for a single language. - */ - private static class DictionaryGroup { - // TODO: Add null analysis annotations. - // TODO: Run evaluation to determine a reasonable value for these constants. The current - // values are ad-hoc and chosen without any particular care or methodology. - public static final float WEIGHT_FOR_MOST_PROBABLE_LANGUAGE = 1.0f; - public static final float WEIGHT_FOR_GESTURING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.95f; - public static final float WEIGHT_FOR_TYPING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.6f; - - /** - * The locale associated with the dictionary group. - */ - @Nullable public final Locale mLocale; - - /** - * The user account associated with the dictionary group. - */ - @Nullable public final String mAccount; - - @Nullable private Dictionary mMainDict; - // Confidence that the most probable language is actually the language the user is - // typing in. For now, this is simply the number of times a word from this language - // has been committed in a row. - private int mConfidence = 0; - - public float mWeightForTypingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE; - public float mWeightForGesturingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE; - public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap = - new ConcurrentHashMap<>(); - - public DictionaryGroup() { - this(null /* locale */, null /* mainDict */, null /* account */, - Collections.<String, ExpandableBinaryDictionary>emptyMap() /* subDicts */); - } - - public DictionaryGroup(@Nullable final Locale locale, - @Nullable final Dictionary mainDict, - @Nullable final String account, - final Map<String, ExpandableBinaryDictionary> subDicts) { - mLocale = locale; - mAccount = account; - // The main dictionary can be asynchronously loaded. - setMainDict(mainDict); - for (final Map.Entry<String, ExpandableBinaryDictionary> entry : subDicts.entrySet()) { - setSubDict(entry.getKey(), entry.getValue()); - } - } - - private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) { - if (dict != null) { - mSubDictMap.put(dictType, dict); - } - } - - public void setMainDict(final Dictionary mainDict) { - // Close old dictionary if exists. Main dictionary can be assigned multiple times. - final Dictionary oldDict = mMainDict; - mMainDict = mainDict; - if (oldDict != null && mainDict != oldDict) { - oldDict.close(); - } - } - - public Dictionary getDict(final String dictType) { - if (Dictionary.TYPE_MAIN.equals(dictType)) { - return mMainDict; - } - return getSubDict(dictType); - } - - public ExpandableBinaryDictionary getSubDict(final String dictType) { - return mSubDictMap.get(dictType); - } - - public boolean hasDict(final String dictType, @Nullable final String account) { - if (Dictionary.TYPE_MAIN.equals(dictType)) { - return mMainDict != null; - } - if (Dictionary.TYPE_USER_HISTORY.equals(dictType) && - !TextUtils.equals(account, mAccount)) { - // If the dictionary type is user history, & if the account doesn't match, - // return immediately. If the account matches, continue looking it up in the - // sub dictionary map. - return false; - } - return mSubDictMap.containsKey(dictType); - } - - public void closeDict(final String dictType) { - final Dictionary dict; - if (Dictionary.TYPE_MAIN.equals(dictType)) { - dict = mMainDict; - } else { - dict = mSubDictMap.remove(dictType); - } - if (dict != null) { - dict.close(); - } - } - } - - public DictionaryFacilitatorImpl() { - } - - @Override - public void onStartInput() { - } - - @Override - public void onFinishInput(Context context) { - } - - @Override - public boolean isActive() { - return mDictionaryGroup.mLocale != null; - } - - @Override - public Locale getLocale() { - return mDictionaryGroup.mLocale; - } - - @Override - public boolean usesContacts() { - return mDictionaryGroup.getSubDict(Dictionary.TYPE_CONTACTS) != null; - } - - @Override - public String getAccount() { - return null; - } - - @Nullable - private static ExpandableBinaryDictionary getSubDict(final String dictType, - final Context context, final Locale locale, final File dictFile, - final String dictNamePrefix, @Nullable final String account) { - final Class<? extends ExpandableBinaryDictionary> dictClass = - DICT_TYPE_TO_CLASS.get(dictType); - if (dictClass == null) { - return null; - } - try { - final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME, - DICT_FACTORY_METHOD_ARG_TYPES); - final Object dict = factoryMethod.invoke(null /* obj */, - new Object[] { context, locale, dictFile, dictNamePrefix, account }); - return (ExpandableBinaryDictionary) dict; - } catch (final NoSuchMethodException | SecurityException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException e) { - Log.e(TAG, "Cannot create dictionary: " + dictType, e); - return null; - } - } - - @Nullable - static DictionaryGroup findDictionaryGroupWithLocale(final DictionaryGroup dictionaryGroup, - final Locale locale) { - return locale.equals(dictionaryGroup.mLocale) ? dictionaryGroup : null; - } - - @Override - public void resetDictionaries( - final Context context, - final Locale newLocale, - final boolean useContactsDict, - final boolean usePersonalizedDicts, - final boolean forceReloadMainDictionary, - @Nullable final String account, - final String dictNamePrefix, - @Nullable final DictionaryInitializationListener listener) { - final HashMap<Locale, ArrayList<String>> existingDictionariesToCleanup = new HashMap<>(); - // TODO: Make subDictTypesToUse configurable by resource or a static final list. - final HashSet<String> subDictTypesToUse = new HashSet<>(); - subDictTypesToUse.add(Dictionary.TYPE_USER); - - // Do not use contacts dictionary if we do not have permissions to read contacts. - final boolean contactsPermissionGranted = PermissionsUtil.checkAllPermissionsGranted( - context, Manifest.permission.READ_CONTACTS); - if (useContactsDict && contactsPermissionGranted) { - subDictTypesToUse.add(Dictionary.TYPE_CONTACTS); - } - if (usePersonalizedDicts) { - subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY); - } - - // Gather all dictionaries. We'll remove them from the list to clean up later. - final ArrayList<String> dictTypeForLocale = new ArrayList<>(); - existingDictionariesToCleanup.put(newLocale, dictTypeForLocale); - final DictionaryGroup currentDictionaryGroupForLocale = - findDictionaryGroupWithLocale(mDictionaryGroup, newLocale); - if (currentDictionaryGroupForLocale != null) { - for (final String dictType : DYNAMIC_DICTIONARY_TYPES) { - if (currentDictionaryGroupForLocale.hasDict(dictType, account)) { - dictTypeForLocale.add(dictType); - } - } - if (currentDictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) { - dictTypeForLocale.add(Dictionary.TYPE_MAIN); - } - } - - final DictionaryGroup dictionaryGroupForLocale = - findDictionaryGroupWithLocale(mDictionaryGroup, newLocale); - final ArrayList<String> dictTypesToCleanupForLocale = - existingDictionariesToCleanup.get(newLocale); - final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale); - - final Dictionary mainDict; - if (forceReloadMainDictionary || noExistingDictsForThisLocale - || !dictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) { - mainDict = null; - } else { - mainDict = dictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN); - dictTypesToCleanupForLocale.remove(Dictionary.TYPE_MAIN); - } - - final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>(); - for (final String subDictType : subDictTypesToUse) { - final ExpandableBinaryDictionary subDict; - if (noExistingDictsForThisLocale - || !dictionaryGroupForLocale.hasDict(subDictType, account)) { - // Create a new dictionary. - subDict = getSubDict(subDictType, context, newLocale, null /* dictFile */, - dictNamePrefix, account); - } else { - // Reuse the existing dictionary, and don't close it at the end - subDict = dictionaryGroupForLocale.getSubDict(subDictType); - dictTypesToCleanupForLocale.remove(subDictType); - } - subDicts.put(subDictType, subDict); - } - DictionaryGroup newDictionaryGroup = - new DictionaryGroup(newLocale, mainDict, account, subDicts); - - // Replace Dictionaries. - final DictionaryGroup oldDictionaryGroup; - synchronized (mLock) { - oldDictionaryGroup = mDictionaryGroup; - mDictionaryGroup = newDictionaryGroup; - if (hasAtLeastOneUninitializedMainDictionary()) { - asyncReloadUninitializedMainDictionaries(context, newLocale, listener); - } - } - if (listener != null) { - listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary()); - } - - // Clean up old dictionaries. - for (final Locale localeToCleanUp : existingDictionariesToCleanup.keySet()) { - final ArrayList<String> dictTypesToCleanUp = - existingDictionariesToCleanup.get(localeToCleanUp); - final DictionaryGroup dictionarySetToCleanup = - findDictionaryGroupWithLocale(oldDictionaryGroup, localeToCleanUp); - for (final String dictType : dictTypesToCleanUp) { - dictionarySetToCleanup.closeDict(dictType); - } - } - - if (mValidSpellingWordWriteCache != null) { - mValidSpellingWordWriteCache.evictAll(); - } - } - - private void asyncReloadUninitializedMainDictionaries(final Context context, - final Locale locale, final DictionaryInitializationListener listener) { - final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1); - mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary; - ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() { - @Override - public void run() { - doReloadUninitializedMainDictionaries( - context, locale, listener, latchForWaitingLoadingMainDictionary); - } - }); - } - - void doReloadUninitializedMainDictionaries(final Context context, final Locale locale, - final DictionaryInitializationListener listener, - final CountDownLatch latchForWaitingLoadingMainDictionary) { - final DictionaryGroup dictionaryGroup = - findDictionaryGroupWithLocale(mDictionaryGroup, locale); - if (null == dictionaryGroup) { - // This should never happen, but better safe than crashy - Log.w(TAG, "Expected a dictionary group for " + locale + " but none found"); - return; - } - final Dictionary mainDict = - DictionaryFactory.createMainDictionaryFromManager(context, locale); - synchronized (mLock) { - if (locale.equals(dictionaryGroup.mLocale)) { - dictionaryGroup.setMainDict(mainDict); - } else { - // Dictionary facilitator has been reset for another locale. - mainDict.close(); - } - } - if (listener != null) { - listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary()); - } - latchForWaitingLoadingMainDictionary.countDown(); - } - - @UsedForTesting - public void resetDictionariesForTesting(final Context context, final Locale locale, - final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles, - final Map<String, Map<String, String>> additionalDictAttributes, - @Nullable final String account) { - Dictionary mainDictionary = null; - final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>(); - - for (final String dictType : dictionaryTypes) { - if (dictType.equals(Dictionary.TYPE_MAIN)) { - mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context, - locale); - } else { - final File dictFile = dictionaryFiles.get(dictType); - final ExpandableBinaryDictionary dict = getSubDict( - dictType, context, locale, dictFile, "" /* dictNamePrefix */, account); - if (additionalDictAttributes.containsKey(dictType)) { - dict.clearAndFlushDictionaryWithAdditionalAttributes( - additionalDictAttributes.get(dictType)); - } - if (dict == null) { - throw new RuntimeException("Unknown dictionary type: " + dictType); - } - dict.reloadDictionaryIfRequired(); - dict.waitAllTasksForTests(); - subDicts.put(dictType, dict); - } - } - mDictionaryGroup = new DictionaryGroup(locale, mainDictionary, account, subDicts); - } - - public void closeDictionaries() { - final DictionaryGroup dictionaryGroupToClose; - synchronized (mLock) { - dictionaryGroupToClose = mDictionaryGroup; - mDictionaryGroup = new DictionaryGroup(); - } - for (final String dictType : ALL_DICTIONARY_TYPES) { - dictionaryGroupToClose.closeDict(dictType); - } - } - - @UsedForTesting - public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) { - return mDictionaryGroup.getSubDict(dictName); - } - - // The main dictionaries are loaded asynchronously. Don't cache the return value - // of these methods. - public boolean hasAtLeastOneInitializedMainDictionary() { - final Dictionary mainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN); - if (mainDict != null && mainDict.isInitialized()) { - return true; - } - return false; - } - - public boolean hasAtLeastOneUninitializedMainDictionary() { - final Dictionary mainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN); - if (mainDict == null || !mainDict.isInitialized()) { - return true; - } - return false; - } - - public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit) - throws InterruptedException { - mLatchForWaitingLoadingMainDictionaries.await(timeout, unit); - } - - @UsedForTesting - public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit) - throws InterruptedException { - waitForLoadingMainDictionaries(timeout, unit); - for (final ExpandableBinaryDictionary dict : mDictionaryGroup.mSubDictMap.values()) { - dict.waitAllTasksForTests(); - } - } - - public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized, - @Nonnull final NgramContext ngramContext, final long timeStampInSeconds, - final boolean blockPotentiallyOffensive) { - // Update the spelling cache before learning. Words that are not yet added to user history - // and appear in no other language model are not considered valid. - putWordIntoValidSpellingWordCache("addToUserHistory", suggestion); - - final String[] words = suggestion.split(Constants.WORD_SEPARATOR); - NgramContext ngramContextForCurrentWord = ngramContext; - for (int i = 0; i < words.length; i++) { - final String currentWord = words[i]; - final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false; - addWordToUserHistory(mDictionaryGroup, ngramContextForCurrentWord, currentWord, - wasCurrentWordAutoCapitalized, (int) timeStampInSeconds, - blockPotentiallyOffensive); - ngramContextForCurrentWord = - ngramContextForCurrentWord.getNextNgramContext(new WordInfo(currentWord)); - } - } - - private void putWordIntoValidSpellingWordCache( - @Nonnull final String caller, - @Nonnull final String originalWord) { - if (mValidSpellingWordWriteCache == null) { - return; - } - - final String lowerCaseWord = originalWord.toLowerCase(getLocale()); - final boolean lowerCaseValid = isValidSpellingWord(lowerCaseWord); - mValidSpellingWordWriteCache.put(lowerCaseWord, lowerCaseValid); - - final String capitalWord = - StringUtils.capitalizeFirstAndDowncaseRest(originalWord, getLocale()); - final boolean capitalValid; - if (lowerCaseValid) { - // The lower case form of the word is valid, so the upper case must be valid. - capitalValid = true; - } else { - capitalValid = isValidSpellingWord(capitalWord); - } - mValidSpellingWordWriteCache.put(capitalWord, capitalValid); - } - - private void addWordToUserHistory(final DictionaryGroup dictionaryGroup, - final NgramContext ngramContext, final String word, final boolean wasAutoCapitalized, - final int timeStampInSeconds, final boolean blockPotentiallyOffensive) { - final ExpandableBinaryDictionary userHistoryDictionary = - dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY); - if (userHistoryDictionary == null || !isForLocale(userHistoryDictionary.mLocale)) { - return; - } - final int maxFreq = getFrequency(word); - if (maxFreq == 0 && blockPotentiallyOffensive) { - return; - } - final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale); - final String secondWord; - if (wasAutoCapitalized) { - if (isValidSuggestionWord(word) && !isValidSuggestionWord(lowerCasedWord)) { - // If the word was auto-capitalized and exists only as a capitalized word in the - // dictionary, then we must not downcase it before registering it. For example, - // the name of the contacts in start-of-sentence position would come here with the - // wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version - // of that contact's name which would end up popping in suggestions. - secondWord = word; - } else { - // If however the word is not in the dictionary, or exists as a lower-case word - // only, then we consider that was a lower-case word that had been auto-capitalized. - secondWord = lowerCasedWord; - } - } else { - // HACK: We'd like to avoid adding the capitalized form of common words to the User - // History dictionary in order to avoid suggesting them until the dictionary - // consolidation is done. - // TODO: Remove this hack when ready. - final int lowerCaseFreqInMainDict = dictionaryGroup.hasDict(Dictionary.TYPE_MAIN, - null /* account */) ? - dictionaryGroup.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) : - Dictionary.NOT_A_PROBABILITY; - if (maxFreq < lowerCaseFreqInMainDict - && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) { - // Use lower cased word as the word can be a distracter of the popular word. - secondWord = lowerCasedWord; - } else { - secondWord = word; - } - } - // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid". - // We don't add words with 0-frequency (assuming they would be profanity etc.). - final boolean isValid = maxFreq > 0; - UserHistoryDictionary.addToDictionary(userHistoryDictionary, ngramContext, secondWord, - isValid, timeStampInSeconds); - } - - private void removeWord(final String dictName, final String word) { - final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName); - if (dictionary != null) { - dictionary.removeUnigramEntryDynamically(word); - } - } - - @Override - public void unlearnFromUserHistory(final String word, - @Nonnull final NgramContext ngramContext, final long timeStampInSeconds, - final int eventType) { - // TODO: Decide whether or not to remove the word on EVENT_BACKSPACE. - if (eventType != Constants.EVENT_BACKSPACE) { - removeWord(Dictionary.TYPE_USER_HISTORY, word); - } - - // Update the spelling cache after unlearning. Words that are removed from user history - // and appear in no other language model are not considered valid. - putWordIntoValidSpellingWordCache("unlearnFromUserHistory", word.toLowerCase()); - } - - // TODO: Revise the way to fusion suggestion results. - @Override - @Nonnull public SuggestionResults getSuggestionResults(ComposedData composedData, - NgramContext ngramContext, @Nonnull final Keyboard keyboard, - SettingsValuesForSuggestion settingsValuesForSuggestion, int sessionId, - int inputStyle) { - long proximityInfoHandle = keyboard.getProximityInfo().getNativeProximityInfo(); - final SuggestionResults suggestionResults = new SuggestionResults( - SuggestedWords.MAX_SUGGESTIONS, ngramContext.isBeginningOfSentenceContext(), - false /* firstSuggestionExceedsConfidenceThreshold */); - final float[] weightOfLangModelVsSpatialModel = - new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL }; - for (final String dictType : ALL_DICTIONARY_TYPES) { - final Dictionary dictionary = mDictionaryGroup.getDict(dictType); - if (null == dictionary) continue; - final float weightForLocale = composedData.mIsBatchMode - ? mDictionaryGroup.mWeightForGesturingInLocale - : mDictionaryGroup.mWeightForTypingInLocale; - final ArrayList<SuggestedWordInfo> dictionarySuggestions = - dictionary.getSuggestions(composedData, ngramContext, - proximityInfoHandle, settingsValuesForSuggestion, sessionId, - weightForLocale, weightOfLangModelVsSpatialModel); - if (null == dictionarySuggestions) continue; - suggestionResults.addAll(dictionarySuggestions); - if (null != suggestionResults.mRawSuggestions) { - suggestionResults.mRawSuggestions.addAll(dictionarySuggestions); - } - } - return suggestionResults; - } - - public boolean isValidSpellingWord(final String word) { - if (mValidSpellingWordReadCache != null) { - final Boolean cachedValue = mValidSpellingWordReadCache.get(word); - if (cachedValue != null) { - return cachedValue; - } - } - - return isValidWord(word, ALL_DICTIONARY_TYPES); - } - - public boolean isValidSuggestionWord(final String word) { - return isValidWord(word, ALL_DICTIONARY_TYPES); - } - - private boolean isValidWord(final String word, final String[] dictionariesToCheck) { - if (TextUtils.isEmpty(word)) { - return false; - } - if (mDictionaryGroup.mLocale == null) { - return false; - } - for (final String dictType : dictionariesToCheck) { - final Dictionary dictionary = mDictionaryGroup.getDict(dictType); - // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and - // would be immutable once it's finished initializing, but concretely a null test is - // probably good enough for the time being. - if (null == dictionary) continue; - if (dictionary.isValidWord(word)) { - return true; - } - } - return false; - } - - private int getFrequency(final String word) { - if (TextUtils.isEmpty(word)) { - return Dictionary.NOT_A_PROBABILITY; - } - int maxFreq = Dictionary.NOT_A_PROBABILITY; - for (final String dictType : ALL_DICTIONARY_TYPES) { - final Dictionary dictionary = mDictionaryGroup.getDict(dictType); - if (dictionary == null) continue; - final int tempFreq = dictionary.getFrequency(word); - if (tempFreq >= maxFreq) { - maxFreq = tempFreq; - } - } - return maxFreq; - } - - private boolean clearSubDictionary(final String dictName) { - final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName); - if (dictionary == null) { - return false; - } - dictionary.clear(); - return true; - } - - @Override - public boolean clearUserHistoryDictionary(final Context context) { - return clearSubDictionary(Dictionary.TYPE_USER_HISTORY); - } - - @Override - public void dumpDictionaryForDebug(final String dictName) { - final ExpandableBinaryDictionary dictToDump = mDictionaryGroup.getSubDict(dictName); - if (dictToDump == null) { - Log.e(TAG, "Cannot dump " + dictName + ". " - + "The dictionary is not being used for suggestion or cannot be dumped."); - return; - } - dictToDump.dumpAllWordsForDebug(); - } - - @Override - @Nonnull public List<DictionaryStats> getDictionaryStats(final Context context) { - final ArrayList<DictionaryStats> statsOfEnabledSubDicts = new ArrayList<>(); - for (final String dictType : DYNAMIC_DICTIONARY_TYPES) { - final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictType); - if (dictionary == null) continue; - statsOfEnabledSubDicts.add(dictionary.getDictionaryStats()); - } - return statsOfEnabledSubDicts; - } - - @Override - public String dump(final Context context) { - return ""; - } -} diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java deleted file mode 100644 index cbaf6ea4e..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin; - -import java.util.Locale; -import java.util.concurrent.TimeUnit; - -import android.content.Context; -import android.util.Log; - -/** - * Cache for dictionary facilitators of multiple locales. - * This class automatically creates and releases up to 3 facilitator instances using LRU policy. - */ -public class DictionaryFacilitatorLruCache { - private static final String TAG = "DictionaryFacilitatorLruCache"; - private static final int WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS = 1000; - private static final int MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT = 5; - - private final Context mContext; - private final String mDictionaryNamePrefix; - private final Object mLock = new Object(); - private final DictionaryFacilitator mDictionaryFacilitator; - private boolean mUseContactsDictionary; - private Locale mLocale; - - public DictionaryFacilitatorLruCache(final Context context, final String dictionaryNamePrefix) { - mContext = context; - mDictionaryNamePrefix = dictionaryNamePrefix; - mDictionaryFacilitator = DictionaryFacilitatorProvider.getDictionaryFacilitator( - true /* isNeededForSpellChecking */); - } - - private static void waitForLoadingMainDictionary( - final DictionaryFacilitator dictionaryFacilitator) { - for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) { - try { - dictionaryFacilitator.waitForLoadingMainDictionaries( - WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS); - return; - } catch (final InterruptedException e) { - Log.i(TAG, "Interrupted during waiting for loading main dictionary.", e); - if (i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT - 1) { - Log.i(TAG, "Retry", e); - } else { - Log.w(TAG, "Give up retrying. Retried " - + MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT + " times.", e); - } - } - } - } - - private void resetDictionariesForLocaleLocked() { - // Nothing to do if the locale is null. This would be the case before any get() calls. - if (mLocale != null) { - // Note: Given that personalized dictionaries are not used here; we can pass null account. - mDictionaryFacilitator.resetDictionaries(mContext, mLocale, - mUseContactsDictionary, false /* usePersonalizedDicts */, - false /* forceReloadMainDictionary */, null /* account */, - mDictionaryNamePrefix, null /* listener */); - } - } - - public void setUseContactsDictionary(final boolean useContactsDictionary) { - synchronized (mLock) { - if (mUseContactsDictionary == useContactsDictionary) { - // The value has not been changed. - return; - } - mUseContactsDictionary = useContactsDictionary; - resetDictionariesForLocaleLocked(); - waitForLoadingMainDictionary(mDictionaryFacilitator); - } - } - - public DictionaryFacilitator get(final Locale locale) { - synchronized (mLock) { - if (!mDictionaryFacilitator.isForLocale(locale)) { - mLocale = locale; - resetDictionariesForLocaleLocked(); - } - waitForLoadingMainDictionary(mDictionaryFacilitator); - return mDictionaryFacilitator; - } - } - - public void closeDictionaries() { - synchronized (mLock) { - mDictionaryFacilitator.closeDictionaries(); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorProvider.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorProvider.java deleted file mode 100644 index a48b41fa7..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorProvider.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin; - -/** - * Factory for instantiating DictionaryFacilitator objects. - */ -public class DictionaryFacilitatorProvider { - public static DictionaryFacilitator getDictionaryFacilitator(boolean isNeededForSpellChecking) { - return new DictionaryFacilitatorImpl(); - } -} diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java deleted file mode 100644 index 5dd02bd1c..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin; - -import android.content.ContentProviderClient; -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.util.Log; - -import com.android.inputmethod.latin.utils.DictionaryInfoUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.Locale; - -/** - * Factory for dictionary instances. - */ -public final class DictionaryFactory { - private static final String TAG = DictionaryFactory.class.getSimpleName(); - - /** - * Initializes a main dictionary collection from a dictionary pack, with explicit flags. - * - * This searches for a content provider providing a dictionary pack for the specified - * locale. If none is found, it falls back to the built-in dictionary - if any. - * @param context application context for reading resources - * @param locale the locale for which to create the dictionary - * @return an initialized instance of DictionaryCollection - */ - public static DictionaryCollection createMainDictionaryFromManager(final Context context, - final Locale locale) { - if (null == locale) { - Log.e(TAG, "No locale defined for dictionary"); - return new DictionaryCollection(Dictionary.TYPE_MAIN, locale, - createReadOnlyBinaryDictionary(context, locale)); - } - - final LinkedList<Dictionary> dictList = new LinkedList<>(); - final ArrayList<AssetFileAddress> assetFileList = - BinaryDictionaryGetter.getDictionaryFiles(locale, context, true); - if (null != assetFileList) { - for (final AssetFileAddress f : assetFileList) { - final ReadOnlyBinaryDictionary readOnlyBinaryDictionary = - new ReadOnlyBinaryDictionary(f.mFilename, f.mOffset, f.mLength, - false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN); - if (readOnlyBinaryDictionary.isValidDictionary()) { - dictList.add(readOnlyBinaryDictionary); - } else { - readOnlyBinaryDictionary.close(); - // Prevent this dictionary to do any further harm. - killDictionary(context, f); - } - } - } - - // If the list is empty, that means we should not use any dictionary (for example, the user - // explicitly disabled the main dictionary), so the following is okay. dictList is never - // null, but if for some reason it is, DictionaryCollection handles it gracefully. - return new DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList); - } - - /** - * Kills a dictionary so that it is never used again, if possible. - * @param context The context to contact the dictionary provider, if possible. - * @param f A file address to the dictionary to kill. - */ - public static void killDictionary(final Context context, final AssetFileAddress f) { - if (f.pointsToPhysicalFile()) { - f.deleteUnderlyingFile(); - // Warn the dictionary provider if the dictionary came from there. - final ContentProviderClient providerClient; - try { - providerClient = context.getContentResolver().acquireContentProviderClient( - BinaryDictionaryFileDumper.getProviderUriBuilder("").build()); - } catch (final SecurityException e) { - Log.e(TAG, "No permission to communicate with the dictionary provider", e); - return; - } - if (null == providerClient) { - Log.e(TAG, "Can't establish communication with the dictionary provider"); - return; - } - final String wordlistId = - DictionaryInfoUtils.getWordListIdFromFileName(new File(f.mFilename).getName()); - // TODO: this is a reasonable last resort, but it is suboptimal. - // The following will remove the entry for this dictionary with the dictionary - // provider. When the metadata is downloaded again, we will try downloading it - // again. - // However, in the practice that will mean the user will find themselves without - // the new dictionary. That's fine for languages where it's included in the APK, - // but for other languages it will leave the user without a dictionary at all until - // the next update, which may be a few days away. - // Ideally, we would trigger a new download right away, and use increasing retry - // delays for this particular id/version combination. - // Then again, this is expected to only ever happen in case of human mistake. If - // the wrong file is on the server, the following is still doing the right thing. - // If it's a file left over from the last version however, it's not great. - BinaryDictionaryFileDumper.reportBrokenFileToDictionaryProvider( - providerClient, - context.getString(R.string.dictionary_pack_client_id), - wordlistId); - } - } - - /** - * Initializes a read-only binary dictionary from a raw resource file - * @param context application context for reading resources - * @param locale the locale to use for the resource - * @return an initialized instance of ReadOnlyBinaryDictionary - */ - private static ReadOnlyBinaryDictionary createReadOnlyBinaryDictionary(final Context context, - final Locale locale) { - AssetFileDescriptor afd = null; - try { - final int resId = DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale( - context.getResources(), locale); - if (0 == resId) return null; - afd = context.getResources().openRawResourceFd(resId); - if (afd == null) { - Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); - return null; - } - final String sourceDir = context.getApplicationInfo().sourceDir; - final File packagePath = new File(sourceDir); - // TODO: Come up with a way to handle a directory. - if (!packagePath.isFile()) { - Log.e(TAG, "sourceDir is not a file: " + sourceDir); - return null; - } - return new ReadOnlyBinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(), - false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN); - } catch (android.content.res.Resources.NotFoundException e) { - Log.e(TAG, "Could not find the resource"); - return null; - } finally { - if (null != afd) { - try { - afd.close(); - } catch (java.io.IOException e) { - /* IOException on close ? What am I supposed to do ? */ - } - } - } - } -} diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java deleted file mode 100644 index 2dcfdb0b7..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin; - -import com.android.inputmethod.dictionarypack.DictionaryPackConstants; -import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ProviderInfo; -import android.net.Uri; -import android.util.Log; - -/** - * Receives broadcasts pertaining to dictionary management and takes the appropriate action. - * - * This object receives three types of broadcasts. - * - Package installed/added. When a dictionary provider application is added or removed, we - * need to query the dictionaries. - * - New dictionary broadcast. The dictionary provider broadcasts new dictionary availability. When - * this happens, we need to re-query the dictionaries. - * - Unknown client. If the dictionary provider is in urgent need of data about some client that - * it does not know, it sends this broadcast. When we receive this, we need to tell the dictionary - * provider about ourselves. This happens when the settings for the dictionary pack are accessed, - * but Latin IME never got a chance to register itself. - */ -public final class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver { - private static final String TAG = DictionaryPackInstallBroadcastReceiver.class.getSimpleName(); - - final LatinIME mService; - - public DictionaryPackInstallBroadcastReceiver() { - // This empty constructor is necessary for the system to instantiate this receiver. - // This happens when the dictionary pack says it can't find a record for our client, - // which happens when the dictionary pack settings are called before the keyboard - // was ever started once. - Log.i(TAG, "Latin IME dictionary broadcast receiver instantiated from the framework."); - mService = null; - } - - public DictionaryPackInstallBroadcastReceiver(final LatinIME service) { - mService = service; - } - - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - final PackageManager manager = context.getPackageManager(); - - // We need to reread the dictionary if a new dictionary package is installed. - if (action.equals(Intent.ACTION_PACKAGE_ADDED)) { - if (null == mService) { - Log.e(TAG, "Called with intent " + action + " but we don't know the service: this " - + "should never happen"); - return; - } - final Uri packageUri = intent.getData(); - if (null == packageUri) return; // No package name : we can't do anything - final String packageName = packageUri.getSchemeSpecificPart(); - if (null == packageName) return; - // TODO: do this in a more appropriate place - TargetPackageInfoGetterTask.removeCachedPackageInfo(packageName); - final PackageInfo packageInfo; - try { - packageInfo = manager.getPackageInfo(packageName, PackageManager.GET_PROVIDERS); - } catch (android.content.pm.PackageManager.NameNotFoundException e) { - return; // No package info : we can't do anything - } - final ProviderInfo[] providers = packageInfo.providers; - if (null == providers) return; // No providers : it is not a dictionary. - - // Search for some dictionary pack in the just-installed package. If found, reread. - for (ProviderInfo info : providers) { - if (DictionaryPackConstants.AUTHORITY.equals(info.authority)) { - mService.resetSuggestMainDict(); - return; - } - } - // If we come here none of the authorities matched the one we searched for. - // We can exit safely. - return; - } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) - && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { - if (null == mService) { - Log.e(TAG, "Called with intent " + action + " but we don't know the service: this " - + "should never happen"); - return; - } - // When the dictionary package is removed, we need to reread dictionary (to use the - // next-priority one, or stop using a dictionary at all if this was the only one, - // since this is the user request). - // If we are replacing the package, we will receive ADDED right away so no need to - // remove the dictionary at the moment, since we will do it when we receive the - // ADDED broadcast. - - // TODO: Only reload dictionary on REMOVED when the removed package is the one we - // read dictionary from? - mService.resetSuggestMainDict(); - } else if (action.equals(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)) { - if (null == mService) { - Log.e(TAG, "Called with intent " + action + " but we don't know the service: this " - + "should never happen"); - return; - } - mService.resetSuggestMainDict(); - } else if (action.equals(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT)) { - if (null != mService) { - // Careful! This is returning if the service is NOT null. This is because we - // should come here instantiated by the framework in reaction to a broadcast of - // the above action, so we should gave gone through the no-args constructor. - Log.e(TAG, "Called with intent " + action + " but we have a reference to the " - + "service: this should never happen"); - return; - } - // The dictionary provider does not know about some client. We check that it's really - // us that it needs to know about, and if it's the case, we register with the provider. - final String wantedClientId = - intent.getStringExtra(DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA); - final String myClientId = context.getString(R.string.dictionary_pack_client_id); - if (!wantedClientId.equals(myClientId)) return; // Not for us - BinaryDictionaryFileDumper.initializeClientRecordHelper(context, myClientId); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/DictionaryStats.java b/java/src/com/android/inputmethod/latin/DictionaryStats.java deleted file mode 100644 index b65d25bb1..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryStats.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin; - -import java.io.File; -import java.math.BigDecimal; -import java.util.Locale; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class DictionaryStats { - public static final int NOT_AN_ENTRY_COUNT = -1; - - public final Locale mLocale; - public final String mDictType; - public final String mDictFileName; - public final long mDictFileSize; - public final int mContentVersion; - public final int mWordCount; - - public DictionaryStats( - @Nonnull final Locale locale, - @Nonnull final String dictType, - @Nullable final String dictFileName, - @Nullable final File dictFile, - final int contentVersion) { - mLocale = locale; - mDictType = dictType; - mDictFileSize = (dictFile == null || !dictFile.exists()) ? 0 : dictFile.length(); - mDictFileName = dictFileName; - mContentVersion = contentVersion; - mWordCount = -1; - } - - public DictionaryStats( - @Nonnull final Locale locale, - @Nonnull final String dictType, - final int wordCount) { - mLocale = locale; - mDictType = dictType; - mDictFileSize = wordCount; - mDictFileName = null; - mContentVersion = 0; - mWordCount = wordCount; - } - - public String getFileSizeString() { - BigDecimal bytes = new BigDecimal(mDictFileSize); - BigDecimal kb = bytes.divide(new BigDecimal(1024), 2, BigDecimal.ROUND_HALF_UP); - if (kb.longValue() == 0) { - return bytes.toString() + " bytes"; - } - BigDecimal mb = kb.divide(new BigDecimal(1024), 2, BigDecimal.ROUND_HALF_UP); - if (mb.longValue() == 0) { - return kb.toString() + " kb"; - } - return mb.toString() + " Mb"; - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(mDictType); - if (mDictType.equals(Dictionary.TYPE_MAIN)) { - builder.append(" ("); - builder.append(mContentVersion); - builder.append(")"); - } - builder.append(": "); - if (mWordCount > -1) { - builder.append(mWordCount); - builder.append(" words"); - } else { - builder.append(mDictFileName); - builder.append(" / "); - builder.append(getFileSizeString()); - } - return builder.toString(); - } - - public static String toString(final Iterable<DictionaryStats> stats) { - final StringBuilder builder = new StringBuilder("LM Stats"); - for (DictionaryStats stat : stats) { - builder.append("\n "); - builder.append(stat.toString()); - } - return builder.toString(); - } -} diff --git a/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java b/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java deleted file mode 100644 index 8924e0a3d..000000000 --- a/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2014, 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 com.android.inputmethod.latin; - -import android.content.res.Resources; -import android.util.Log; -import android.util.Pair; -import android.view.KeyEvent; - -import com.android.inputmethod.keyboard.KeyboardSwitcher; -import com.android.inputmethod.latin.settings.Settings; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.annotation.Nonnull; - -/** - * A class for detecting Emoji-Alt physical key. - */ -final class EmojiAltPhysicalKeyDetector { - private static final String TAG = "EmojiAltPhysicalKeyDetector"; - private static final boolean DEBUG = false; - - private List<EmojiHotKeys> mHotKeysList; - - private static class HotKeySet extends HashSet<Pair<Integer, Integer>> { }; - - private abstract class EmojiHotKeys { - private final String mName; - private final HotKeySet mKeySet; - - boolean mCanFire; - int mMetaState; - - public EmojiHotKeys(final String name, HotKeySet keySet) { - mName = name; - mKeySet = keySet; - mCanFire = false; - } - - public void onKeyDown(@Nonnull final KeyEvent keyEvent) { - if (DEBUG) { - Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - considering " + keyEvent); - } - - final Pair<Integer, Integer> key = - Pair.create(keyEvent.getKeyCode(), keyEvent.getMetaState()); - if (mKeySet.contains(key)) { - if (DEBUG) { - Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - enabling action"); - } - mCanFire = true; - mMetaState = keyEvent.getMetaState(); - } else if (mCanFire) { - if (DEBUG) { - Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - disabling action"); - } - mCanFire = false; - } - } - - public void onKeyUp(@Nonnull final KeyEvent keyEvent) { - if (DEBUG) { - Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - considering " + keyEvent); - } - - final int keyCode = keyEvent.getKeyCode(); - int metaState = keyEvent.getMetaState(); - if (KeyEvent.isModifierKey(keyCode)) { - // Try restoring meta stat in case the released key was a modifier. - // I am sure one can come up with scenarios to break this, but it - // seems to work well in practice. - metaState |= mMetaState; - } - - final Pair<Integer, Integer> key = Pair.create(keyCode, metaState); - if (mKeySet.contains(key)) { - if (mCanFire) { - if (!keyEvent.isCanceled()) { - if (DEBUG) { - Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - firing action"); - } - action(); - } else { - // This key up event was a part of key combinations and - // should be ignored. - if (DEBUG) { - Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - canceled, ignoring action"); - } - } - mCanFire = false; - } - } - - if (mCanFire) { - if (DEBUG) { - Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - disabling action"); - } - mCanFire = false; - } - } - - protected abstract void action(); - } - - public EmojiAltPhysicalKeyDetector(@Nonnull final Resources resources) { - mHotKeysList = new ArrayList<EmojiHotKeys>(); - - final HotKeySet emojiSwitchSet = parseHotKeys( - resources, R.array.keyboard_switcher_emoji); - final EmojiHotKeys emojiHotKeys = new EmojiHotKeys("emoji", emojiSwitchSet) { - @Override - protected void action() { - final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); - switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.EMOJI); - } - }; - mHotKeysList.add(emojiHotKeys); - - final HotKeySet symbolsSwitchSet = parseHotKeys( - resources, R.array.keyboard_switcher_symbols_shifted); - final EmojiHotKeys symbolsHotKeys = new EmojiHotKeys("symbols", symbolsSwitchSet) { - @Override - protected void action() { - final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); - switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.SYMBOLS_SHIFTED); - } - }; - mHotKeysList.add(symbolsHotKeys); - } - - public void onKeyDown(@Nonnull final KeyEvent keyEvent) { - if (DEBUG) { - Log.d(TAG, "onKeyDown(): " + keyEvent); - } - - if (shouldProcessEvent(keyEvent)) { - for (EmojiHotKeys hotKeys : mHotKeysList) { - hotKeys.onKeyDown(keyEvent); - } - } - } - - public void onKeyUp(@Nonnull final KeyEvent keyEvent) { - if (DEBUG) { - Log.d(TAG, "onKeyUp(): " + keyEvent); - } - - if (shouldProcessEvent(keyEvent)) { - for (EmojiHotKeys hotKeys : mHotKeysList) { - hotKeys.onKeyUp(keyEvent); - } - } - } - - private static boolean shouldProcessEvent(@Nonnull final KeyEvent keyEvent) { - if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) { - // The feature is disabled. - if (DEBUG) { - Log.d(TAG, "shouldProcessEvent(): Disabled"); - } - return false; - } - - return true; - } - - private static HotKeySet parseHotKeys( - @Nonnull final Resources resources, final int resourceId) { - final HotKeySet keySet = new HotKeySet(); - final String name = resources.getResourceEntryName(resourceId); - final String[] values = resources.getStringArray(resourceId); - for (int i = 0; values != null && i < values.length; i++) { - String[] valuePair = values[i].split(","); - if (valuePair.length != 2) { - Log.w(TAG, "Expected 2 integers in " + name + "[" + i + "] : " + values[i]); - } - try { - final Integer keyCode = Integer.parseInt(valuePair[0]); - final Integer metaState = Integer.parseInt(valuePair[1]); - final Pair<Integer, Integer> key = Pair.create( - keyCode, KeyEvent.normalizeMetaState(metaState)); - keySet.add(key); - } catch (NumberFormatException e) { - Log.w(TAG, "Failed to parse " + name + "[" + i + "] : " + values[i], e); - } - } - return keySet; - } -} diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java deleted file mode 100644 index 907095746..000000000 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ /dev/null @@ -1,757 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin; - -import android.content.Context; -import android.util.Log; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.ComposedData; -import com.android.inputmethod.latin.common.FileUtils; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; -import com.android.inputmethod.latin.makedict.DictionaryHeader; -import com.android.inputmethod.latin.makedict.FormatSpec; -import com.android.inputmethod.latin.makedict.UnsupportedFormatException; -import com.android.inputmethod.latin.makedict.WordProperty; -import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; -import com.android.inputmethod.latin.utils.AsyncResultHolder; -import com.android.inputmethod.latin.utils.CombinedFormatUtils; -import com.android.inputmethod.latin.utils.ExecutorUtils; -import com.android.inputmethod.latin.utils.WordInputEventForPersonalization; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Abstract base class for an expandable dictionary that can be created and updated dynamically - * during runtime. When updated it automatically generates a new binary dictionary to handle future - * queries in native code. This binary dictionary is written to internal storage. - * - * A class that extends this abstract class must have a static factory method named - * getDictionary(Context context, Locale locale, File dictFile, String dictNamePrefix) - */ -abstract public class ExpandableBinaryDictionary extends Dictionary { - private static final boolean DEBUG = false; - - /** Used for Log actions from this class */ - private static final String TAG = ExpandableBinaryDictionary.class.getSimpleName(); - - /** Whether to print debug output to log */ - private static final boolean DBG_STRESS_TEST = false; - - private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100; - - /** - * The maximum length of a word in this dictionary. - */ - protected static final int MAX_WORD_LENGTH = - DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH; - - private static final int DICTIONARY_FORMAT_VERSION = FormatSpec.VERSION4; - - private static final WordProperty[] DEFAULT_WORD_PROPERTIES_FOR_SYNC = - new WordProperty[0] /* default */; - - /** The application context. */ - protected final Context mContext; - - /** - * The binary dictionary generated dynamically from the fusion dictionary. This is used to - * answer unigram and bigram queries. - */ - private BinaryDictionary mBinaryDictionary; - - /** - * The name of this dictionary, used as a part of the filename for storing the binary - * dictionary. - */ - private final String mDictName; - - /** Dictionary file */ - private final File mDictFile; - - /** Indicates whether a task for reloading the dictionary has been scheduled. */ - private final AtomicBoolean mIsReloading; - - /** Indicates whether the current dictionary needs to be recreated. */ - private boolean mNeedsToRecreate; - - private final ReentrantReadWriteLock mLock; - - private Map<String, String> mAdditionalAttributeMap = null; - - /* A extension for a binary dictionary file. */ - protected static final String DICT_FILE_EXTENSION = ".dict"; - - /** - * Abstract method for loading initial contents of a given dictionary. - */ - protected abstract void loadInitialContentsLocked(); - - static boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) { - return formatVersion == FormatSpec.VERSION4; - } - - private static boolean needsToMigrateDictionary(final int formatVersion) { - // When we bump up the dictionary format version, the old version should be added to here - // for supporting migration. Note that native code has to support reading such formats. - return formatVersion == FormatSpec.VERSION402; - } - - public boolean isValidDictionaryLocked() { - return mBinaryDictionary.isValidDictionary(); - } - - /** - * Creates a new expandable binary dictionary. - * - * @param context The application context of the parent. - * @param dictName The name of the dictionary. Multiple instances with the same - * name is supported. - * @param locale the dictionary locale. - * @param dictType the dictionary type, as a human-readable string - * @param dictFile dictionary file path. if null, use default dictionary path based on - * dictionary type. - */ - public ExpandableBinaryDictionary(final Context context, final String dictName, - final Locale locale, final String dictType, final File dictFile) { - super(dictType, locale); - mDictName = dictName; - mContext = context; - mDictFile = getDictFile(context, dictName, dictFile); - mBinaryDictionary = null; - mIsReloading = new AtomicBoolean(); - mNeedsToRecreate = false; - mLock = new ReentrantReadWriteLock(); - } - - public static File getDictFile(final Context context, final String dictName, - final File dictFile) { - return (dictFile != null) ? dictFile - : new File(context.getFilesDir(), dictName + DICT_FILE_EXTENSION); - } - - public static String getDictName(final String name, final Locale locale, - final File dictFile) { - return dictFile != null ? dictFile.getName() : name + "." + locale.toString(); - } - - private void asyncExecuteTaskWithWriteLock(final Runnable task) { - asyncExecuteTaskWithLock(mLock.writeLock(), task); - } - - private static void asyncExecuteTaskWithLock(final Lock lock, final Runnable task) { - ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() { - @Override - public void run() { - lock.lock(); - try { - task.run(); - } finally { - lock.unlock(); - } - } - }); - } - - @Nullable - BinaryDictionary getBinaryDictionary() { - return mBinaryDictionary; - } - - void closeBinaryDictionary() { - if (mBinaryDictionary != null) { - mBinaryDictionary.close(); - mBinaryDictionary = null; - } - } - - /** - * Closes and cleans up the binary dictionary. - */ - @Override - public void close() { - asyncExecuteTaskWithWriteLock(new Runnable() { - @Override - public void run() { - closeBinaryDictionary(); - } - }); - } - - protected Map<String, String> getHeaderAttributeMap() { - HashMap<String, String> attributeMap = new HashMap<>(); - if (mAdditionalAttributeMap != null) { - attributeMap.putAll(mAdditionalAttributeMap); - } - attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, mDictName); - attributeMap.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, mLocale.toString()); - attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY, - String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))); - return attributeMap; - } - - private void removeBinaryDictionary() { - asyncExecuteTaskWithWriteLock(new Runnable() { - @Override - public void run() { - removeBinaryDictionaryLocked(); - } - }); - } - - void removeBinaryDictionaryLocked() { - closeBinaryDictionary(); - if (mDictFile.exists() && !FileUtils.deleteRecursively(mDictFile)) { - Log.e(TAG, "Can't remove a file: " + mDictFile.getName()); - } - } - - private void openBinaryDictionaryLocked() { - mBinaryDictionary = new BinaryDictionary( - mDictFile.getAbsolutePath(), 0 /* offset */, mDictFile.length(), - true /* useFullEditDistance */, mLocale, mDictType, true /* isUpdatable */); - } - - void createOnMemoryBinaryDictionaryLocked() { - mBinaryDictionary = new BinaryDictionary( - mDictFile.getAbsolutePath(), true /* useFullEditDistance */, mLocale, mDictType, - DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap()); - } - - public void clear() { - asyncExecuteTaskWithWriteLock(new Runnable() { - @Override - public void run() { - removeBinaryDictionaryLocked(); - createOnMemoryBinaryDictionaryLocked(); - } - }); - } - - /** - * Check whether GC is needed and run GC if required. - */ - public void runGCIfRequired(final boolean mindsBlockByGC) { - asyncExecuteTaskWithWriteLock(new Runnable() { - @Override - public void run() { - if (getBinaryDictionary() == null) { - return; - } - runGCIfRequiredLocked(mindsBlockByGC); - } - }); - } - - protected void runGCIfRequiredLocked(final boolean mindsBlockByGC) { - if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) { - mBinaryDictionary.flushWithGC(); - } - } - - private void updateDictionaryWithWriteLock(@Nonnull final Runnable updateTask) { - reloadDictionaryIfRequired(); - final Runnable task = new Runnable() { - @Override - public void run() { - if (getBinaryDictionary() == null) { - return; - } - runGCIfRequiredLocked(true /* mindsBlockByGC */); - updateTask.run(); - } - }; - asyncExecuteTaskWithWriteLock(task); - } - - /** - * Adds unigram information of a word to the dictionary. May overwrite an existing entry. - */ - public void addUnigramEntry(final String word, final int frequency, - final boolean isNotAWord, final boolean isPossiblyOffensive, final int timestamp) { - updateDictionaryWithWriteLock(new Runnable() { - @Override - public void run() { - addUnigramLocked(word, frequency, isNotAWord, isPossiblyOffensive, timestamp); - } - }); - } - - protected void addUnigramLocked(final String word, final int frequency, - final boolean isNotAWord, final boolean isPossiblyOffensive, final int timestamp) { - if (!mBinaryDictionary.addUnigramEntry(word, frequency, - false /* isBeginningOfSentence */, isNotAWord, isPossiblyOffensive, timestamp)) { - Log.e(TAG, "Cannot add unigram entry. word: " + word); - } - } - - /** - * Dynamically remove the unigram entry from the dictionary. - */ - public void removeUnigramEntryDynamically(final String word) { - reloadDictionaryIfRequired(); - asyncExecuteTaskWithWriteLock(new Runnable() { - @Override - public void run() { - final BinaryDictionary binaryDictionary = getBinaryDictionary(); - if (binaryDictionary == null) { - return; - } - runGCIfRequiredLocked(true /* mindsBlockByGC */); - if (!binaryDictionary.removeUnigramEntry(word)) { - if (DEBUG) { - Log.i(TAG, "Cannot remove unigram entry: " + word); - } - } - } - }); - } - - /** - * Adds n-gram information of a word to the dictionary. May overwrite an existing entry. - */ - public void addNgramEntry(@Nonnull final NgramContext ngramContext, final String word, - final int frequency, final int timestamp) { - reloadDictionaryIfRequired(); - asyncExecuteTaskWithWriteLock(new Runnable() { - @Override - public void run() { - if (getBinaryDictionary() == null) { - return; - } - runGCIfRequiredLocked(true /* mindsBlockByGC */); - addNgramEntryLocked(ngramContext, word, frequency, timestamp); - } - }); - } - - protected void addNgramEntryLocked(@Nonnull final NgramContext ngramContext, final String word, - final int frequency, final int timestamp) { - if (!mBinaryDictionary.addNgramEntry(ngramContext, word, frequency, timestamp)) { - if (DEBUG) { - Log.i(TAG, "Cannot add n-gram entry."); - Log.i(TAG, " NgramContext: " + ngramContext + ", word: " + word); - } - } - } - - /** - * Update dictionary for the word with the ngramContext. - */ - public void updateEntriesForWord(@Nonnull final NgramContext ngramContext, - final String word, final boolean isValidWord, final int count, final int timestamp) { - updateDictionaryWithWriteLock(new Runnable() { - @Override - public void run() { - final BinaryDictionary binaryDictionary = getBinaryDictionary(); - if (binaryDictionary == null) { - return; - } - if (!binaryDictionary.updateEntriesForWordWithNgramContext(ngramContext, word, - isValidWord, count, timestamp)) { - if (DEBUG) { - Log.e(TAG, "Cannot update counter. word: " + word - + " context: " + ngramContext.toString()); - } - } - } - }); - } - - /** - * Used by Sketch. - * {@see https://cs.corp.google.com/#android/vendor/unbundled_google/packages/LatinIMEGoogle/tools/sketch/ime-simulator/src/com/android/inputmethod/sketch/imesimulator/ImeSimulator.java&q=updateEntriesForInputEventsCallback&l=286} - */ - @UsedForTesting - public interface UpdateEntriesForInputEventsCallback { - public void onFinished(); - } - - /** - * Dynamically update entries according to input events. - * - * Used by Sketch. - * {@see https://cs.corp.google.com/#android/vendor/unbundled_google/packages/LatinIMEGoogle/tools/sketch/ime-simulator/src/com/android/inputmethod/sketch/imesimulator/ImeSimulator.java&q=updateEntriesForInputEventsCallback&l=286} - */ - @UsedForTesting - public void updateEntriesForInputEvents( - @Nonnull final ArrayList<WordInputEventForPersonalization> inputEvents, - final UpdateEntriesForInputEventsCallback callback) { - reloadDictionaryIfRequired(); - asyncExecuteTaskWithWriteLock(new Runnable() { - @Override - public void run() { - try { - final BinaryDictionary binaryDictionary = getBinaryDictionary(); - if (binaryDictionary == null) { - return; - } - binaryDictionary.updateEntriesForInputEvents( - inputEvents.toArray( - new WordInputEventForPersonalization[inputEvents.size()])); - } finally { - if (callback != null) { - callback.onFinished(); - } - } - } - }); - } - - @Override - public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, - final NgramContext ngramContext, final long proximityInfoHandle, - final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId, - final float weightForLocale, final float[] inOutWeightOfLangModelVsSpatialModel) { - reloadDictionaryIfRequired(); - boolean lockAcquired = false; - try { - lockAcquired = mLock.readLock().tryLock( - TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS, TimeUnit.MILLISECONDS); - if (lockAcquired) { - if (mBinaryDictionary == null) { - return null; - } - final ArrayList<SuggestedWordInfo> suggestions = - mBinaryDictionary.getSuggestions(composedData, ngramContext, - proximityInfoHandle, settingsValuesForSuggestion, sessionId, - weightForLocale, inOutWeightOfLangModelVsSpatialModel); - if (mBinaryDictionary.isCorrupted()) { - Log.i(TAG, "Dictionary (" + mDictName +") is corrupted. " - + "Remove and regenerate it."); - removeBinaryDictionary(); - } - return suggestions; - } - } catch (final InterruptedException e) { - Log.e(TAG, "Interrupted tryLock() in getSuggestionsWithSessionId().", e); - } finally { - if (lockAcquired) { - mLock.readLock().unlock(); - } - } - return null; - } - - @Override - public boolean isInDictionary(final String word) { - reloadDictionaryIfRequired(); - boolean lockAcquired = false; - try { - lockAcquired = mLock.readLock().tryLock( - TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS, TimeUnit.MILLISECONDS); - if (lockAcquired) { - if (mBinaryDictionary == null) { - return false; - } - return isInDictionaryLocked(word); - } - } catch (final InterruptedException e) { - Log.e(TAG, "Interrupted tryLock() in isInDictionary().", e); - } finally { - if (lockAcquired) { - mLock.readLock().unlock(); - } - } - return false; - } - - protected boolean isInDictionaryLocked(final String word) { - if (mBinaryDictionary == null) return false; - return mBinaryDictionary.isInDictionary(word); - } - - @Override - public int getMaxFrequencyOfExactMatches(final String word) { - reloadDictionaryIfRequired(); - boolean lockAcquired = false; - try { - lockAcquired = mLock.readLock().tryLock( - TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS, TimeUnit.MILLISECONDS); - if (lockAcquired) { - if (mBinaryDictionary == null) { - return NOT_A_PROBABILITY; - } - return mBinaryDictionary.getMaxFrequencyOfExactMatches(word); - } - } catch (final InterruptedException e) { - Log.e(TAG, "Interrupted tryLock() in getMaxFrequencyOfExactMatches().", e); - } finally { - if (lockAcquired) { - mLock.readLock().unlock(); - } - } - return NOT_A_PROBABILITY; - } - - - /** - * Loads the current binary dictionary from internal storage. Assumes the dictionary file - * exists. - */ - void loadBinaryDictionaryLocked() { - if (DBG_STRESS_TEST) { - // Test if this class does not cause problems when it takes long time to load binary - // dictionary. - try { - Log.w(TAG, "Start stress in loading: " + mDictName); - Thread.sleep(15000); - Log.w(TAG, "End stress in loading"); - } catch (InterruptedException e) { - Log.w("Interrupted while loading: " + mDictName, e); - } - } - final BinaryDictionary oldBinaryDictionary = mBinaryDictionary; - openBinaryDictionaryLocked(); - if (oldBinaryDictionary != null) { - oldBinaryDictionary.close(); - } - if (mBinaryDictionary.isValidDictionary() - && needsToMigrateDictionary(mBinaryDictionary.getFormatVersion())) { - if (!mBinaryDictionary.migrateTo(DICTIONARY_FORMAT_VERSION)) { - Log.e(TAG, "Dictionary migration failed: " + mDictName); - removeBinaryDictionaryLocked(); - } - } - } - - /** - * Create a new binary dictionary and load initial contents. - */ - void createNewDictionaryLocked() { - removeBinaryDictionaryLocked(); - createOnMemoryBinaryDictionaryLocked(); - loadInitialContentsLocked(); - // Run GC and flush to file when initial contents have been loaded. - mBinaryDictionary.flushWithGCIfHasUpdated(); - } - - /** - * Marks that the dictionary needs to be recreated. - * - */ - protected void setNeedsToRecreate() { - mNeedsToRecreate = true; - } - - void clearNeedsToRecreate() { - mNeedsToRecreate = false; - } - - boolean isNeededToRecreate() { - return mNeedsToRecreate; - } - - /** - * Load the current binary dictionary from internal storage. If the dictionary file doesn't - * exists or needs to be regenerated, the new dictionary file will be asynchronously generated. - * However, the dictionary itself is accessible even before the new dictionary file is actually - * generated. It may return a null result for getSuggestions() in that case by design. - */ - public final void reloadDictionaryIfRequired() { - if (!isReloadRequired()) return; - asyncReloadDictionary(); - } - - /** - * Returns whether a dictionary reload is required. - */ - private boolean isReloadRequired() { - return mBinaryDictionary == null || mNeedsToRecreate; - } - - /** - * Reloads the dictionary. Access is controlled on a per dictionary file basis. - */ - private void asyncReloadDictionary() { - final AtomicBoolean isReloading = mIsReloading; - if (!isReloading.compareAndSet(false, true)) { - return; - } - final File dictFile = mDictFile; - asyncExecuteTaskWithWriteLock(new Runnable() { - @Override - public void run() { - try { - if (!dictFile.exists() || isNeededToRecreate()) { - // If the dictionary file does not exist or contents have been updated, - // generate a new one. - createNewDictionaryLocked(); - } else if (getBinaryDictionary() == null) { - // Otherwise, load the existing dictionary. - loadBinaryDictionaryLocked(); - final BinaryDictionary binaryDictionary = getBinaryDictionary(); - if (binaryDictionary != null && !(isValidDictionaryLocked() - // TODO: remove the check below - && matchesExpectedBinaryDictFormatVersionForThisType( - binaryDictionary.getFormatVersion()))) { - // Binary dictionary or its format version is not valid. Regenerate - // the dictionary file. createNewDictionaryLocked will remove the - // existing files if appropriate. - createNewDictionaryLocked(); - } - } - clearNeedsToRecreate(); - } finally { - isReloading.set(false); - } - } - }); - } - - /** - * Flush binary dictionary to dictionary file. - */ - public void asyncFlushBinaryDictionary() { - asyncExecuteTaskWithWriteLock(new Runnable() { - @Override - public void run() { - final BinaryDictionary binaryDictionary = getBinaryDictionary(); - if (binaryDictionary == null) { - return; - } - if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) { - binaryDictionary.flushWithGC(); - } else { - binaryDictionary.flush(); - } - } - }); - } - - public DictionaryStats getDictionaryStats() { - reloadDictionaryIfRequired(); - final String dictName = mDictName; - final File dictFile = mDictFile; - final AsyncResultHolder<DictionaryStats> result = - new AsyncResultHolder<>("DictionaryStats"); - asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() { - @Override - public void run() { - result.set(new DictionaryStats(mLocale, dictName, dictName, dictFile, 0)); - } - }); - return result.get(null /* defaultValue */, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS); - } - - @UsedForTesting - public void waitAllTasksForTests() { - final CountDownLatch countDownLatch = new CountDownLatch(1); - asyncExecuteTaskWithWriteLock(new Runnable() { - @Override - public void run() { - countDownLatch.countDown(); - } - }); - try { - countDownLatch.await(); - } catch (InterruptedException e) { - Log.e(TAG, "Interrupted while waiting for finishing dictionary operations.", e); - } - } - - @UsedForTesting - public void clearAndFlushDictionaryWithAdditionalAttributes( - final Map<String, String> attributeMap) { - mAdditionalAttributeMap = attributeMap; - clear(); - } - - public void dumpAllWordsForDebug() { - reloadDictionaryIfRequired(); - final String tag = TAG; - final String dictName = mDictName; - asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() { - @Override - public void run() { - Log.d(tag, "Dump dictionary: " + dictName + " for " + mLocale); - final BinaryDictionary binaryDictionary = getBinaryDictionary(); - if (binaryDictionary == null) { - return; - } - try { - final DictionaryHeader header = binaryDictionary.getHeader(); - Log.d(tag, "Format version: " + binaryDictionary.getFormatVersion()); - Log.d(tag, CombinedFormatUtils.formatAttributeMap( - header.mDictionaryOptions.mAttributes)); - } catch (final UnsupportedFormatException e) { - Log.d(tag, "Cannot fetch header information.", e); - } - int token = 0; - do { - final BinaryDictionary.GetNextWordPropertyResult result = - binaryDictionary.getNextWordProperty(token); - final WordProperty wordProperty = result.mWordProperty; - if (wordProperty == null) { - Log.d(tag, " dictionary is empty."); - break; - } - Log.d(tag, wordProperty.toString()); - token = result.mNextToken; - } while (token != 0); - } - }); - } - - /** - * Returns dictionary content required for syncing. - */ - public WordProperty[] getWordPropertiesForSyncing() { - reloadDictionaryIfRequired(); - final AsyncResultHolder<WordProperty[]> result = - new AsyncResultHolder<>("WordPropertiesForSync"); - asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() { - @Override - public void run() { - final ArrayList<WordProperty> wordPropertyList = new ArrayList<>(); - final BinaryDictionary binaryDictionary = getBinaryDictionary(); - if (binaryDictionary == null) { - return; - } - int token = 0; - do { - // TODO: We need a new API that returns *new* un-synced data. - final BinaryDictionary.GetNextWordPropertyResult nextWordPropertyResult = - binaryDictionary.getNextWordProperty(token); - final WordProperty wordProperty = nextWordPropertyResult.mWordProperty; - if (wordProperty == null) { - break; - } - wordPropertyList.add(wordProperty); - token = nextWordPropertyResult.mNextToken; - } while (token != 0); - result.set(wordPropertyList.toArray(new WordProperty[wordPropertyList.size()])); - } - }); - // TODO: Figure out the best timeout duration for this API. - return result.get(DEFAULT_WORD_PROPERTIES_FOR_SYNC, - TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS); - } -} diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java deleted file mode 100644 index 86c7810f4..000000000 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin; - -import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_FLOATING_GESTURE_PREVIEW; -import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE; -import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT; - -import android.text.InputType; -import android.util.Log; -import android.view.inputmethod.EditorInfo; - -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.utils.InputTypeUtils; - -import java.util.ArrayList; -import java.util.Arrays; - -/** - * Class to hold attributes of the input field. - */ -public final class InputAttributes { - private final String TAG = InputAttributes.class.getSimpleName(); - - final public String mTargetApplicationPackageName; - final public boolean mInputTypeNoAutoCorrect; - final public boolean mIsPasswordField; - final public boolean mShouldShowSuggestions; - final public boolean mApplicationSpecifiedCompletionOn; - final public boolean mShouldInsertSpacesAutomatically; - final public boolean mShouldShowVoiceInputKey; - /** - * Whether the floating gesture preview should be disabled. If true, this should override the - * corresponding keyboard settings preference, always suppressing the floating preview text. - * {@link com.android.inputmethod.latin.settings.SettingsValues#mGestureFloatingPreviewTextEnabled} - */ - final public boolean mDisableGestureFloatingPreviewText; - final public boolean mIsGeneralTextInput; - final private int mInputType; - final private EditorInfo mEditorInfo; - final private String mPackageNameForPrivateImeOptions; - - public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode, - final String packageNameForPrivateImeOptions) { - mEditorInfo = editorInfo; - mPackageNameForPrivateImeOptions = packageNameForPrivateImeOptions; - mTargetApplicationPackageName = null != editorInfo ? editorInfo.packageName : null; - final int inputType = null != editorInfo ? editorInfo.inputType : 0; - final int inputClass = inputType & InputType.TYPE_MASK_CLASS; - mInputType = inputType; - mIsPasswordField = InputTypeUtils.isPasswordInputType(inputType) - || InputTypeUtils.isVisiblePasswordInputType(inputType); - if (inputClass != InputType.TYPE_CLASS_TEXT) { - // If we are not looking at a TYPE_CLASS_TEXT field, the following strange - // cases may arise, so we do a couple validity checks for them. If it's a - // TYPE_CLASS_TEXT field, these special cases cannot happen, by construction - // of the flags. - if (null == editorInfo) { - Log.w(TAG, "No editor info for this field. Bug?"); - } else if (InputType.TYPE_NULL == inputType) { - // TODO: We should honor TYPE_NULL specification. - Log.i(TAG, "InputType.TYPE_NULL is specified"); - } else if (inputClass == 0) { - // TODO: is this check still necessary? - Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x" - + " imeOptions=0x%08x", inputType, editorInfo.imeOptions)); - } - mShouldShowSuggestions = false; - mInputTypeNoAutoCorrect = false; - mApplicationSpecifiedCompletionOn = false; - mShouldInsertSpacesAutomatically = false; - mShouldShowVoiceInputKey = false; - mDisableGestureFloatingPreviewText = false; - mIsGeneralTextInput = false; - return; - } - // inputClass == InputType.TYPE_CLASS_TEXT - final int variation = inputType & InputType.TYPE_MASK_VARIATION; - final boolean flagNoSuggestions = - 0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - final boolean flagMultiLine = - 0 != (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE); - final boolean flagAutoCorrect = - 0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); - final boolean flagAutoComplete = - 0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); - - // TODO: Have a helper method in InputTypeUtils - // Make sure that passwords are not displayed in {@link SuggestionStripView}. - final boolean shouldSuppressSuggestions = mIsPasswordField - || InputTypeUtils.isEmailVariation(variation) - || InputType.TYPE_TEXT_VARIATION_URI == variation - || InputType.TYPE_TEXT_VARIATION_FILTER == variation - || flagNoSuggestions - || flagAutoComplete; - mShouldShowSuggestions = !shouldSuppressSuggestions; - - mShouldInsertSpacesAutomatically = InputTypeUtils.isAutoSpaceFriendlyType(inputType); - - final boolean noMicrophone = mIsPasswordField - || InputTypeUtils.isEmailVariation(variation) - || InputType.TYPE_TEXT_VARIATION_URI == variation - || hasNoMicrophoneKeyOption(); - mShouldShowVoiceInputKey = !noMicrophone; - - mDisableGestureFloatingPreviewText = InputAttributes.inPrivateImeOptions( - mPackageNameForPrivateImeOptions, NO_FLOATING_GESTURE_PREVIEW, editorInfo); - - // If it's a browser edit field and auto correct is not ON explicitly, then - // disable auto correction, but keep suggestions on. - // If NO_SUGGESTIONS is set, don't do prediction. - // If it's not multiline and the autoCorrect flag is not set, then don't correct - mInputTypeNoAutoCorrect = - (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT && !flagAutoCorrect) - || flagNoSuggestions - || (!flagAutoCorrect && !flagMultiLine); - - mApplicationSpecifiedCompletionOn = flagAutoComplete && isFullscreenMode; - - // If we come here, inputClass is always TYPE_CLASS_TEXT - mIsGeneralTextInput = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS != variation - && InputType.TYPE_TEXT_VARIATION_PASSWORD != variation - && InputType.TYPE_TEXT_VARIATION_PHONETIC != variation - && InputType.TYPE_TEXT_VARIATION_URI != variation - && InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD != variation - && InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != variation - && InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD != variation; - } - - public boolean isTypeNull() { - return InputType.TYPE_NULL == mInputType; - } - - public boolean isSameInputType(final EditorInfo editorInfo) { - return editorInfo.inputType == mInputType; - } - - private boolean hasNoMicrophoneKeyOption() { - @SuppressWarnings("deprecation") - final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions( - null, NO_MICROPHONE_COMPAT, mEditorInfo); - final boolean noMicrophone = InputAttributes.inPrivateImeOptions( - mPackageNameForPrivateImeOptions, NO_MICROPHONE, mEditorInfo); - return noMicrophone || deprecatedNoMicrophone; - } - - @SuppressWarnings("unused") - private void dumpFlags(final int inputType) { - final int inputClass = inputType & InputType.TYPE_MASK_CLASS; - final String inputClassString = toInputClassString(inputClass); - final String variationString = toVariationString( - inputClass, inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); - final String flagsString = toFlagsString(inputType & InputType.TYPE_MASK_FLAGS); - Log.i(TAG, "Input class: " + inputClassString); - Log.i(TAG, "Variation: " + variationString); - Log.i(TAG, "Flags: " + flagsString); - } - - private static String toInputClassString(final int inputClass) { - switch (inputClass) { - case InputType.TYPE_CLASS_TEXT: - return "TYPE_CLASS_TEXT"; - case InputType.TYPE_CLASS_PHONE: - return "TYPE_CLASS_PHONE"; - case InputType.TYPE_CLASS_NUMBER: - return "TYPE_CLASS_NUMBER"; - case InputType.TYPE_CLASS_DATETIME: - return "TYPE_CLASS_DATETIME"; - default: - return String.format("unknownInputClass<0x%08x>", inputClass); - } - } - - private static String toVariationString(final int inputClass, final int variation) { - switch (inputClass) { - case InputType.TYPE_CLASS_TEXT: - return toTextVariationString(variation); - case InputType.TYPE_CLASS_NUMBER: - return toNumberVariationString(variation); - case InputType.TYPE_CLASS_DATETIME: - return toDatetimeVariationString(variation); - default: - return ""; - } - } - - private static String toTextVariationString(final int variation) { - switch (variation) { - case InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS: - return " TYPE_TEXT_VARIATION_EMAIL_ADDRESS"; - case InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT: - return "TYPE_TEXT_VARIATION_EMAIL_SUBJECT"; - case InputType.TYPE_TEXT_VARIATION_FILTER: - return "TYPE_TEXT_VARIATION_FILTER"; - case InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE: - return "TYPE_TEXT_VARIATION_LONG_MESSAGE"; - case InputType.TYPE_TEXT_VARIATION_NORMAL: - return "TYPE_TEXT_VARIATION_NORMAL"; - case InputType.TYPE_TEXT_VARIATION_PASSWORD: - return "TYPE_TEXT_VARIATION_PASSWORD"; - case InputType.TYPE_TEXT_VARIATION_PERSON_NAME: - return "TYPE_TEXT_VARIATION_PERSON_NAME"; - case InputType.TYPE_TEXT_VARIATION_PHONETIC: - return "TYPE_TEXT_VARIATION_PHONETIC"; - case InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS: - return "TYPE_TEXT_VARIATION_POSTAL_ADDRESS"; - case InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE: - return "TYPE_TEXT_VARIATION_SHORT_MESSAGE"; - case InputType.TYPE_TEXT_VARIATION_URI: - return "TYPE_TEXT_VARIATION_URI"; - case InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD: - return "TYPE_TEXT_VARIATION_VISIBLE_PASSWORD"; - case InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT: - return "TYPE_TEXT_VARIATION_WEB_EDIT_TEXT"; - case InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS: - return "TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS"; - case InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD: - return "TYPE_TEXT_VARIATION_WEB_PASSWORD"; - default: - return String.format("unknownVariation<0x%08x>", variation); - } - } - - private static String toNumberVariationString(final int variation) { - switch (variation) { - case InputType.TYPE_NUMBER_VARIATION_NORMAL: - return "TYPE_NUMBER_VARIATION_NORMAL"; - case InputType.TYPE_NUMBER_VARIATION_PASSWORD: - return "TYPE_NUMBER_VARIATION_PASSWORD"; - default: - return String.format("unknownVariation<0x%08x>", variation); - } - } - - private static String toDatetimeVariationString(final int variation) { - switch (variation) { - case InputType.TYPE_DATETIME_VARIATION_NORMAL: - return "TYPE_DATETIME_VARIATION_NORMAL"; - case InputType.TYPE_DATETIME_VARIATION_DATE: - return "TYPE_DATETIME_VARIATION_DATE"; - case InputType.TYPE_DATETIME_VARIATION_TIME: - return "TYPE_DATETIME_VARIATION_TIME"; - default: - return String.format("unknownVariation<0x%08x>", variation); - } - } - - private static String toFlagsString(final int flags) { - final ArrayList<String> flagsArray = new ArrayList<>(); - if (0 != (flags & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS)) - flagsArray.add("TYPE_TEXT_FLAG_NO_SUGGESTIONS"); - if (0 != (flags & InputType.TYPE_TEXT_FLAG_MULTI_LINE)) - flagsArray.add("TYPE_TEXT_FLAG_MULTI_LINE"); - if (0 != (flags & InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE)) - flagsArray.add("TYPE_TEXT_FLAG_IME_MULTI_LINE"); - if (0 != (flags & InputType.TYPE_TEXT_FLAG_CAP_WORDS)) - flagsArray.add("TYPE_TEXT_FLAG_CAP_WORDS"); - if (0 != (flags & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES)) - flagsArray.add("TYPE_TEXT_FLAG_CAP_SENTENCES"); - if (0 != (flags & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS)) - flagsArray.add("TYPE_TEXT_FLAG_CAP_CHARACTERS"); - if (0 != (flags & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT)) - flagsArray.add("TYPE_TEXT_FLAG_AUTO_CORRECT"); - if (0 != (flags & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE)) - flagsArray.add("TYPE_TEXT_FLAG_AUTO_COMPLETE"); - return flagsArray.isEmpty() ? "" : Arrays.toString(flagsArray.toArray()); - } - - // Pretty print - @Override - public String toString() { - return String.format( - "%s: inputType=0x%08x%s%s%s%s%s targetApp=%s\n", getClass().getSimpleName(), - mInputType, - (mInputTypeNoAutoCorrect ? " noAutoCorrect" : ""), - (mIsPasswordField ? " password" : ""), - (mShouldShowSuggestions ? " shouldShowSuggestions" : ""), - (mApplicationSpecifiedCompletionOn ? " appSpecified" : ""), - (mShouldInsertSpacesAutomatically ? " insertSpaces" : ""), - mTargetApplicationPackageName); - } - - public static boolean inPrivateImeOptions(final String packageName, final String key, - final EditorInfo editorInfo) { - if (editorInfo == null) return false; - final String findingKey = (packageName != null) ? packageName + "." + key : key; - return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions); - } -} diff --git a/java/src/com/android/inputmethod/latin/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java deleted file mode 100644 index f3a8ca169..000000000 --- a/java/src/com/android/inputmethod/latin/InputView.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin; - -import android.content.Context; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; - -import com.android.inputmethod.accessibility.AccessibilityUtils; -import com.android.inputmethod.keyboard.MainKeyboardView; -import com.android.inputmethod.latin.suggestions.MoreSuggestionsView; -import com.android.inputmethod.latin.suggestions.SuggestionStripView; - -public final class InputView extends FrameLayout { - private final Rect mInputViewRect = new Rect(); - private MainKeyboardView mMainKeyboardView; - private KeyboardTopPaddingForwarder mKeyboardTopPaddingForwarder; - private MoreSuggestionsViewCanceler mMoreSuggestionsViewCanceler; - private MotionEventForwarder<?, ?> mActiveForwarder; - - public InputView(final Context context, final AttributeSet attrs) { - super(context, attrs, 0); - } - - @Override - protected void onFinishInflate() { - final SuggestionStripView suggestionStripView = - (SuggestionStripView)findViewById(R.id.suggestion_strip_view); - mMainKeyboardView = (MainKeyboardView)findViewById(R.id.keyboard_view); - mKeyboardTopPaddingForwarder = new KeyboardTopPaddingForwarder( - mMainKeyboardView, suggestionStripView); - mMoreSuggestionsViewCanceler = new MoreSuggestionsViewCanceler( - mMainKeyboardView, suggestionStripView); - } - - public void setKeyboardTopPadding(final int keyboardTopPadding) { - mKeyboardTopPaddingForwarder.setKeyboardTopPadding(keyboardTopPadding); - } - - @Override - protected boolean dispatchHoverEvent(final MotionEvent event) { - if (AccessibilityUtils.getInstance().isTouchExplorationEnabled() - && mMainKeyboardView.isShowingMoreKeysPanel()) { - // With accessibility mode on, discard hover events while a more keys keyboard is shown. - // The {@link MoreKeysKeyboard} receives hover events directly from the platform. - return true; - } - return super.dispatchHoverEvent(event); - } - - @Override - public boolean onInterceptTouchEvent(final MotionEvent me) { - final Rect rect = mInputViewRect; - getGlobalVisibleRect(rect); - final int index = me.getActionIndex(); - final int x = (int)me.getX(index) + rect.left; - final int y = (int)me.getY(index) + rect.top; - - // The touch events that hit the top padding of keyboard should be forwarded to - // {@link SuggestionStripView}. - if (mKeyboardTopPaddingForwarder.onInterceptTouchEvent(x, y, me)) { - mActiveForwarder = mKeyboardTopPaddingForwarder; - return true; - } - - // To cancel {@link MoreSuggestionsView}, we should intercept a touch event to - // {@link MainKeyboardView} and dismiss the {@link MoreSuggestionsView}. - if (mMoreSuggestionsViewCanceler.onInterceptTouchEvent(x, y, me)) { - mActiveForwarder = mMoreSuggestionsViewCanceler; - return true; - } - - mActiveForwarder = null; - return false; - } - - @Override - public boolean onTouchEvent(final MotionEvent me) { - if (mActiveForwarder == null) { - return super.onTouchEvent(me); - } - - final Rect rect = mInputViewRect; - getGlobalVisibleRect(rect); - final int index = me.getActionIndex(); - final int x = (int)me.getX(index) + rect.left; - final int y = (int)me.getY(index) + rect.top; - return mActiveForwarder.onTouchEvent(x, y, me); - } - - /** - * This class forwards series of {@link MotionEvent}s from <code>SenderView</code> to - * <code>ReceiverView</code>. - * - * @param <SenderView> a {@link View} that may send a {@link MotionEvent} to <ReceiverView>. - * @param <ReceiverView> a {@link View} that receives forwarded {@link MotionEvent} from - * <SenderView>. - */ - private static abstract class - MotionEventForwarder<SenderView extends View, ReceiverView extends View> { - protected final SenderView mSenderView; - protected final ReceiverView mReceiverView; - - protected final Rect mEventSendingRect = new Rect(); - protected final Rect mEventReceivingRect = new Rect(); - - public MotionEventForwarder(final SenderView senderView, final ReceiverView receiverView) { - mSenderView = senderView; - mReceiverView = receiverView; - } - - // Return true if a touch event of global coordinate x, y needs to be forwarded. - protected abstract boolean needsToForward(final int x, final int y); - - // Translate global x-coordinate to <code>ReceiverView</code> local coordinate. - protected int translateX(final int x) { - return x - mEventReceivingRect.left; - } - - // Translate global y-coordinate to <code>ReceiverView</code> local coordinate. - protected int translateY(final int y) { - return y - mEventReceivingRect.top; - } - - /** - * Callback when a {@link MotionEvent} is forwarded. - * @param me the motion event to be forwarded. - */ - protected void onForwardingEvent(final MotionEvent me) {} - - // Returns true if a {@link MotionEvent} is needed to be forwarded to - // <code>ReceiverView</code>. Otherwise returns false. - public boolean onInterceptTouchEvent(final int x, final int y, final MotionEvent me) { - // Forwards a {link MotionEvent} only if both <code>SenderView</code> and - // <code>ReceiverView</code> are visible. - if (mSenderView.getVisibility() != View.VISIBLE || - mReceiverView.getVisibility() != View.VISIBLE) { - return false; - } - mSenderView.getGlobalVisibleRect(mEventSendingRect); - if (!mEventSendingRect.contains(x, y)) { - return false; - } - - if (me.getActionMasked() == MotionEvent.ACTION_DOWN) { - // If the down event happens in the forwarding area, successive - // {@link MotionEvent}s should be forwarded to <code>ReceiverView</code>. - if (needsToForward(x, y)) { - return true; - } - } - - return false; - } - - // Returns true if a {@link MotionEvent} is forwarded to <code>ReceiverView</code>. - // Otherwise returns false. - public boolean onTouchEvent(final int x, final int y, final MotionEvent me) { - mReceiverView.getGlobalVisibleRect(mEventReceivingRect); - // Translate global coordinates to <code>ReceiverView</code> local coordinates. - me.setLocation(translateX(x), translateY(y)); - mReceiverView.dispatchTouchEvent(me); - onForwardingEvent(me); - return true; - } - } - - /** - * This class forwards {@link MotionEvent}s happened in the top padding of - * {@link MainKeyboardView} to {@link SuggestionStripView}. - */ - private static class KeyboardTopPaddingForwarder - extends MotionEventForwarder<MainKeyboardView, SuggestionStripView> { - private int mKeyboardTopPadding; - - public KeyboardTopPaddingForwarder(final MainKeyboardView mainKeyboardView, - final SuggestionStripView suggestionStripView) { - super(mainKeyboardView, suggestionStripView); - } - - public void setKeyboardTopPadding(final int keyboardTopPadding) { - mKeyboardTopPadding = keyboardTopPadding; - } - - private boolean isInKeyboardTopPadding(final int y) { - return y < mEventSendingRect.top + mKeyboardTopPadding; - } - - @Override - protected boolean needsToForward(final int x, final int y) { - // Forwarding an event only when {@link MainKeyboardView} is visible. - // Because the visibility of {@link MainKeyboardView} is controlled by its parent - // view in {@link KeyboardSwitcher#setMainKeyboardFrame()}, we should check the - // visibility of the parent view. - final View mainKeyboardFrame = (View)mSenderView.getParent(); - return mainKeyboardFrame.getVisibility() == View.VISIBLE && isInKeyboardTopPadding(y); - } - - @Override - protected int translateY(final int y) { - final int translatedY = super.translateY(y); - if (isInKeyboardTopPadding(y)) { - // The forwarded event should have coordinates that are inside of the target. - return Math.min(translatedY, mEventReceivingRect.height() - 1); - } - return translatedY; - } - } - - /** - * This class forwards {@link MotionEvent}s happened in the {@link MainKeyboardView} to - * {@link SuggestionStripView} when the {@link MoreSuggestionsView} is showing. - * {@link SuggestionStripView} dismisses {@link MoreSuggestionsView} when it receives any event - * outside of it. - */ - private static class MoreSuggestionsViewCanceler - extends MotionEventForwarder<MainKeyboardView, SuggestionStripView> { - public MoreSuggestionsViewCanceler(final MainKeyboardView mainKeyboardView, - final SuggestionStripView suggestionStripView) { - super(mainKeyboardView, suggestionStripView); - } - - @Override - protected boolean needsToForward(final int x, final int y) { - return mReceiverView.isShowingMoreSuggestionPanel() && mEventSendingRect.contains(x, y); - } - - @Override - protected void onForwardingEvent(final MotionEvent me) { - if (me.getActionMasked() == MotionEvent.ACTION_DOWN) { - mReceiverView.dismissMoreSuggestionsPanel(); - } - } - } -} diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java deleted file mode 100644 index 426d33e6d..000000000 --- a/java/src/com/android/inputmethod/latin/LastComposedWord.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin; - -import android.text.TextUtils; - -import com.android.inputmethod.event.Event; -import com.android.inputmethod.latin.common.InputPointers; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; - -import java.util.ArrayList; - -/** - * This class encapsulates data about a word previously composed, but that has been - * committed already. This is used for resuming suggestion, and cancel auto-correction. - */ -public final class LastComposedWord { - // COMMIT_TYPE_USER_TYPED_WORD is used when the word committed is the exact typed word, with - // no hinting from the IME. It happens when some external event happens (rotating the device, - // for example) or when auto-correction is off by settings or editor attributes. - public static final int COMMIT_TYPE_USER_TYPED_WORD = 0; - // COMMIT_TYPE_MANUAL_PICK is used when the user pressed a field in the suggestion strip. - public static final int COMMIT_TYPE_MANUAL_PICK = 1; - // COMMIT_TYPE_DECIDED_WORD is used when the IME commits the word it decided was best - // for the current user input. It may be different from what the user typed (true auto-correct) - // or it may be exactly what the user typed if it's in the dictionary or the IME does not have - // enough confidence in any suggestion to auto-correct (auto-correct to typed word). - public static final int COMMIT_TYPE_DECIDED_WORD = 2; - // COMMIT_TYPE_CANCEL_AUTO_CORRECT is used upon committing back the old word upon cancelling - // an auto-correction. - public static final int COMMIT_TYPE_CANCEL_AUTO_CORRECT = 3; - - public static final String NOT_A_SEPARATOR = ""; - - public final ArrayList<Event> mEvents; - public final String mTypedWord; - public final CharSequence mCommittedWord; - public final String mSeparatorString; - public final NgramContext mNgramContext; - public final int mCapitalizedMode; - public final InputPointers mInputPointers = - new InputPointers(DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH); - - private boolean mActive; - - public static final LastComposedWord NOT_A_COMPOSED_WORD = - new LastComposedWord(new ArrayList<Event>(), null, "", "", - NOT_A_SEPARATOR, null, WordComposer.CAPS_MODE_OFF); - - // Warning: this is using the passed objects as is and fully expects them to be - // immutable. Do not fiddle with their contents after you passed them to this constructor. - public LastComposedWord(final ArrayList<Event> events, - final InputPointers inputPointers, final String typedWord, - final CharSequence committedWord, final String separatorString, - final NgramContext ngramContext, final int capitalizedMode) { - if (inputPointers != null) { - mInputPointers.copy(inputPointers); - } - mTypedWord = typedWord; - mEvents = new ArrayList<>(events); - mCommittedWord = committedWord; - mSeparatorString = separatorString; - mActive = true; - mNgramContext = ngramContext; - mCapitalizedMode = capitalizedMode; - } - - public void deactivate() { - mActive = false; - } - - public boolean canRevertCommit() { - return mActive && !TextUtils.isEmpty(mCommittedWord) && !didCommitTypedWord(); - } - - private boolean didCommitTypedWord() { - return TextUtils.equals(mTypedWord, mCommittedWord); - } -} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java deleted file mode 100644 index e68b43b39..000000000 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ /dev/null @@ -1,2033 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.inputmethod.latin; - -import static com.android.inputmethod.latin.common.Constants.ImeOption.FORCE_ASCII; -import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE; -import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT; - -import android.Manifest.permission; -import android.app.ActivityOptions; -import android.app.AlertDialog; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Color; -import android.inputmethodservice.InputMethodService; -import android.media.AudioManager; -import android.os.Build; -import android.os.Debug; -import android.os.IBinder; -import android.os.Message; -import android.preference.PreferenceManager; -import android.text.InputType; -import android.util.Log; -import android.util.PrintWriterPrinter; -import android.util.Printer; -import android.util.SparseArray; -import android.view.Display; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.view.Window; -import android.view.WindowManager; -import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodSubtype; - -import androidx.annotation.NonNull; - -import com.android.inputmethod.accessibility.AccessibilityUtils; -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.compat.BuildCompatUtils; -import com.android.inputmethod.compat.EditorInfoCompatUtils; -import com.android.inputmethod.compat.InputMethodServiceCompatUtils; -import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils; -import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater; -import com.android.inputmethod.dictionarypack.DictionaryPackConstants; -import com.android.inputmethod.event.Event; -import com.android.inputmethod.event.HardwareEventDecoder; -import com.android.inputmethod.event.HardwareKeyboardEventDecoder; -import com.android.inputmethod.event.InputTransaction; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.keyboard.KeyboardSwitcher; -import com.android.inputmethod.keyboard.MainKeyboardView; -import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.CoordinateUtils; -import com.android.inputmethod.latin.common.InputPointers; -import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.define.ProductionFlags; -import com.android.inputmethod.latin.inputlogic.InputLogic; -import com.android.inputmethod.latin.permissions.PermissionsManager; -import com.android.inputmethod.latin.personalization.PersonalizationHelper; -import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.settings.SettingsActivity; -import com.android.inputmethod.latin.settings.SettingsValues; -import com.android.inputmethod.latin.suggestions.SuggestionStripView; -import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor; -import com.android.inputmethod.latin.touchinputconsumer.GestureConsumer; -import com.android.inputmethod.latin.utils.ApplicationUtils; -import com.android.inputmethod.latin.utils.DialogUtils; -import com.android.inputmethod.latin.utils.ImportantNoticeUtils; -import com.android.inputmethod.latin.utils.IntentUtils; -import com.android.inputmethod.latin.utils.JniUtils; -import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; -import com.android.inputmethod.latin.utils.StatsUtils; -import com.android.inputmethod.latin.utils.StatsUtilsManager; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; -import com.android.inputmethod.latin.utils.ViewLayoutUtils; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Input method implementation for Qwerty'ish keyboard. - */ -public class LatinIME extends InputMethodService implements KeyboardActionListener, - SuggestionStripView.Listener, SuggestionStripViewAccessor, - DictionaryFacilitator.DictionaryInitializationListener, - PermissionsManager.PermissionsResultCallback { - static final String TAG = LatinIME.class.getSimpleName(); - private static final boolean TRACE = false; - - private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2; - private static final int PENDING_IMS_CALLBACK_DURATION_MILLIS = 800; - static final long DELAY_WAIT_FOR_DICTIONARY_LOAD_MILLIS = TimeUnit.SECONDS.toMillis(2); - static final long DELAY_DEALLOCATE_MEMORY_MILLIS = TimeUnit.SECONDS.toMillis(10); - - /** - * A broadcast intent action to hide the software keyboard. - */ - static final String ACTION_HIDE_SOFT_INPUT = - "com.android.inputmethod.latin.HIDE_SOFT_INPUT"; - - /** - * A custom permission for external apps to send {@link #ACTION_HIDE_SOFT_INPUT}. - */ - static final String PERMISSION_HIDE_SOFT_INPUT = - "com.android.inputmethod.latin.HIDE_SOFT_INPUT"; - - /** - * The name of the scheme used by the Package Manager to warn of a new package installation, - * replacement or removal. - */ - private static final String SCHEME_PACKAGE = "package"; - - final Settings mSettings; - private final DictionaryFacilitator mDictionaryFacilitator = - DictionaryFacilitatorProvider.getDictionaryFacilitator( - false /* isNeededForSpellChecking */); - final InputLogic mInputLogic = new InputLogic(this /* LatinIME */, - this /* SuggestionStripViewAccessor */, mDictionaryFacilitator); - // We expect to have only one decoder in almost all cases, hence the default capacity of 1. - // If it turns out we need several, it will get grown seamlessly. - final SparseArray<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1); - - // TODO: Move these {@link View}s to {@link KeyboardSwitcher}. - private View mInputView; - private InsetsUpdater mInsetsUpdater; - private SuggestionStripView mSuggestionStripView; - - private RichInputMethodManager mRichImm; - @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; - private final SubtypeState mSubtypeState = new SubtypeState(); - private EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector; - private StatsUtilsManager mStatsUtilsManager; - // Working variable for {@link #startShowingInputView()} and - // {@link #onEvaluateInputViewShown()}. - private boolean mIsExecutingStartShowingInputView; - - // Used for re-initialize keyboard layout after onConfigurationChange. - @Nullable private Context mDisplayContext; - - // Object for reacting to adding/removing a dictionary pack. - private final BroadcastReceiver mDictionaryPackInstallReceiver = - new DictionaryPackInstallBroadcastReceiver(this); - - private final BroadcastReceiver mDictionaryDumpBroadcastReceiver = - new DictionaryDumpBroadcastReceiver(this); - - final static class HideSoftInputReceiver extends BroadcastReceiver { - private final InputMethodService mIms; - - public HideSoftInputReceiver(InputMethodService ims) { - mIms = ims; - } - - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (ACTION_HIDE_SOFT_INPUT.equals(action)) { - mIms.requestHideSelf(0 /* flags */); - } else { - Log.e(TAG, "Unexpected intent " + intent); - } - } - } - final HideSoftInputReceiver mHideSoftInputReceiver = new HideSoftInputReceiver(this); - - private AlertDialog mOptionsDialog; - - private final boolean mIsHardwareAcceleratedDrawingEnabled; - - private GestureConsumer mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; - - public final UIHandler mHandler = new UIHandler(this); - - public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> { - private static final int MSG_UPDATE_SHIFT_STATE = 0; - private static final int MSG_PENDING_IMS_CALLBACK = 1; - private static final int MSG_UPDATE_SUGGESTION_STRIP = 2; - private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3; - private static final int MSG_RESUME_SUGGESTIONS = 4; - private static final int MSG_REOPEN_DICTIONARIES = 5; - private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6; - private static final int MSG_RESET_CACHES = 7; - private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8; - private static final int MSG_DEALLOCATE_MEMORY = 9; - private static final int MSG_RESUME_SUGGESTIONS_FOR_START_INPUT = 10; - private static final int MSG_SWITCH_LANGUAGE_AUTOMATICALLY = 11; - // Update this when adding new messages - private static final int MSG_LAST = MSG_SWITCH_LANGUAGE_AUTOMATICALLY; - - private static final int ARG1_NOT_GESTURE_INPUT = 0; - private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; - private static final int ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT = 2; - private static final int ARG2_UNUSED = 0; - private static final int ARG1_TRUE = 1; - - private int mDelayInMillisecondsToUpdateSuggestions; - private int mDelayInMillisecondsToUpdateShiftState; - - public UIHandler(@Nonnull final LatinIME ownerInstance) { - super(ownerInstance); - } - - public void onCreate() { - final LatinIME latinIme = getOwnerInstance(); - if (latinIme == null) { - return; - } - final Resources res = latinIme.getResources(); - mDelayInMillisecondsToUpdateSuggestions = res.getInteger( - R.integer.config_delay_in_milliseconds_to_update_suggestions); - mDelayInMillisecondsToUpdateShiftState = res.getInteger( - R.integer.config_delay_in_milliseconds_to_update_shift_state); - } - - @Override - public void handleMessage(final Message msg) { - final LatinIME latinIme = getOwnerInstance(); - if (latinIme == null) { - return; - } - final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher; - switch (msg.what) { - case MSG_UPDATE_SUGGESTION_STRIP: - cancelUpdateSuggestionStrip(); - latinIme.mInputLogic.performUpdateSuggestionStripSync( - latinIme.mSettings.getCurrent(), msg.arg1 /* inputStyle */); - break; - case MSG_UPDATE_SHIFT_STATE: - switcher.requestUpdatingShiftState(latinIme.getCurrentAutoCapsState(), - latinIme.getCurrentRecapitalizeState()); - break; - case MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP: - if (msg.arg1 == ARG1_NOT_GESTURE_INPUT) { - final SuggestedWords suggestedWords = (SuggestedWords) msg.obj; - latinIme.showSuggestionStrip(suggestedWords); - } else { - latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords) msg.obj, - msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT); - } - break; - case MSG_RESUME_SUGGESTIONS: - latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor( - latinIme.mSettings.getCurrent(), false /* forStartInput */, - latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId()); - break; - case MSG_RESUME_SUGGESTIONS_FOR_START_INPUT: - latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor( - latinIme.mSettings.getCurrent(), true /* forStartInput */, - latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId()); - break; - case MSG_REOPEN_DICTIONARIES: - // We need to re-evaluate the currently composing word in case the script has - // changed. - postWaitForDictionaryLoad(); - latinIme.resetDictionaryFacilitatorIfNecessary(); - break; - case MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED: - final SuggestedWords suggestedWords = (SuggestedWords) msg.obj; - latinIme.mInputLogic.onUpdateTailBatchInputCompleted( - latinIme.mSettings.getCurrent(), - suggestedWords, latinIme.mKeyboardSwitcher); - latinIme.onTailBatchInputResultShown(suggestedWords); - break; - case MSG_RESET_CACHES: - final SettingsValues settingsValues = latinIme.mSettings.getCurrent(); - if (latinIme.mInputLogic.retryResetCachesAndReturnSuccess( - msg.arg1 == ARG1_TRUE /* tryResumeSuggestions */, - msg.arg2 /* remainingTries */, this /* handler */)) { - // If we were able to reset the caches, then we can reload the keyboard. - // Otherwise, we'll do it when we can. - latinIme.mKeyboardSwitcher.loadKeyboard(latinIme.getCurrentInputEditorInfo(), - settingsValues, latinIme.getCurrentAutoCapsState(), - latinIme.getCurrentRecapitalizeState()); - } - break; - case MSG_WAIT_FOR_DICTIONARY_LOAD: - Log.i(TAG, "Timeout waiting for dictionary load"); - break; - case MSG_DEALLOCATE_MEMORY: - latinIme.deallocateMemory(); - break; - case MSG_SWITCH_LANGUAGE_AUTOMATICALLY: - latinIme.switchLanguage((InputMethodSubtype)msg.obj); - break; - } - } - - public void postUpdateSuggestionStrip(final int inputStyle) { - sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP, inputStyle, - 0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions); - } - - public void postReopenDictionaries() { - sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES)); - } - - private void postResumeSuggestionsInternal(final boolean shouldDelay, - final boolean forStartInput) { - final LatinIME latinIme = getOwnerInstance(); - if (latinIme == null) { - return; - } - if (!latinIme.mSettings.getCurrent().isSuggestionsEnabledPerUserSettings()) { - return; - } - removeMessages(MSG_RESUME_SUGGESTIONS); - removeMessages(MSG_RESUME_SUGGESTIONS_FOR_START_INPUT); - final int message = forStartInput ? MSG_RESUME_SUGGESTIONS_FOR_START_INPUT - : MSG_RESUME_SUGGESTIONS; - if (shouldDelay) { - sendMessageDelayed(obtainMessage(message), - mDelayInMillisecondsToUpdateSuggestions); - } else { - sendMessage(obtainMessage(message)); - } - } - - public void postResumeSuggestions(final boolean shouldDelay) { - postResumeSuggestionsInternal(shouldDelay, false /* forStartInput */); - } - - public void postResumeSuggestionsForStartInput(final boolean shouldDelay) { - postResumeSuggestionsInternal(shouldDelay, true /* forStartInput */); - } - - public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) { - removeMessages(MSG_RESET_CACHES); - sendMessage(obtainMessage(MSG_RESET_CACHES, tryResumeSuggestions ? 1 : 0, - remainingTries, null)); - } - - public void postWaitForDictionaryLoad() { - sendMessageDelayed(obtainMessage(MSG_WAIT_FOR_DICTIONARY_LOAD), - DELAY_WAIT_FOR_DICTIONARY_LOAD_MILLIS); - } - - public void cancelWaitForDictionaryLoad() { - removeMessages(MSG_WAIT_FOR_DICTIONARY_LOAD); - } - - public boolean hasPendingWaitForDictionaryLoad() { - return hasMessages(MSG_WAIT_FOR_DICTIONARY_LOAD); - } - - public void cancelUpdateSuggestionStrip() { - removeMessages(MSG_UPDATE_SUGGESTION_STRIP); - } - - public boolean hasPendingUpdateSuggestions() { - return hasMessages(MSG_UPDATE_SUGGESTION_STRIP); - } - - public boolean hasPendingReopenDictionaries() { - return hasMessages(MSG_REOPEN_DICTIONARIES); - } - - public void postUpdateShiftState() { - removeMessages(MSG_UPDATE_SHIFT_STATE); - sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), - mDelayInMillisecondsToUpdateShiftState); - } - - public void postDeallocateMemory() { - sendMessageDelayed(obtainMessage(MSG_DEALLOCATE_MEMORY), - DELAY_DEALLOCATE_MEMORY_MILLIS); - } - - public void cancelDeallocateMemory() { - removeMessages(MSG_DEALLOCATE_MEMORY); - } - - public boolean hasPendingDeallocateMemory() { - return hasMessages(MSG_DEALLOCATE_MEMORY); - } - - @UsedForTesting - public void removeAllMessages() { - for (int i = 0; i <= MSG_LAST; ++i) { - removeMessages(i); - } - } - - public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords, - final boolean dismissGestureFloatingPreviewText) { - removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); - final int arg1 = dismissGestureFloatingPreviewText - ? ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT - : ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT; - obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1, - ARG2_UNUSED, suggestedWords).sendToTarget(); - } - - public void showSuggestionStrip(final SuggestedWords suggestedWords) { - removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); - obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, - ARG1_NOT_GESTURE_INPUT, ARG2_UNUSED, suggestedWords).sendToTarget(); - } - - public void showTailBatchInputResult(final SuggestedWords suggestedWords) { - obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget(); - } - - public void postSwitchLanguage(final InputMethodSubtype subtype) { - obtainMessage(MSG_SWITCH_LANGUAGE_AUTOMATICALLY, subtype).sendToTarget(); - } - - // Working variables for the following methods. - private boolean mIsOrientationChanging; - private boolean mPendingSuccessiveImsCallback; - private boolean mHasPendingStartInput; - private boolean mHasPendingFinishInputView; - private boolean mHasPendingFinishInput; - private EditorInfo mAppliedEditorInfo; - - public void startOrientationChanging() { - removeMessages(MSG_PENDING_IMS_CALLBACK); - resetPendingImsCallback(); - mIsOrientationChanging = true; - final LatinIME latinIme = getOwnerInstance(); - if (latinIme == null) { - return; - } - if (latinIme.isInputViewShown()) { - latinIme.mKeyboardSwitcher.saveKeyboardState(); - } - } - - private void resetPendingImsCallback() { - mHasPendingFinishInputView = false; - mHasPendingFinishInput = false; - mHasPendingStartInput = false; - } - - private void executePendingImsCallback(final LatinIME latinIme, final EditorInfo editorInfo, - boolean restarting) { - if (mHasPendingFinishInputView) { - latinIme.onFinishInputViewInternal(mHasPendingFinishInput); - } - if (mHasPendingFinishInput) { - latinIme.onFinishInputInternal(); - } - if (mHasPendingStartInput) { - latinIme.onStartInputInternal(editorInfo, restarting); - } - resetPendingImsCallback(); - } - - public void onStartInput(final EditorInfo editorInfo, final boolean restarting) { - if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { - // Typically this is the second onStartInput after orientation changed. - mHasPendingStartInput = true; - } else { - if (mIsOrientationChanging && restarting) { - // This is the first onStartInput after orientation changed. - mIsOrientationChanging = false; - mPendingSuccessiveImsCallback = true; - } - final LatinIME latinIme = getOwnerInstance(); - if (latinIme != null) { - executePendingImsCallback(latinIme, editorInfo, restarting); - latinIme.onStartInputInternal(editorInfo, restarting); - } - } - } - - public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) { - if (hasMessages(MSG_PENDING_IMS_CALLBACK) - && KeyboardId.equivalentEditorInfoForKeyboard(editorInfo, mAppliedEditorInfo)) { - // Typically this is the second onStartInputView after orientation changed. - resetPendingImsCallback(); - } else { - if (mPendingSuccessiveImsCallback) { - // This is the first onStartInputView after orientation changed. - mPendingSuccessiveImsCallback = false; - resetPendingImsCallback(); - sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK), - PENDING_IMS_CALLBACK_DURATION_MILLIS); - } - final LatinIME latinIme = getOwnerInstance(); - if (latinIme != null) { - executePendingImsCallback(latinIme, editorInfo, restarting); - latinIme.onStartInputViewInternal(editorInfo, restarting); - mAppliedEditorInfo = editorInfo; - } - cancelDeallocateMemory(); - } - } - - public void onFinishInputView(final boolean finishingInput) { - if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { - // Typically this is the first onFinishInputView after orientation changed. - mHasPendingFinishInputView = true; - } else { - final LatinIME latinIme = getOwnerInstance(); - if (latinIme != null) { - latinIme.onFinishInputViewInternal(finishingInput); - mAppliedEditorInfo = null; - } - if (!hasPendingDeallocateMemory()) { - postDeallocateMemory(); - } - } - } - - public void onFinishInput() { - if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { - // Typically this is the first onFinishInput after orientation changed. - mHasPendingFinishInput = true; - } else { - final LatinIME latinIme = getOwnerInstance(); - if (latinIme != null) { - executePendingImsCallback(latinIme, null, false); - latinIme.onFinishInputInternal(); - } - } - } - } - - static final class SubtypeState { - private InputMethodSubtype mLastActiveSubtype; - private boolean mCurrentSubtypeHasBeenUsed; - - public void setCurrentSubtypeHasBeenUsed() { - mCurrentSubtypeHasBeenUsed = true; - } - - public void switchSubtype(final IBinder token, final RichInputMethodManager richImm) { - final InputMethodSubtype currentSubtype = richImm.getInputMethodManager() - .getCurrentInputMethodSubtype(); - final InputMethodSubtype lastActiveSubtype = mLastActiveSubtype; - final boolean currentSubtypeHasBeenUsed = mCurrentSubtypeHasBeenUsed; - if (currentSubtypeHasBeenUsed) { - mLastActiveSubtype = currentSubtype; - mCurrentSubtypeHasBeenUsed = false; - } - if (currentSubtypeHasBeenUsed - && richImm.checkIfSubtypeBelongsToThisImeAndEnabled(lastActiveSubtype) - && !currentSubtype.equals(lastActiveSubtype)) { - richImm.setInputMethodAndSubtype(token, lastActiveSubtype); - return; - } - richImm.switchToNextInputMethod(token, true /* onlyCurrentIme */); - } - } - - // Loading the native library eagerly to avoid unexpected UnsatisfiedLinkError at the initial - // JNI call as much as possible. - static { - JniUtils.loadNativeLibrary(); - } - - public LatinIME() { - super(); - mSettings = Settings.getInstance(); - mKeyboardSwitcher = KeyboardSwitcher.getInstance(); - mStatsUtilsManager = StatsUtilsManager.getInstance(); - mIsHardwareAcceleratedDrawingEnabled = - InputMethodServiceCompatUtils.enableHardwareAcceleration(this); - Log.i(TAG, "Hardware accelerated drawing: " + mIsHardwareAcceleratedDrawingEnabled); - } - - @Override - public void onCreate() { - Settings.init(this); - DebugFlags.init(PreferenceManager.getDefaultSharedPreferences(this)); - RichInputMethodManager.init(this); - mRichImm = RichInputMethodManager.getInstance(); - AudioAndHapticFeedbackManager.init(this); - AccessibilityUtils.init(this); - mStatsUtilsManager.onCreate(this /* context */, mDictionaryFacilitator); - final WindowManager wm = getSystemService(WindowManager.class); - mDisplayContext = getDisplayContext(); - KeyboardSwitcher.init(this); - super.onCreate(); - - mHandler.onCreate(); - - // TODO: Resolve mutual dependencies of {@link #loadSettings()} and - // {@link #resetDictionaryFacilitatorIfNecessary()}. - loadSettings(); - resetDictionaryFacilitatorIfNecessary(); - - // Register to receive ringer mode change. - final IntentFilter filter = new IntentFilter(); - filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); - registerReceiver(mRingerModeChangeReceiver, filter); - - // Register to receive installation and removal of a dictionary pack. - final IntentFilter packageFilter = new IntentFilter(); - packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - packageFilter.addDataScheme(SCHEME_PACKAGE); - registerReceiver(mDictionaryPackInstallReceiver, packageFilter); - - final IntentFilter newDictFilter = new IntentFilter(); - newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver(mDictionaryPackInstallReceiver, newDictFilter, - Context.RECEIVER_NOT_EXPORTED); - } else { - registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); - } - - final IntentFilter dictDumpFilter = new IntentFilter(); - dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter, - Context.RECEIVER_NOT_EXPORTED); - } else { - registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter); - } - - final IntentFilter hideSoftInputFilter = new IntentFilter(); - hideSoftInputFilter.addAction(ACTION_HIDE_SOFT_INPUT); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver(mHideSoftInputReceiver, hideSoftInputFilter, - PERMISSION_HIDE_SOFT_INPUT, null /* scheduler */, Context.RECEIVER_EXPORTED); - } else { - registerReceiver(mHideSoftInputReceiver, hideSoftInputFilter, - PERMISSION_HIDE_SOFT_INPUT, null /* scheduler */); - } - - StatsUtils.onCreate(mSettings.getCurrent(), mRichImm); - } - - // Has to be package-visible for unit tests - @UsedForTesting - void loadSettings() { - final Locale locale = mRichImm.getCurrentSubtypeLocale(); - final EditorInfo editorInfo = getCurrentInputEditorInfo(); - final InputAttributes inputAttributes = new InputAttributes( - editorInfo, isFullscreenMode(), getPackageName()); - mSettings.loadSettings(this, locale, inputAttributes); - final SettingsValues currentSettingsValues = mSettings.getCurrent(); - AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues); - // This method is called on startup and language switch, before the new layout has - // been displayed. Opening dictionaries never affects responsivity as dictionaries are - // asynchronously loaded. - if (!mHandler.hasPendingReopenDictionaries()) { - resetDictionaryFacilitator(locale); - } - refreshPersonalizationDictionarySession(currentSettingsValues); - resetDictionaryFacilitatorIfNecessary(); - mStatsUtilsManager.onLoadSettings(this /* context */, currentSettingsValues); - } - - private void refreshPersonalizationDictionarySession( - final SettingsValues currentSettingsValues) { - if (!currentSettingsValues.mUsePersonalizedDicts) { - // Remove user history dictionaries. - PersonalizationHelper.removeAllUserHistoryDictionaries(this); - mDictionaryFacilitator.clearUserHistoryDictionary(this); - } - } - - // Note that this method is called from a non-UI thread. - @Override - public void onUpdateMainDictionaryAvailability(final boolean isMainDictionaryAvailable) { - final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - if (mainKeyboardView != null) { - mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable); - } - if (mHandler.hasPendingWaitForDictionaryLoad()) { - mHandler.cancelWaitForDictionaryLoad(); - mHandler.postResumeSuggestions(false /* shouldDelay */); - } - } - - void resetDictionaryFacilitatorIfNecessary() { - final Locale subtypeSwitcherLocale = mRichImm.getCurrentSubtypeLocale(); - final Locale subtypeLocale; - if (subtypeSwitcherLocale == null) { - // This happens in very rare corner cases - for example, immediately after a switch - // to LatinIME has been requested, about a frame later another switch happens. In this - // case, we are about to go down but we still don't know it, however the system tells - // us there is no current subtype. - Log.e(TAG, "System is reporting no current subtype."); - subtypeLocale = getResources().getConfiguration().locale; - } else { - subtypeLocale = subtypeSwitcherLocale; - } - if (mDictionaryFacilitator.isForLocale(subtypeLocale) - && mDictionaryFacilitator.isForAccount(mSettings.getCurrent().mAccount)) { - return; - } - resetDictionaryFacilitator(subtypeLocale); - } - - /** - * Reset the facilitator by loading dictionaries for the given locale and - * the current settings values. - * - * @param locale the locale - */ - // TODO: make sure the current settings always have the right locales, and read from them. - private void resetDictionaryFacilitator(final Locale locale) { - final SettingsValues settingsValues = mSettings.getCurrent(); - mDictionaryFacilitator.resetDictionaries(this /* context */, locale, - settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, - false /* forceReloadMainDictionary */, - settingsValues.mAccount, "" /* dictNamePrefix */, - this /* DictionaryInitializationListener */); - if (settingsValues.mAutoCorrectionEnabledPerUserSettings) { - mInputLogic.mSuggest.setAutoCorrectionThreshold( - settingsValues.mAutoCorrectionThreshold); - } - mInputLogic.mSuggest.setPlausibilityThreshold(settingsValues.mPlausibilityThreshold); - } - - /** - * Reset suggest by loading the main dictionary of the current locale. - */ - /* package private */ void resetSuggestMainDict() { - final SettingsValues settingsValues = mSettings.getCurrent(); - mDictionaryFacilitator.resetDictionaries(this /* context */, - mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict, - settingsValues.mUsePersonalizedDicts, - true /* forceReloadMainDictionary */, - settingsValues.mAccount, "" /* dictNamePrefix */, - this /* DictionaryInitializationListener */); - } - - @Override - public void onDestroy() { - mDictionaryFacilitator.closeDictionaries(); - mSettings.onDestroy(); - unregisterReceiver(mHideSoftInputReceiver); - unregisterReceiver(mRingerModeChangeReceiver); - unregisterReceiver(mDictionaryPackInstallReceiver); - unregisterReceiver(mDictionaryDumpBroadcastReceiver); - mStatsUtilsManager.onDestroy(this /* context */); - super.onDestroy(); - } - - @UsedForTesting - public void recycle() { - unregisterReceiver(mDictionaryPackInstallReceiver); - unregisterReceiver(mDictionaryDumpBroadcastReceiver); - unregisterReceiver(mRingerModeChangeReceiver); - mInputLogic.recycle(); - } - - private boolean isImeSuppressedByHardwareKeyboard() { - final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); - return !onEvaluateInputViewShown() && switcher.isImeSuppressedByHardwareKeyboard( - mSettings.getCurrent(), switcher.getKeyboardSwitchState()); - } - - @Override - public void onConfigurationChanged(final Configuration conf) { - SettingsValues settingsValues = mSettings.getCurrent(); - if (settingsValues.mDisplayOrientation != conf.orientation) { - mHandler.startOrientationChanging(); - mInputLogic.onOrientationChange(mSettings.getCurrent()); - } - if (settingsValues.mHasHardwareKeyboard != Settings.readHasHardwareKeyboard(conf)) { - // If the state of having a hardware keyboard changed, then we want to reload the - // settings to adjust for that. - // TODO: we should probably do this unconditionally here, rather than only when we - // have a change in hardware keyboard configuration. - loadSettings(); - settingsValues = mSettings.getCurrent(); - if (isImeSuppressedByHardwareKeyboard()) { - // We call cleanupInternalStateForFinishInput() because it's the right thing to do; - // however, it seems at the moment the framework is passing us a seemingly valid - // but actually non-functional InputConnection object. So if this bug ever gets - // fixed we'll be able to remove the composition, but until it is this code is - // actually not doing much. - cleanupInternalStateForFinishInput(); - } - } - super.onConfigurationChanged(conf); - } - - @Override - public void onInitializeInterface() { - mDisplayContext = getDisplayContext(); - mKeyboardSwitcher.updateKeyboardTheme(mDisplayContext); - } - - /** - * Returns the context object whose resources are adjusted to match the metrics of the display. - * - * Note that before {@link android.os.Build.VERSION_CODES#KITKAT}, there is no way to support - * multi-display scenarios, so the context object will just return the IME context itself. - * - * With initiating multi-display APIs from {@link android.os.Build.VERSION_CODES#KITKAT}, the - * context object has to return with re-creating the display context according the metrics - * of the display in runtime. - * - * Starts from {@link android.os.Build.VERSION_CODES#S_V2}, the returning context object has - * became to IME context self since it ends up capable of updating its resources internally. - * - * @see android.content.Context#createDisplayContext(Display) - */ - private @NonNull Context getDisplayContext() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - // createDisplayContext is not available. - return this; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2) { - // IME context sources is now managed by WindowProviderService from Android 12L. - return this; - } - // An issue in Q that non-activity components Resources / DisplayMetrics in - // Context doesn't well updated when the IME window moving to external display. - // Currently we do a workaround is to create new display context directly and re-init - // keyboard layout with this context. - final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - return createDisplayContext(wm.getDefaultDisplay()); - } - - @Override - public View onCreateInputView() { - StatsUtils.onCreateInputView(); - return mKeyboardSwitcher.onCreateInputView(mDisplayContext, - mIsHardwareAcceleratedDrawingEnabled); - } - - @Override - public void setInputView(final View view) { - super.setInputView(view); - mInputView = view; - mInsetsUpdater = ViewOutlineProviderCompatUtils.setInsetsOutlineProvider(view); - updateSoftInputWindowLayoutParameters(); - mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view); - if (hasSuggestionStripView()) { - mSuggestionStripView.setListener(this, view); - } - } - - @Override - public void setCandidatesView(final View view) { - // To ensure that CandidatesView will never be set. - } - - @Override - public void onStartInput(final EditorInfo editorInfo, final boolean restarting) { - mHandler.onStartInput(editorInfo, restarting); - } - - @Override - public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) { - mHandler.onStartInputView(editorInfo, restarting); - mStatsUtilsManager.onStartInputView(); - } - - @Override - public void onFinishInputView(final boolean finishingInput) { - StatsUtils.onFinishInputView(); - mHandler.onFinishInputView(finishingInput); - mStatsUtilsManager.onFinishInputView(); - mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; - } - - @Override - public void onFinishInput() { - mHandler.onFinishInput(); - } - - @Override - public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) { - // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged() - // is not guaranteed. It may even be called at the same time on a different thread. - InputMethodSubtype oldSubtype = mRichImm.getCurrentSubtype().getRawSubtype(); - StatsUtils.onSubtypeChanged(oldSubtype, subtype); - mRichImm.onSubtypeChanged(subtype); - mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype), - mSettings.getCurrent()); - loadKeyboard(); - } - - void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) { - super.onStartInput(editorInfo, restarting); - - // If the primary hint language does not match the current subtype language, then try - // to switch to the primary hint language. - // TODO: Support all the locales in EditorInfo#hintLocales. - final Locale primaryHintLocale = EditorInfoCompatUtils.getPrimaryHintLocale(editorInfo); - if (primaryHintLocale == null) { - return; - } - final InputMethodSubtype newSubtype = mRichImm.findSubtypeByLocale(primaryHintLocale); - if (newSubtype == null || newSubtype.equals(mRichImm.getCurrentSubtype().getRawSubtype())) { - return; - } - mHandler.postSwitchLanguage(newSubtype); - } - - @SuppressWarnings("deprecation") - void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) { - super.onStartInputView(editorInfo, restarting); - - mDictionaryFacilitator.onStartInput(); - // Switch to the null consumer to handle cases leading to early exit below, for which we - // also wouldn't be consuming gesture data. - mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; - mRichImm.refreshSubtypeCaches(); - final KeyboardSwitcher switcher = mKeyboardSwitcher; - switcher.updateKeyboardTheme(mDisplayContext); - final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView(); - // If we are starting input in a different text field from before, we'll have to reload - // settings, so currentSettingsValues can't be final. - SettingsValues currentSettingsValues = mSettings.getCurrent(); - - if (editorInfo == null) { - Log.e(TAG, "Null EditorInfo in onStartInputView()"); - if (DebugFlags.DEBUG_ENABLED) { - throw new NullPointerException("Null EditorInfo in onStartInputView()"); - } - return; - } - if (DebugFlags.DEBUG_ENABLED) { - Log.d(TAG, "onStartInputView: editorInfo:" - + String.format("inputType=0x%08x imeOptions=0x%08x", - editorInfo.inputType, editorInfo.imeOptions)); - Log.d(TAG, "All caps = " - + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) - + ", sentence caps = " - + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) - + ", word caps = " - + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0)); - } - Log.i(TAG, "Starting input. Cursor position = " - + editorInfo.initialSelStart + "," + editorInfo.initialSelEnd); - // TODO: Consolidate these checks with {@link InputAttributes}. - if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) { - Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions); - Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead"); - } - if (InputAttributes.inPrivateImeOptions(getPackageName(), FORCE_ASCII, editorInfo)) { - Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions); - Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead"); - } - - // In landscape mode, this method gets called without the input view being created. - if (mainKeyboardView == null) { - return; - } - - // Update to a gesture consumer with the current editor and IME state. - mGestureConsumer = GestureConsumer.newInstance(editorInfo, - mInputLogic.getPrivateCommandPerformer(), - mRichImm.getCurrentSubtypeLocale(), - switcher.getKeyboard()); - - // Forward this event to the accessibility utilities, if enabled. - final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance(); - if (accessUtils.isTouchExplorationEnabled()) { - accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting); - } - - final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo); - final boolean isDifferentTextField = !restarting || inputTypeChanged; - - StatsUtils.onStartInputView(editorInfo.inputType, - Settings.getInstance().getCurrent().mDisplayOrientation, - !isDifferentTextField); - - // The EditorInfo might have a flag that affects fullscreen mode. - // Note: This call should be done by InputMethodService? - updateFullscreenMode(); - - // ALERT: settings have not been reloaded and there is a chance they may be stale. - // In the practice, if it is, we should have gotten onConfigurationChanged so it should - // be fine, but this is horribly confusing and must be fixed AS SOON AS POSSIBLE. - - // In some cases the input connection has not been reset yet and we can't access it. In - // this case we will need to call loadKeyboard() later, when it's accessible, so that we - // can go into the correct mode, so we need to do some housekeeping here. - final boolean needToCallLoadKeyboardLater; - final Suggest suggest = mInputLogic.mSuggest; - if (!isImeSuppressedByHardwareKeyboard()) { - // The app calling setText() has the effect of clearing the composing - // span, so we should reset our state unconditionally, even if restarting is true. - // We also tell the input logic about the combining rules for the current subtype, so - // it can adjust its combiners if needed. - mInputLogic.startInput(mRichImm.getCombiningRulesExtraValueOfCurrentSubtype(), - currentSettingsValues); - - resetDictionaryFacilitatorIfNecessary(); - - // TODO[IL]: Can the following be moved to InputLogic#startInput? - if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess( - editorInfo.initialSelStart, editorInfo.initialSelEnd, - false /* shouldFinishComposition */)) { - // Sometimes, while rotating, for some reason the framework tells the app we are not - // connected to it and that means we can't refresh the cache. In this case, schedule - // a refresh later. - // We try resetting the caches up to 5 times before giving up. - mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */); - // mLastSelection{Start,End} are reset later in this method, no need to do it here - needToCallLoadKeyboardLater = true; - } else { - // When rotating, and when input is starting again in a field from where the focus - // didn't move (the keyboard having been closed with the back key), - // initialSelStart and initialSelEnd sometimes are lying. Make a best effort to - // work around this bug. - mInputLogic.mConnection.tryFixLyingCursorPosition(); - mHandler.postResumeSuggestionsForStartInput(true /* shouldDelay */); - needToCallLoadKeyboardLater = false; - } - } else { - // If we have a hardware keyboard we don't need to call loadKeyboard later anyway. - needToCallLoadKeyboardLater = false; - } - - if (isDifferentTextField || - !currentSettingsValues.hasSameOrientation(getResources().getConfiguration())) { - loadSettings(); - } - if (isDifferentTextField) { - mainKeyboardView.closing(); - currentSettingsValues = mSettings.getCurrent(); - - if (currentSettingsValues.mAutoCorrectionEnabledPerUserSettings) { - suggest.setAutoCorrectionThreshold( - currentSettingsValues.mAutoCorrectionThreshold); - } - suggest.setPlausibilityThreshold(currentSettingsValues.mPlausibilityThreshold); - - switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(), - getCurrentRecapitalizeState()); - if (needToCallLoadKeyboardLater) { - // If we need to call loadKeyboard again later, we need to save its state now. The - // later call will be done in #retryResetCaches. - switcher.saveKeyboardState(); - } - } else if (restarting) { - // TODO: Come up with a more comprehensive way to reset the keyboard layout when - // a keyboard layout set doesn't get reloaded in this method. - switcher.resetKeyboardStateToAlphabet(getCurrentAutoCapsState(), - getCurrentRecapitalizeState()); - // In apps like Talk, we come here when the text is sent and the field gets emptied and - // we need to re-evaluate the shift state, but not the whole layout which would be - // disruptive. - // Space state must be updated before calling updateShiftState - switcher.requestUpdatingShiftState(getCurrentAutoCapsState(), - getCurrentRecapitalizeState()); - } - // This will set the punctuation suggestions if next word suggestion is off; - // otherwise it will clear the suggestion strip. - setNeutralSuggestionStrip(); - - mHandler.cancelUpdateSuggestionStrip(); - - mainKeyboardView.setMainDictionaryAvailability( - mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()); - mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn, - currentSettingsValues.mKeyPreviewPopupDismissDelay); - mainKeyboardView.setSlidingKeyInputPreviewEnabled( - currentSettingsValues.mSlidingKeyInputPreviewEnabled); - mainKeyboardView.setGestureHandlingEnabledByUser( - currentSettingsValues.mGestureInputEnabled, - currentSettingsValues.mGestureTrailEnabled, - currentSettingsValues.mGestureFloatingPreviewTextEnabled); - - if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); - } - - @Override - public void onWindowShown() { - super.onWindowShown(); - setNavigationBarVisibility(isInputViewShown()); - } - - @Override - public void onWindowHidden() { - super.onWindowHidden(); - final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - if (mainKeyboardView != null) { - mainKeyboardView.closing(); - } - setNavigationBarVisibility(false); - } - - void onFinishInputInternal() { - super.onFinishInput(); - - mDictionaryFacilitator.onFinishInput(this); - final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - if (mainKeyboardView != null) { - mainKeyboardView.closing(); - } - } - - void onFinishInputViewInternal(final boolean finishingInput) { - super.onFinishInputView(finishingInput); - cleanupInternalStateForFinishInput(); - } - - private void cleanupInternalStateForFinishInput() { - // Remove pending messages related to update suggestions - mHandler.cancelUpdateSuggestionStrip(); - // Should do the following in onFinishInputInternal but until JB MR2 it's not called :( - mInputLogic.finishInput(); - } - - protected void deallocateMemory() { - mKeyboardSwitcher.deallocateMemory(); - } - - @Override - public void onUpdateSelection(final int oldSelStart, final int oldSelEnd, - final int newSelStart, final int newSelEnd, - final int composingSpanStart, final int composingSpanEnd) { - super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, - composingSpanStart, composingSpanEnd); - if (DebugFlags.DEBUG_ENABLED) { - Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd - + ", nss=" + newSelStart + ", nse=" + newSelEnd - + ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd); - } - - // This call happens whether our view is displayed or not, but if it's not then we should - // not attempt recorrection. This is true even with a hardware keyboard connected: if the - // view is not displayed we have no means of showing suggestions anyway, and if it is then - // we want to show suggestions anyway. - final SettingsValues settingsValues = mSettings.getCurrent(); - if (isInputViewShown() - && mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, - settingsValues)) { - mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), - getCurrentRecapitalizeState()); - } - } - - /** - * This is called when the user has clicked on the extracted text view, - * when running in fullscreen mode. The default implementation hides - * the suggestions view when this happens, but only if the extracted text - * editor has a vertical scroll bar because its text doesn't fit. - * Here we override the behavior due to the possibility that a re-correction could - * cause the suggestions strip to disappear and re-appear. - */ - @Override - public void onExtractedTextClicked() { - if (mSettings.getCurrent().needsToLookupSuggestions()) { - return; - } - - super.onExtractedTextClicked(); - } - - /** - * This is called when the user has performed a cursor movement in the - * extracted text view, when it is running in fullscreen mode. The default - * implementation hides the suggestions view when a vertical movement - * happens, but only if the extracted text editor has a vertical scroll bar - * because its text doesn't fit. - * Here we override the behavior due to the possibility that a re-correction could - * cause the suggestions strip to disappear and re-appear. - */ - @Override - public void onExtractedCursorMovement(final int dx, final int dy) { - if (mSettings.getCurrent().needsToLookupSuggestions()) { - return; - } - - super.onExtractedCursorMovement(dx, dy); - } - - @Override - public void hideWindow() { - mKeyboardSwitcher.onHideWindow(); - - if (TRACE) Debug.stopMethodTracing(); - if (isShowingOptionDialog()) { - mOptionsDialog.dismiss(); - mOptionsDialog = null; - } - super.hideWindow(); - } - - @Override - public void onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions) { - if (DebugFlags.DEBUG_ENABLED) { - Log.i(TAG, "Received completions:"); - if (applicationSpecifiedCompletions != null) { - for (int i = 0; i < applicationSpecifiedCompletions.length; i++) { - Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]); - } - } - } - if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) { - return; - } - // If we have an update request in flight, we need to cancel it so it does not override - // these completions. - mHandler.cancelUpdateSuggestionStrip(); - if (applicationSpecifiedCompletions == null) { - setNeutralSuggestionStrip(); - return; - } - - final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords = - SuggestedWords.getFromApplicationSpecifiedCompletions( - applicationSpecifiedCompletions); - final SuggestedWords suggestedWords = new SuggestedWords(applicationSuggestedWords, - null /* rawSuggestions */, - null /* typedWord */, - false /* typedWordValid */, - false /* willAutoCorrect */, - false /* isObsoleteSuggestions */, - SuggestedWords.INPUT_STYLE_APPLICATION_SPECIFIED /* inputStyle */, - SuggestedWords.NOT_A_SEQUENCE_NUMBER); - // When in fullscreen mode, show completions generated by the application forcibly - setSuggestedWords(suggestedWords); - } - - @Override - public void onComputeInsets(final InputMethodService.Insets outInsets) { - super.onComputeInsets(outInsets); - // This method may be called before {@link #setInputView(View)}. - if (mInputView == null) { - return; - } - final SettingsValues settingsValues = mSettings.getCurrent(); - final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView(); - if (visibleKeyboardView == null || !hasSuggestionStripView()) { - return; - } - final int inputHeight = mInputView.getHeight(); - if (isImeSuppressedByHardwareKeyboard() && !visibleKeyboardView.isShown()) { - // If there is a hardware keyboard and a visible software keyboard view has been hidden, - // no visual element will be shown on the screen. - outInsets.contentTopInsets = inputHeight; - outInsets.visibleTopInsets = inputHeight; - mInsetsUpdater.setInsets(outInsets); - return; - } - final int suggestionsHeight = (!mKeyboardSwitcher.isShowingEmojiPalettes() - && mSuggestionStripView.getVisibility() == View.VISIBLE) - ? mSuggestionStripView.getHeight() : 0; - final int visibleTopY = inputHeight - visibleKeyboardView.getHeight() - suggestionsHeight; - mSuggestionStripView.setMoreSuggestionsHeight(visibleTopY); - // Need to set expanded touchable region only if a keyboard view is being shown. - if (visibleKeyboardView.isShown()) { - final int touchLeft = 0; - final int touchTop = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY; - final int touchRight = visibleKeyboardView.getWidth(); - final int touchBottom = inputHeight; - outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; - outInsets.touchableRegion.set(touchLeft, touchTop, touchRight, touchBottom); - } - outInsets.contentTopInsets = visibleTopY; - outInsets.visibleTopInsets = visibleTopY; - mInsetsUpdater.setInsets(outInsets); - } - - public void startShowingInputView(final boolean needsToLoadKeyboard) { - mIsExecutingStartShowingInputView = true; - // This {@link #showWindow(boolean)} will eventually call back - // {@link #onEvaluateInputViewShown()}. - showWindow(true /* showInput */); - mIsExecutingStartShowingInputView = false; - if (needsToLoadKeyboard) { - loadKeyboard(); - } - } - - public void stopShowingInputView() { - showWindow(false /* showInput */); - } - - @Override - public boolean onShowInputRequested(final int flags, final boolean configChange) { - if (isImeSuppressedByHardwareKeyboard()) { - return true; - } - return super.onShowInputRequested(flags, configChange); - } - - @Override - public boolean onEvaluateInputViewShown() { - if (mIsExecutingStartShowingInputView) { - return true; - } - return super.onEvaluateInputViewShown(); - } - - @Override - public boolean onEvaluateFullscreenMode() { - final SettingsValues settingsValues = mSettings.getCurrent(); - if (isImeSuppressedByHardwareKeyboard()) { - // If there is a hardware keyboard, disable full screen mode. - return false; - } - // Reread resource value here, because this method is called by the framework as needed. - final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources()); - if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) { - // TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI - // implies NO_FULLSCREEN. However, the framework mistakenly does. i.e. NO_EXTRACT_UI - // without NO_FULLSCREEN doesn't work as expected. Because of this we need this - // hack for now. Let's get rid of this once the framework gets fixed. - final EditorInfo ei = getCurrentInputEditorInfo(); - return !(ei != null && ((ei.imeOptions & EditorInfo.IME_FLAG_NO_EXTRACT_UI) != 0)); - } - return false; - } - - @Override - public void updateFullscreenMode() { - super.updateFullscreenMode(); - updateSoftInputWindowLayoutParameters(); - } - - private void updateSoftInputWindowLayoutParameters() { - // Override layout parameters to expand {@link SoftInputWindow} to the entire screen. - // See {@link InputMethodService#setinputView(View)} and - // {@link SoftInputWindow#updateWidthHeight(WindowManager.LayoutParams)}. - final Window window = getWindow().getWindow(); - ViewLayoutUtils.updateLayoutHeightOf(window, LayoutParams.MATCH_PARENT); - // This method may be called before {@link #setInputView(View)}. - if (mInputView != null) { - // In non-fullscreen mode, {@link InputView} and its parent inputArea should expand to - // the entire screen and be placed at the bottom of {@link SoftInputWindow}. - // In fullscreen mode, these shouldn't expand to the entire screen and should be - // coexistent with {@link #mExtractedArea} above. - // See {@link InputMethodService#setInputView(View) and - // com.android.internal.R.layout.input_method.xml. - final int layoutHeight = isFullscreenMode() - ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT; - final View inputArea = window.findViewById(android.R.id.inputArea); - ViewLayoutUtils.updateLayoutHeightOf(inputArea, layoutHeight); - ViewLayoutUtils.updateLayoutGravityOf(inputArea, Gravity.BOTTOM); - ViewLayoutUtils.updateLayoutHeightOf(mInputView, layoutHeight); - } - } - - int getCurrentAutoCapsState() { - return mInputLogic.getCurrentAutoCapsState(mSettings.getCurrent()); - } - - int getCurrentRecapitalizeState() { - return mInputLogic.getCurrentRecapitalizeState(); - } - - /** - * @param codePoints code points to get coordinates for. - * @return x,y coordinates for this keyboard, as a flattened array. - */ - public int[] getCoordinatesForCurrentKeyboard(final int[] codePoints) { - final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - if (null == keyboard) { - return CoordinateUtils.newCoordinateArray(codePoints.length, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); - } - return keyboard.getCoordinates(codePoints); - } - - // Callback for the {@link SuggestionStripView}, to call when the important notice strip is - // pressed. - @Override - public void showImportantNoticeContents() { - PermissionsManager.get(this).requestPermissions( - this /* PermissionsResultCallback */, - null /* activity */, permission.READ_CONTACTS); - } - - @Override - public void onRequestPermissionsResult(boolean allGranted) { - ImportantNoticeUtils.updateContactsNoticeShown(this /* context */); - setNeutralSuggestionStrip(); - } - - public void displaySettingsDialog() { - if (isShowingOptionDialog()) { - return; - } - showSubtypeSelectorAndSettings(); - } - - @Override - public boolean onCustomRequest(final int requestCode) { - if (isShowingOptionDialog()) return false; - switch (requestCode) { - case Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER: - if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) { - mRichImm.getInputMethodManager().showInputMethodPicker(); - return true; - } - return false; - } - return false; - } - - private boolean isShowingOptionDialog() { - return mOptionsDialog != null && mOptionsDialog.isShowing(); - } - - public void switchLanguage(final InputMethodSubtype subtype) { - final IBinder token = getWindow().getWindow().getAttributes().token; - mRichImm.setInputMethodAndSubtype(token, subtype); - } - - // TODO: Revise the language switch key behavior to make it much smarter and more reasonable. - public void switchToNextSubtype() { - final IBinder token = getWindow().getWindow().getAttributes().token; - if (shouldSwitchToOtherInputMethods()) { - mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */); - return; - } - mSubtypeState.switchSubtype(token, mRichImm); - } - - // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for - // alphabetic shift and shift while in symbol layout and get rid of this method. - private int getCodePointForKeyboard(final int codePoint) { - if (Constants.CODE_SHIFT == codePoint) { - final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard(); - if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) { - return codePoint; - } - return Constants.CODE_SYMBOL_SHIFT; - } - return codePoint; - } - - // Implementation of {@link KeyboardActionListener}. - @Override - public void onCodeInput(final int codePoint, final int x, final int y, - final boolean isKeyRepeat) { - // TODO: this processing does not belong inside LatinIME, the caller should be doing this. - final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - // x and y include some padding, but everything down the line (especially native - // code) needs the coordinates in the keyboard frame. - // TODO: We should reconsider which coordinate system should be used to represent - // keyboard event. Also we should pull this up -- LatinIME has no business doing - // this transformation, it should be done already before calling onEvent. - final int keyX = mainKeyboardView.getKeyX(x); - final int keyY = mainKeyboardView.getKeyY(y); - final Event event = createSoftwareKeypressEvent(getCodePointForKeyboard(codePoint), - keyX, keyY, isKeyRepeat); - onEvent(event); - } - - // This method is public for testability of LatinIME, but also in the future it should - // completely replace #onCodeInput. - public void onEvent(@Nonnull final Event event) { - if (Constants.CODE_SHORTCUT == event.mKeyCode) { - mRichImm.switchToShortcutIme(this); - } - final InputTransaction completeInputTransaction = - mInputLogic.onCodeInput(mSettings.getCurrent(), event, - mKeyboardSwitcher.getKeyboardShiftMode(), - mKeyboardSwitcher.getCurrentKeyboardScriptId(), mHandler); - updateStateAfterInputTransaction(completeInputTransaction); - mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); - } - - // A helper method to split the code point and the key code. Ultimately, they should not be - // squashed into the same variable, and this method should be removed. - // public for testing, as we don't want to copy the same logic into test code - @Nonnull - public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX, - final int keyY, final boolean isKeyRepeat) { - final int keyCode; - final int codePoint; - if (keyCodeOrCodePoint <= 0) { - keyCode = keyCodeOrCodePoint; - codePoint = Event.NOT_A_CODE_POINT; - } else { - keyCode = Event.NOT_A_KEY_CODE; - codePoint = keyCodeOrCodePoint; - } - return Event.createSoftwareKeypressEvent(codePoint, keyCode, keyX, keyY, isKeyRepeat); - } - - // Called from PointerTracker through the KeyboardActionListener interface - @Override - public void onTextInput(final String rawText) { - // TODO: have the keyboard pass the correct key code when we need it. - final Event event = Event.createSoftwareTextEvent(rawText, Constants.CODE_OUTPUT_TEXT); - final InputTransaction completeInputTransaction = - mInputLogic.onTextInput(mSettings.getCurrent(), event, - mKeyboardSwitcher.getKeyboardShiftMode(), mHandler); - updateStateAfterInputTransaction(completeInputTransaction); - mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); - } - - @Override - public void onStartBatchInput() { - mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler); - mGestureConsumer.onGestureStarted( - mRichImm.getCurrentSubtypeLocale(), - mKeyboardSwitcher.getKeyboard()); - } - - @Override - public void onUpdateBatchInput(final InputPointers batchPointers) { - mInputLogic.onUpdateBatchInput(batchPointers); - } - - @Override - public void onEndBatchInput(final InputPointers batchPointers) { - mInputLogic.onEndBatchInput(batchPointers); - mGestureConsumer.onGestureCompleted(batchPointers); - } - - @Override - public void onCancelBatchInput() { - mInputLogic.onCancelBatchInput(mHandler); - mGestureConsumer.onGestureCanceled(); - } - - /** - * To be called after the InputLogic has gotten a chance to act on the suggested words by the - * IME for the full gesture, possibly updating the TextView to reflect the first suggestion. - * <p> - * This method must be run on the UI Thread. - * @param suggestedWords suggested words by the IME for the full gesture. - */ - public void onTailBatchInputResultShown(final SuggestedWords suggestedWords) { - mGestureConsumer.onImeSuggestionsProcessed(suggestedWords, - mInputLogic.getComposingStart(), mInputLogic.getComposingLength(), - mDictionaryFacilitator); - } - - // This method must run on the UI Thread. - void showGesturePreviewAndSuggestionStrip(@Nonnull final SuggestedWords suggestedWords, - final boolean dismissGestureFloatingPreviewText) { - showSuggestionStrip(suggestedWords); - final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - mainKeyboardView.showGestureFloatingPreviewText(suggestedWords, - dismissGestureFloatingPreviewText /* dismissDelayed */); - } - - // Called from PointerTracker through the KeyboardActionListener interface - @Override - public void onFinishSlidingInput() { - // User finished sliding input. - mKeyboardSwitcher.onFinishSlidingInput(getCurrentAutoCapsState(), - getCurrentRecapitalizeState()); - } - - // Called from PointerTracker through the KeyboardActionListener interface - @Override - public void onCancelInput() { - // User released a finger outside any key - // Nothing to do so far. - } - - public boolean hasSuggestionStripView() { - return null != mSuggestionStripView; - } - - private void setSuggestedWords(final SuggestedWords suggestedWords) { - final SettingsValues currentSettingsValues = mSettings.getCurrent(); - mInputLogic.setSuggestedWords(suggestedWords); - // TODO: Modify this when we support suggestions with hard keyboard - if (!hasSuggestionStripView()) { - return; - } - if (!onEvaluateInputViewShown()) { - return; - } - - final boolean shouldShowImportantNotice = - ImportantNoticeUtils.shouldShowImportantNotice(this, currentSettingsValues); - final boolean shouldShowSuggestionCandidates = - currentSettingsValues.mInputAttributes.mShouldShowSuggestions - && currentSettingsValues.isSuggestionsEnabledPerUserSettings(); - final boolean shouldShowSuggestionsStripUnlessPassword = shouldShowImportantNotice - || currentSettingsValues.mShowsVoiceInputKey - || shouldShowSuggestionCandidates - || currentSettingsValues.isApplicationSpecifiedCompletionsOn(); - final boolean shouldShowSuggestionsStrip = shouldShowSuggestionsStripUnlessPassword - && !currentSettingsValues.mInputAttributes.mIsPasswordField; - mSuggestionStripView.updateVisibility(shouldShowSuggestionsStrip, isFullscreenMode()); - if (!shouldShowSuggestionsStrip) { - return; - } - - final boolean isEmptyApplicationSpecifiedCompletions = - currentSettingsValues.isApplicationSpecifiedCompletionsOn() - && suggestedWords.isEmpty(); - final boolean noSuggestionsFromDictionaries = suggestedWords.isEmpty() - || suggestedWords.isPunctuationSuggestions() - || isEmptyApplicationSpecifiedCompletions; - final boolean isBeginningOfSentencePrediction = (suggestedWords.mInputStyle - == SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION); - final boolean noSuggestionsToOverrideImportantNotice = noSuggestionsFromDictionaries - || isBeginningOfSentencePrediction; - if (shouldShowImportantNotice && noSuggestionsToOverrideImportantNotice) { - if (mSuggestionStripView.maybeShowImportantNoticeTitle()) { - return; - } - } - - if (currentSettingsValues.isSuggestionsEnabledPerUserSettings() - || currentSettingsValues.isApplicationSpecifiedCompletionsOn() - // We should clear the contextual strip if there is no suggestion from dictionaries. - || noSuggestionsFromDictionaries) { - mSuggestionStripView.setSuggestions(suggestedWords, - mRichImm.getCurrentSubtype().isRtlSubtype()); - } - } - - // TODO[IL]: Move this out of LatinIME. - public void getSuggestedWords(final int inputStyle, final int sequenceNumber, - final OnGetSuggestedWordsCallback callback) { - final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - if (keyboard == null) { - callback.onGetSuggestedWords(SuggestedWords.getEmptyInstance()); - return; - } - mInputLogic.getSuggestedWords(mSettings.getCurrent(), keyboard, - mKeyboardSwitcher.getKeyboardShiftMode(), inputStyle, sequenceNumber, callback); - } - - @Override - public void showSuggestionStrip(final SuggestedWords suggestedWords) { - if (suggestedWords.isEmpty()) { - setNeutralSuggestionStrip(); - } else { - setSuggestedWords(suggestedWords); - } - // Cache the auto-correction in accessibility code so we can speak it if the user - // touches a key that will insert it. - AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords); - } - - // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener} - // interface - @Override - public void pickSuggestionManually(final SuggestedWordInfo suggestionInfo) { - final InputTransaction completeInputTransaction = mInputLogic.onPickSuggestionManually( - mSettings.getCurrent(), suggestionInfo, - mKeyboardSwitcher.getKeyboardShiftMode(), - mKeyboardSwitcher.getCurrentKeyboardScriptId(), - mHandler); - updateStateAfterInputTransaction(completeInputTransaction); - } - - // This will show either an empty suggestion strip (if prediction is enabled) or - // punctuation suggestions (if it's disabled). - @Override - public void setNeutralSuggestionStrip() { - final SettingsValues currentSettings = mSettings.getCurrent(); - final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled - ? SuggestedWords.getEmptyInstance() - : currentSettings.mSpacingAndPunctuations.mSuggestPuncList; - setSuggestedWords(neutralSuggestions); - } - - // Outside LatinIME, only used by the {@link InputTestsBase} test suite. - @UsedForTesting - void loadKeyboard() { - // Since we are switching languages, the most urgent thing is to let the keyboard graphics - // update. LoadKeyboard does that, but we need to wait for buffer flip for it to be on - // the screen. Anything we do right now will delay this, so wait until the next frame - // before we do the rest, like reopening dictionaries and updating suggestions. So we - // post a message. - mHandler.postReopenDictionaries(); - loadSettings(); - if (mKeyboardSwitcher.getMainKeyboardView() != null) { - // Reload keyboard because the current language has been changed. - mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent(), - getCurrentAutoCapsState(), getCurrentRecapitalizeState()); - } - } - - /** - * After an input transaction has been executed, some state must be updated. This includes - * the shift state of the keyboard and suggestions. This method looks at the finished - * inputTransaction to find out what is necessary and updates the state accordingly. - * @param inputTransaction The transaction that has been executed. - */ - private void updateStateAfterInputTransaction(final InputTransaction inputTransaction) { - switch (inputTransaction.getRequiredShiftUpdate()) { - case InputTransaction.SHIFT_UPDATE_LATER: - mHandler.postUpdateShiftState(); - break; - case InputTransaction.SHIFT_UPDATE_NOW: - mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), - getCurrentRecapitalizeState()); - break; - default: // SHIFT_NO_UPDATE - } - if (inputTransaction.requiresUpdateSuggestions()) { - final int inputStyle; - if (inputTransaction.mEvent.isSuggestionStripPress()) { - // Suggestion strip press: no input. - inputStyle = SuggestedWords.INPUT_STYLE_NONE; - } else if (inputTransaction.mEvent.isGesture()) { - inputStyle = SuggestedWords.INPUT_STYLE_TAIL_BATCH; - } else { - inputStyle = SuggestedWords.INPUT_STYLE_TYPING; - } - mHandler.postUpdateSuggestionStrip(inputStyle); - } - if (inputTransaction.didAffectContents()) { - mSubtypeState.setCurrentSubtypeHasBeenUsed(); - } - } - - private void hapticAndAudioFeedback(final int code, final int repeatCount) { - final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView(); - if (keyboardView != null && keyboardView.isInDraggingFinger()) { - // No need to feedback while finger is dragging. - return; - } - if (repeatCount > 0) { - if (code == Constants.CODE_DELETE && !mInputLogic.mConnection.canDeleteCharacters()) { - // No need to feedback when repeat delete key will have no effect. - return; - } - // TODO: Use event time that the last feedback has been generated instead of relying on - // a repeat count to thin out feedback. - if (repeatCount % PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT == 0) { - return; - } - } - final AudioAndHapticFeedbackManager feedbackManager = - AudioAndHapticFeedbackManager.getInstance(); - if (repeatCount == 0) { - // TODO: Reconsider how to perform haptic feedback when repeating key. - feedbackManager.performHapticFeedback(keyboardView); - } - feedbackManager.performAudioFeedback(code); - } - - // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed; - // release matching call is {@link #onReleaseKey(int,boolean)} below. - @Override - public void onPressKey(final int primaryCode, final int repeatCount, - final boolean isSinglePointer) { - mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer, getCurrentAutoCapsState(), - getCurrentRecapitalizeState()); - hapticAndAudioFeedback(primaryCode, repeatCount); - } - - // Callback of the {@link KeyboardActionListener}. This is called when a key is released; - // press matching call is {@link #onPressKey(int,int,boolean)} above. - @Override - public void onReleaseKey(final int primaryCode, final boolean withSliding) { - mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding, getCurrentAutoCapsState(), - getCurrentRecapitalizeState()); - } - - private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) { - final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId); - if (null != decoder) return decoder; - // TODO: create the decoder according to the specification - final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId); - mHardwareEventDecoders.put(deviceId, newDecoder); - return newDecoder; - } - - // Hooks for hardware keyboard - @Override - public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { - if (mEmojiAltPhysicalKeyDetector == null) { - mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector( - getApplicationContext().getResources()); - } - mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent); - if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { - return super.onKeyDown(keyCode, keyEvent); - } - final Event event = getHardwareKeyEventDecoder( - keyEvent.getDeviceId()).decodeHardwareKey(keyEvent); - // If the event is not handled by LatinIME, we just pass it to the parent implementation. - // If it's handled, we return true because we did handle it. - if (event.isHandled()) { - mInputLogic.onCodeInput(mSettings.getCurrent(), event, - mKeyboardSwitcher.getKeyboardShiftMode(), - // TODO: this is not necessarily correct for a hardware keyboard right now - mKeyboardSwitcher.getCurrentKeyboardScriptId(), - mHandler); - return true; - } - return super.onKeyDown(keyCode, keyEvent); - } - - @Override - public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) { - if (mEmojiAltPhysicalKeyDetector == null) { - mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector( - getApplicationContext().getResources()); - } - mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent); - if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { - return super.onKeyUp(keyCode, keyEvent); - } - final long keyIdentifier = keyEvent.getDeviceId() << 32 + keyEvent.getKeyCode(); - if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) { - return true; - } - return super.onKeyUp(keyCode, keyEvent); - } - - // onKeyDown and onKeyUp are the main events we are interested in. There are two more events - // related to handling of hardware key events that we may want to implement in the future: - // boolean onKeyLongPress(final int keyCode, final KeyEvent event); - // boolean onKeyMultiple(final int keyCode, final int count, final KeyEvent event); - - // receive ringer mode change. - private final BroadcastReceiver mRingerModeChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - final String action = intent.getAction(); - if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { - AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged(); - } - } - }; - - /** - * Starts {@link android.app.Activity} on the same display where the IME is shown. - * - * @param intent {@link Intent} to be used to start {@link android.app.Activity}. - */ - private void startActivityOnTheSameDisplay(Intent intent) { - // Note that WindowManager#getDefaultDisplay() returns the display ID associated with the - // Context from which the WindowManager instance was obtained. Therefore the following code - // returns the display ID for the window where the IME is shown. - final int currentDisplayId = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)) - .getDefaultDisplay().getDisplayId(); - - startActivity(intent, - ActivityOptions.makeBasic().setLaunchDisplayId(currentDisplayId).toBundle()); - } - - void launchSettings(final String extraEntryValue) { - mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR); - requestHideSelf(0); - final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - if (mainKeyboardView != null) { - mainKeyboardView.closing(); - } - final Intent intent = new Intent(); - intent.setClass(LatinIME.this, SettingsActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(SettingsActivity.EXTRA_SHOW_HOME_AS_UP, false); - intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY, extraEntryValue); - startActivityOnTheSameDisplay(intent); - } - - private void showSubtypeSelectorAndSettings() { - final CharSequence title = getString(R.string.english_ime_input_options); - // TODO: Should use new string "Select active input modes". - final CharSequence languageSelectionTitle = getString(R.string.language_selection_title); - final CharSequence[] items = new CharSequence[] { - languageSelectionTitle, - getString(ApplicationUtils.getActivityTitleResId(this, SettingsActivity.class)) - }; - final String imeId = mRichImm.getInputMethodIdOfThisIme(); - final OnClickListener listener = new OnClickListener() { - @Override - public void onClick(DialogInterface di, int position) { - di.dismiss(); - switch (position) { - case 0: - final Intent intent = IntentUtils.getInputLanguageSelectionIntent( - imeId, - Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(Intent.EXTRA_TITLE, languageSelectionTitle); - startActivityOnTheSameDisplay(intent); - break; - case 1: - launchSettings(SettingsActivity.EXTRA_ENTRY_VALUE_LONG_PRESS_COMMA); - break; - } - } - }; - final AlertDialog.Builder builder = new AlertDialog.Builder( - DialogUtils.getPlatformDialogThemeContext(this)); - builder.setItems(items, listener).setTitle(title); - final AlertDialog dialog = builder.create(); - dialog.setCancelable(true /* cancelable */); - dialog.setCanceledOnTouchOutside(true /* cancelable */); - showOptionDialog(dialog); - } - - // TODO: Move this method out of {@link LatinIME}. - private void showOptionDialog(final AlertDialog dialog) { - final IBinder windowToken = mKeyboardSwitcher.getMainKeyboardView().getWindowToken(); - if (windowToken == null) { - return; - } - - final Window window = dialog.getWindow(); - final WindowManager.LayoutParams lp = window.getAttributes(); - lp.token = windowToken; - lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; - window.setAttributes(lp); - window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - - mOptionsDialog = dialog; - dialog.show(); - } - - @UsedForTesting - SuggestedWords getSuggestedWordsForTest() { - // You may not use this method for anything else than debug - return DebugFlags.DEBUG_ENABLED ? mInputLogic.mSuggestedWords : null; - } - - // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME. - @UsedForTesting - void waitForLoadingDictionaries(final long timeout, final TimeUnit unit) - throws InterruptedException { - mDictionaryFacilitator.waitForLoadingDictionariesForTesting(timeout, unit); - } - - // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly. - @UsedForTesting - void replaceDictionariesForTest(final Locale locale) { - final SettingsValues settingsValues = mSettings.getCurrent(); - mDictionaryFacilitator.resetDictionaries(this, locale, - settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, - false /* forceReloadMainDictionary */, - settingsValues.mAccount, "", /* dictionaryNamePrefix */ - this /* DictionaryInitializationListener */); - } - - // DO NOT USE THIS for any other purpose than testing. - @UsedForTesting - void clearPersonalizedDictionariesForTest() { - mDictionaryFacilitator.clearUserHistoryDictionary(this); - } - - @UsedForTesting - List<InputMethodSubtype> getEnabledSubtypesForTest() { - return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList( - true /* allowsImplicitlySelectedSubtypes */) : new ArrayList<InputMethodSubtype>(); - } - - public void dumpDictionaryForDebug(final String dictName) { - if (!mDictionaryFacilitator.isActive()) { - resetDictionaryFacilitatorIfNecessary(); - } - mDictionaryFacilitator.dumpDictionaryForDebug(dictName); - } - - public void debugDumpStateAndCrashWithException(final String context) { - final SettingsValues settingsValues = mSettings.getCurrent(); - final StringBuilder s = new StringBuilder(settingsValues.toString()); - s.append("\nAttributes : ").append(settingsValues.mInputAttributes) - .append("\nContext : ").append(context); - throw new RuntimeException(s.toString()); - } - - @Override - protected void dump(final FileDescriptor fd, final PrintWriter fout, final String[] args) { - super.dump(fd, fout, args); - - final Printer p = new PrintWriterPrinter(fout); - p.println("LatinIME state :"); - p.println(" VersionCode = " + ApplicationUtils.getVersionCode(this)); - p.println(" VersionName = " + ApplicationUtils.getVersionName(this)); - final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1; - p.println(" Keyboard mode = " + keyboardMode); - final SettingsValues settingsValues = mSettings.getCurrent(); - p.println(settingsValues.dump()); - p.println(mDictionaryFacilitator.dump(this /* context */)); - // TODO: Dump all settings values - } - - public boolean shouldSwitchToOtherInputMethods() { - // TODO: Revisit here to reorganize the settings. Probably we can/should use different - // strategy once the implementation of - // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well. - final boolean fallbackValue = mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList; - final IBinder token = getWindow().getWindow().getAttributes().token; - if (token == null) { - return fallbackValue; - } - return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue); - } - - public boolean shouldShowLanguageSwitchKey() { - // TODO: Revisit here to reorganize the settings. Probably we can/should use different - // strategy once the implementation of - // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well. - final boolean fallbackValue = mSettings.getCurrent().isLanguageSwitchKeyEnabled(); - final IBinder token = getWindow().getWindow().getAttributes().token; - if (token == null) { - return fallbackValue; - } - return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue); - } - - private void setNavigationBarVisibility(final boolean visible) { - if (BuildCompatUtils.EFFECTIVE_SDK_INT > Build.VERSION_CODES.M) { - // For N and later, IMEs can specify Color.TRANSPARENT to make the navigation bar - // transparent. For other colors the system uses the default color. - getWindow().getWindow().setNavigationBarColor( - visible ? Color.BLACK : Color.TRANSPARENT); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/NgramContext.java b/java/src/com/android/inputmethod/latin/NgramContext.java deleted file mode 100644 index 9682fb8a4..000000000 --- a/java/src/com/android/inputmethod/latin/NgramContext.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin; - -import android.text.TextUtils; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; - -import java.util.ArrayList; -import java.util.Arrays; - -import javax.annotation.Nonnull; - -/** - * Class to represent information of previous words. This class is used to add n-gram entries - * into binary dictionaries, to get predictions, and to get suggestions. - */ -public class NgramContext { - @Nonnull - public static final NgramContext EMPTY_PREV_WORDS_INFO = - new NgramContext(WordInfo.EMPTY_WORD_INFO); - @Nonnull - public static final NgramContext BEGINNING_OF_SENTENCE = - new NgramContext(WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO); - - public static final String BEGINNING_OF_SENTENCE_TAG = "<S>"; - - public static final String CONTEXT_SEPARATOR = " "; - - public static NgramContext getEmptyPrevWordsContext(int maxPrevWordCount) { - return new NgramContext(maxPrevWordCount, WordInfo.EMPTY_WORD_INFO); - } - - /** - * Word information used to represent previous words information. - */ - public static class WordInfo { - @Nonnull - public static final WordInfo EMPTY_WORD_INFO = new WordInfo(null); - @Nonnull - public static final WordInfo BEGINNING_OF_SENTENCE_WORD_INFO = new WordInfo(); - - // This is an empty char sequence when mIsBeginningOfSentence is true. - public final CharSequence mWord; - // TODO: Have sentence separator. - // Whether the current context is beginning of sentence or not. This is true when composing - // at the beginning of an input field or composing a word after a sentence separator. - public final boolean mIsBeginningOfSentence; - - // Beginning of sentence. - private WordInfo() { - mWord = ""; - mIsBeginningOfSentence = true; - } - - public WordInfo(final CharSequence word) { - mWord = word; - mIsBeginningOfSentence = false; - } - - public boolean isValid() { - return mWord != null; - } - - @Override - public int hashCode() { - return Arrays.hashCode(new Object[] { mWord, mIsBeginningOfSentence } ); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof WordInfo)) return false; - final WordInfo wordInfo = (WordInfo)o; - if (mWord == null || wordInfo.mWord == null) { - return mWord == wordInfo.mWord - && mIsBeginningOfSentence == wordInfo.mIsBeginningOfSentence; - } - return TextUtils.equals(mWord, wordInfo.mWord) - && mIsBeginningOfSentence == wordInfo.mIsBeginningOfSentence; - } - } - - // The words immediately before the considered word. EMPTY_WORD_INFO element means we don't - // have any context for that previous word including the "beginning of sentence context" - we - // just don't know what to predict using the information. An example of that is after a comma. - // For simplicity of implementation, elements may also be EMPTY_WORD_INFO transiently after the - // WordComposer was reset and before starting a new composing word, but we should never be - // calling getSuggetions* in this situation. - private final WordInfo[] mPrevWordsInfo; - private final int mPrevWordsCount; - - private final int mMaxPrevWordCount; - - // Construct from the previous word information. - public NgramContext(final WordInfo... prevWordsInfo) { - this(DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM, prevWordsInfo); - } - - public NgramContext(final int maxPrevWordCount, final WordInfo... prevWordsInfo) { - mPrevWordsInfo = prevWordsInfo; - mPrevWordsCount = prevWordsInfo.length; - mMaxPrevWordCount = maxPrevWordCount; - } - - /** - * Create next prevWordsInfo using current prevWordsInfo. - */ - @Nonnull - public NgramContext getNextNgramContext(final WordInfo wordInfo) { - final int nextPrevWordCount = Math.min(mMaxPrevWordCount, mPrevWordsCount + 1); - final WordInfo[] prevWordsInfo = new WordInfo[nextPrevWordCount]; - prevWordsInfo[0] = wordInfo; - System.arraycopy(mPrevWordsInfo, 0, prevWordsInfo, 1, nextPrevWordCount - 1); - return new NgramContext(mMaxPrevWordCount, prevWordsInfo); - } - - - /** - * Extracts the previous words context. - * - * @return a String with the previous words separated by white space. - */ - public String extractPrevWordsContext() { - final ArrayList<String> terms = new ArrayList<>(); - for (int i = mPrevWordsInfo.length - 1; i >= 0; --i) { - if (mPrevWordsInfo[i] != null && mPrevWordsInfo[i].isValid()) { - final NgramContext.WordInfo wordInfo = mPrevWordsInfo[i]; - if (wordInfo.mIsBeginningOfSentence) { - terms.add(BEGINNING_OF_SENTENCE_TAG); - } else { - final String term = wordInfo.mWord.toString(); - if (!term.isEmpty()) { - terms.add(term); - } - } - } - } - return TextUtils.join(CONTEXT_SEPARATOR, terms); - } - - /** - * Extracts the previous words context. - * - * @return a String array with the previous words. - */ - public String[] extractPrevWordsContextArray() { - final ArrayList<String> prevTermList = new ArrayList<>(); - for (int i = mPrevWordsInfo.length - 1; i >= 0; --i) { - if (mPrevWordsInfo[i] != null && mPrevWordsInfo[i].isValid()) { - final NgramContext.WordInfo wordInfo = mPrevWordsInfo[i]; - if (wordInfo.mIsBeginningOfSentence) { - prevTermList.add(BEGINNING_OF_SENTENCE_TAG); - } else { - final String term = wordInfo.mWord.toString(); - if (!term.isEmpty()) { - prevTermList.add(term); - } - } - } - } - final String[] contextStringArray = prevTermList.toArray(new String[prevTermList.size()]); - return contextStringArray; - } - - public boolean isValid() { - return mPrevWordsCount > 0 && mPrevWordsInfo[0].isValid(); - } - - public boolean isBeginningOfSentenceContext() { - return mPrevWordsCount > 0 && mPrevWordsInfo[0].mIsBeginningOfSentence; - } - - // n is 1-indexed. - // TODO: Remove - public CharSequence getNthPrevWord(final int n) { - if (n <= 0 || n > mPrevWordsCount) { - return null; - } - return mPrevWordsInfo[n - 1].mWord; - } - - // n is 1-indexed. - @UsedForTesting - public boolean isNthPrevWordBeginningOfSentence(final int n) { - if (n <= 0 || n > mPrevWordsCount) { - return false; - } - return mPrevWordsInfo[n - 1].mIsBeginningOfSentence; - } - - public void outputToArray(final int[][] codePointArrays, - final boolean[] isBeginningOfSentenceArray) { - for (int i = 0; i < mPrevWordsCount; i++) { - final WordInfo wordInfo = mPrevWordsInfo[i]; - if (wordInfo == null || !wordInfo.isValid()) { - codePointArrays[i] = new int[0]; - isBeginningOfSentenceArray[i] = false; - continue; - } - codePointArrays[i] = StringUtils.toCodePointArray(wordInfo.mWord); - isBeginningOfSentenceArray[i] = wordInfo.mIsBeginningOfSentence; - } - } - - public int getPrevWordCount() { - return mPrevWordsCount; - } - - @Override - public int hashCode() { - int hashValue = 0; - for (final WordInfo wordInfo : mPrevWordsInfo) { - if (wordInfo == null || !WordInfo.EMPTY_WORD_INFO.equals(wordInfo)) { - break; - } - hashValue ^= wordInfo.hashCode(); - } - return hashValue; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof NgramContext)) return false; - final NgramContext prevWordsInfo = (NgramContext)o; - - final int minLength = Math.min(mPrevWordsCount, prevWordsInfo.mPrevWordsCount); - for (int i = 0; i < minLength; i++) { - if (!mPrevWordsInfo[i].equals(prevWordsInfo.mPrevWordsInfo[i])) { - return false; - } - } - final WordInfo[] longerWordsInfo; - final int longerWordsInfoCount; - if (mPrevWordsCount > prevWordsInfo.mPrevWordsCount) { - longerWordsInfo = mPrevWordsInfo; - longerWordsInfoCount = mPrevWordsCount; - } else { - longerWordsInfo = prevWordsInfo.mPrevWordsInfo; - longerWordsInfoCount = prevWordsInfo.mPrevWordsCount; - } - for (int i = minLength; i < longerWordsInfoCount; i++) { - if (longerWordsInfo[i] != null - && !WordInfo.EMPTY_WORD_INFO.equals(longerWordsInfo[i])) { - return false; - } - } - return true; - } - - @Override - public String toString() { - final StringBuffer builder = new StringBuffer(); - for (int i = 0; i < mPrevWordsCount; i++) { - final WordInfo wordInfo = mPrevWordsInfo[i]; - builder.append("PrevWord["); - builder.append(i); - builder.append("]: "); - if (wordInfo == null) { - builder.append("null. "); - continue; - } - if (!wordInfo.isValid()) { - builder.append("Empty. "); - continue; - } - builder.append(wordInfo.mWord); - builder.append(", isBeginningOfSentence: "); - builder.append(wordInfo.mIsBeginningOfSentence); - builder.append(". "); - } - return builder.toString(); - } -} diff --git a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java deleted file mode 100644 index e2c562174..000000000 --- a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin; - -import com.android.inputmethod.keyboard.internal.KeySpecParser; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; - -import java.util.ArrayList; -import java.util.Arrays; - -import javax.annotation.Nullable; - -/** - * The extended {@link SuggestedWords} class to represent punctuation suggestions. - * - * Each punctuation specification string is the key specification that can be parsed by - * {@link KeySpecParser}. - */ -public final class PunctuationSuggestions extends SuggestedWords { - private PunctuationSuggestions(final ArrayList<SuggestedWordInfo> punctuationsList) { - super(punctuationsList, - null /* rawSuggestions */, - null /* typedWord */, - false /* typedWordValid */, - false /* hasAutoCorrectionCandidate */, - false /* isObsoleteSuggestions */, - INPUT_STYLE_NONE /* inputStyle */, - SuggestedWords.NOT_A_SEQUENCE_NUMBER); - } - - /** - * Create new instance of {@link PunctuationSuggestions} from the array of punctuation key - * specifications. - * - * @param punctuationSpecs The array of punctuation key specifications. - * @return The {@link PunctuationSuggestions} object. - */ - public static PunctuationSuggestions newPunctuationSuggestions( - @Nullable final String[] punctuationSpecs) { - if (punctuationSpecs == null || punctuationSpecs.length == 0) { - return new PunctuationSuggestions(new ArrayList<SuggestedWordInfo>(0)); - } - final ArrayList<SuggestedWordInfo> punctuationList = - new ArrayList<>(punctuationSpecs.length); - for (String spec : punctuationSpecs) { - punctuationList.add(newHardCodedWordInfo(spec)); - } - return new PunctuationSuggestions(punctuationList); - } - - /** - * {@inheritDoc} - * Note that {@link SuggestedWords#getWord(int)} returns a punctuation key specification text. - * The suggested punctuation should be gotten by parsing the key specification. - */ - @Override - public String getWord(final int index) { - final String keySpec = super.getWord(index); - final int code = KeySpecParser.getCode(keySpec); - return (code == Constants.CODE_OUTPUT_TEXT) - ? KeySpecParser.getOutputText(keySpec) - : StringUtils.newSingleCodePointString(code); - } - - /** - * {@inheritDoc} - * Note that {@link SuggestedWords#getWord(int)} returns a punctuation key specification text. - * The displayed text should be gotten by parsing the key specification. - */ - @Override - public String getLabel(final int index) { - final String keySpec = super.getWord(index); - return KeySpecParser.getLabel(keySpec); - } - - /** - * {@inheritDoc} - * Note that {@link #getWord(int)} returns a suggested punctuation. We should create a - * {@link SuggestedWords.SuggestedWordInfo} object that represents a hard coded word. - */ - @Override - public SuggestedWordInfo getInfo(final int index) { - return newHardCodedWordInfo(getWord(index)); - } - - /** - * The predicator to tell whether this object represents punctuation suggestions. - * @return true if this object represents punctuation suggestions. - */ - @Override - public boolean isPunctuationSuggestions() { - return true; - } - - @Override - public String toString() { - return "PunctuationSuggestions: " - + " words=" + Arrays.toString(mSuggestedWordInfoList.toArray()); - } - - private static SuggestedWordInfo newHardCodedWordInfo(final String keySpec) { - return new SuggestedWordInfo(keySpec, "" /* prevWordsContext */, - SuggestedWordInfo.MAX_SCORE, - SuggestedWordInfo.KIND_HARDCODED, - Dictionary.DICTIONARY_HARDCODED, - SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, - SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */); - } -} diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java deleted file mode 100644 index 7b1a53a6e..000000000 --- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin; - -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.ComposedData; -import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; - -import java.util.ArrayList; -import java.util.Locale; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * This class provides binary dictionary reading operations with locking. An instance of this class - * can be used by multiple threads. Note that different session IDs must be used when multiple - * threads get suggestions using this class. - */ -public final class ReadOnlyBinaryDictionary extends Dictionary { - /** - * A lock for accessing binary dictionary. Only closing binary dictionary is the operation - * that change the state of dictionary. - */ - private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock(); - - private final BinaryDictionary mBinaryDictionary; - - public ReadOnlyBinaryDictionary(final String filename, final long offset, final long length, - final boolean useFullEditDistance, final Locale locale, final String dictType) { - super(dictType, locale); - mBinaryDictionary = new BinaryDictionary(filename, offset, length, useFullEditDistance, - locale, dictType, false /* isUpdatable */); - } - - public boolean isValidDictionary() { - return mBinaryDictionary.isValidDictionary(); - } - - @Override - public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, - final NgramContext ngramContext, final long proximityInfoHandle, - final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int sessionId, final float weightForLocale, - final float[] inOutWeightOfLangModelVsSpatialModel) { - if (mLock.readLock().tryLock()) { - try { - return mBinaryDictionary.getSuggestions(composedData, ngramContext, - proximityInfoHandle, settingsValuesForSuggestion, sessionId, - weightForLocale, inOutWeightOfLangModelVsSpatialModel); - } finally { - mLock.readLock().unlock(); - } - } - return null; - } - - @Override - public boolean isInDictionary(final String word) { - if (mLock.readLock().tryLock()) { - try { - return mBinaryDictionary.isInDictionary(word); - } finally { - mLock.readLock().unlock(); - } - } - return false; - } - - @Override - public boolean shouldAutoCommit(final SuggestedWordInfo candidate) { - if (mLock.readLock().tryLock()) { - try { - return mBinaryDictionary.shouldAutoCommit(candidate); - } finally { - mLock.readLock().unlock(); - } - } - return false; - } - - @Override - public int getFrequency(final String word) { - if (mLock.readLock().tryLock()) { - try { - return mBinaryDictionary.getFrequency(word); - } finally { - mLock.readLock().unlock(); - } - } - return NOT_A_PROBABILITY; - } - - @Override - public int getMaxFrequencyOfExactMatches(final String word) { - if (mLock.readLock().tryLock()) { - try { - return mBinaryDictionary.getMaxFrequencyOfExactMatches(word); - } finally { - mLock.readLock().unlock(); - } - } - return NOT_A_PROBABILITY; - } - - @Override - public void close() { - mLock.writeLock().lock(); - try { - mBinaryDictionary.close(); - } finally { - mLock.writeLock().unlock(); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java deleted file mode 100644 index a10f2bdb0..000000000 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ /dev/null @@ -1,1033 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin; - -import android.inputmethodservice.InputMethodService; -import android.os.Build; -import android.os.Bundle; -import android.os.SystemClock; -import android.text.SpannableStringBuilder; -import android.text.TextUtils; -import android.text.style.CharacterStyle; -import android.util.Log; -import android.view.KeyEvent; -import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.CorrectionInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; - -import com.android.inputmethod.compat.InputConnectionCompatUtils; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.UnicodeSurrogate; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.inputlogic.PrivateCommandPerformer; -import com.android.inputmethod.latin.settings.SpacingAndPunctuations; -import com.android.inputmethod.latin.utils.CapsModeUtils; -import com.android.inputmethod.latin.utils.DebugLogUtils; -import com.android.inputmethod.latin.utils.NgramContextUtils; -import com.android.inputmethod.latin.utils.ScriptUtils; -import com.android.inputmethod.latin.utils.SpannableStringUtils; -import com.android.inputmethod.latin.utils.StatsUtils; -import com.android.inputmethod.latin.utils.TextRange; - -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Enrichment class for InputConnection to simplify interaction and add functionality. - * - * This class serves as a wrapper to be able to simply add hooks to any calls to the underlying - * InputConnection. It also keeps track of a number of things to avoid having to call upon IPC - * all the time to find out what text is in the buffer, when we need it to determine caps mode - * for example. - */ -public final class RichInputConnection implements PrivateCommandPerformer { - private static final String TAG = "RichInputConnection"; - private static final boolean DBG = false; - private static final boolean DEBUG_PREVIOUS_TEXT = false; - private static final boolean DEBUG_BATCH_NESTING = false; - private static final int NUM_CHARS_TO_GET_BEFORE_CURSOR = 40; - private static final int NUM_CHARS_TO_GET_AFTER_CURSOR = 40; - private static final int INVALID_CURSOR_POSITION = -1; - - /** - * The amount of time a {@link #reloadTextCache} call needs to take for the keyboard to enter - * the {@link #hasSlowInputConnection} state. - */ - private static final long SLOW_INPUT_CONNECTION_ON_FULL_RELOAD_MS = 1000; - /** - * The amount of time a {@link #getTextBeforeCursor} or {@link #getTextAfterCursor} call needs - * to take for the keyboard to enter the {@link #hasSlowInputConnection} state. - */ - private static final long SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS = 200; - - private static final int OPERATION_GET_TEXT_BEFORE_CURSOR = 0; - private static final int OPERATION_GET_TEXT_AFTER_CURSOR = 1; - private static final int OPERATION_GET_WORD_RANGE_AT_CURSOR = 2; - private static final int OPERATION_RELOAD_TEXT_CACHE = 3; - private static final String[] OPERATION_NAMES = new String[] { - "GET_TEXT_BEFORE_CURSOR", - "GET_TEXT_AFTER_CURSOR", - "GET_WORD_RANGE_AT_CURSOR", - "RELOAD_TEXT_CACHE"}; - - /** - * The amount of time the keyboard will persist in the {@link #hasSlowInputConnection} state - * after observing a slow InputConnection event. - */ - private static final long SLOW_INPUTCONNECTION_PERSIST_MS = TimeUnit.MINUTES.toMillis(10); - - /** - * This variable contains an expected value for the selection start position. This is where the - * cursor or selection start may end up after all the keyboard-triggered updates have passed. We - * keep this to compare it to the actual selection start to guess whether the move was caused by - * a keyboard command or not. - * It's not really the selection start position: the selection start may not be there yet, and - * in some cases, it may never arrive there. - */ - private int mExpectedSelStart = INVALID_CURSOR_POSITION; // in chars, not code points - /** - * The expected selection end. Only differs from mExpectedSelStart if a non-empty selection is - * expected. The same caveats as mExpectedSelStart apply. - */ - private int mExpectedSelEnd = INVALID_CURSOR_POSITION; // in chars, not code points - /** - * This contains the committed text immediately preceding the cursor and the composing - * text, if any. It is refreshed when the cursor moves by calling upon the TextView. - */ - private final StringBuilder mCommittedTextBeforeComposingText = new StringBuilder(); - /** - * This contains the currently composing text, as LatinIME thinks the TextView is seeing it. - */ - private final StringBuilder mComposingText = new StringBuilder(); - - /** - * This variable is a temporary object used in {@link #commitText(CharSequence,int)} - * to avoid object creation. - */ - private SpannableStringBuilder mTempObjectForCommitText = new SpannableStringBuilder(); - - private final InputMethodService mParent; - private InputConnection mIC; - private int mNestLevel; - - /** - * The timestamp of the last slow InputConnection operation - */ - private long mLastSlowInputConnectionTime = -SLOW_INPUTCONNECTION_PERSIST_MS; - - public RichInputConnection(final InputMethodService parent) { - mParent = parent; - mIC = null; - mNestLevel = 0; - } - - public boolean isConnected() { - return mIC != null; - } - - /** - * Returns whether or not the underlying InputConnection is slow. When true, we want to avoid - * calling InputConnection methods that trigger an IPC round-trip (e.g., getTextAfterCursor). - */ - public boolean hasSlowInputConnection() { - return (SystemClock.uptimeMillis() - mLastSlowInputConnectionTime) - <= SLOW_INPUTCONNECTION_PERSIST_MS; - } - - public void onStartInput() { - mLastSlowInputConnectionTime = -SLOW_INPUTCONNECTION_PERSIST_MS; - } - - private void checkConsistencyForDebug() { - final ExtractedTextRequest r = new ExtractedTextRequest(); - r.hintMaxChars = 0; - r.hintMaxLines = 0; - r.token = 1; - r.flags = 0; - final ExtractedText et = mIC.getExtractedText(r, 0); - final CharSequence beforeCursor = getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, - 0); - final StringBuilder internal = new StringBuilder(mCommittedTextBeforeComposingText) - .append(mComposingText); - if (null == et || null == beforeCursor) return; - final int actualLength = Math.min(beforeCursor.length(), internal.length()); - if (internal.length() > actualLength) { - internal.delete(0, internal.length() - actualLength); - } - final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString() - : beforeCursor.subSequence(beforeCursor.length() - actualLength, - beforeCursor.length()).toString(); - if (et.selectionStart != mExpectedSelStart - || !(reference.equals(internal.toString()))) { - final String context = "Expected selection start = " + mExpectedSelStart - + "\nActual selection start = " + et.selectionStart - + "\nExpected text = " + internal.length() + " " + internal - + "\nActual text = " + reference.length() + " " + reference; - ((LatinIME)mParent).debugDumpStateAndCrashWithException(context); - } else { - Log.e(TAG, DebugLogUtils.getStackTrace(2)); - Log.e(TAG, "Exp <> Actual : " + mExpectedSelStart + " <> " + et.selectionStart); - } - } - - public void beginBatchEdit() { - if (++mNestLevel == 1) { - mIC = mParent.getCurrentInputConnection(); - if (isConnected()) { - mIC.beginBatchEdit(); - } - } else { - if (DBG) { - throw new RuntimeException("Nest level too deep"); - } - Log.e(TAG, "Nest level too deep : " + mNestLevel); - } - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - } - - public void endBatchEdit() { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead - if (--mNestLevel == 0 && isConnected()) { - mIC.endBatchEdit(); - } - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - } - - /** - * Reset the cached text and retrieve it again from the editor. - * - * This should be called when the cursor moved. It's possible that we can't connect to - * the application when doing this; notably, this happens sometimes during rotation, probably - * because of a race condition in the framework. In this case, we just can't retrieve the - * data, so we empty the cache and note that we don't know the new cursor position, and we - * return false so that the caller knows about this and can retry later. - * - * @param newSelStart the new position of the selection start, as received from the system. - * @param newSelEnd the new position of the selection end, as received from the system. - * @param shouldFinishComposition whether we should finish the composition in progress. - * @return true if we were able to connect to the editor successfully, false otherwise. When - * this method returns false, the caches could not be correctly refreshed so they were only - * reset: the caller should try again later to return to normal operation. - */ - public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newSelStart, - final int newSelEnd, final boolean shouldFinishComposition) { - mExpectedSelStart = newSelStart; - mExpectedSelEnd = newSelEnd; - mComposingText.setLength(0); - final boolean didReloadTextSuccessfully = reloadTextCache(); - if (!didReloadTextSuccessfully) { - Log.d(TAG, "Will try to retrieve text later."); - return false; - } - if (isConnected() && shouldFinishComposition) { - mIC.finishComposingText(); - } - return true; - } - - /** - * Reload the cached text from the InputConnection. - * - * @return true if successful - */ - private boolean reloadTextCache() { - mCommittedTextBeforeComposingText.setLength(0); - mIC = mParent.getCurrentInputConnection(); - // Call upon the inputconnection directly since our own method is using the cache, and - // we want to refresh it. - final CharSequence textBeforeCursor = getTextBeforeCursorAndDetectLaggyConnection( - OPERATION_RELOAD_TEXT_CACHE, - SLOW_INPUT_CONNECTION_ON_FULL_RELOAD_MS, - Constants.EDITOR_CONTENTS_CACHE_SIZE, - 0 /* flags */); - if (null == textBeforeCursor) { - // For some reason the app thinks we are not connected to it. This looks like a - // framework bug... Fall back to ground state and return false. - mExpectedSelStart = INVALID_CURSOR_POSITION; - mExpectedSelEnd = INVALID_CURSOR_POSITION; - Log.e(TAG, "Unable to connect to the editor to retrieve text."); - return false; - } - mCommittedTextBeforeComposingText.append(textBeforeCursor); - return true; - } - - private void checkBatchEdit() { - if (mNestLevel != 1) { - // TODO: exception instead - Log.e(TAG, "Batch edit level incorrect : " + mNestLevel); - Log.e(TAG, DebugLogUtils.getStackTrace(4)); - } - } - - public void finishComposingText() { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - // TODO: this is not correct! The cursor is not necessarily after the composing text. - // In the practice right now this is only called when input ends so it will be reset so - // it works, but it's wrong and should be fixed. - mCommittedTextBeforeComposingText.append(mComposingText); - mComposingText.setLength(0); - if (isConnected()) { - mIC.finishComposingText(); - } - } - - /** - * Calls {@link InputConnection#commitText(CharSequence, int)}. - * - * @param text The text to commit. This may include styles. - * @param newCursorPosition The new cursor position around the text. - */ - public void commitText(final CharSequence text, final int newCursorPosition) { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - mCommittedTextBeforeComposingText.append(text); - // TODO: the following is exceedingly error-prone. Right now when the cursor is in the - // middle of the composing word mComposingText only holds the part of the composing text - // that is before the cursor, so this actually works, but it's terribly confusing. Fix this. - mExpectedSelStart += text.length() - mComposingText.length(); - mExpectedSelEnd = mExpectedSelStart; - mComposingText.setLength(0); - if (isConnected()) { - mTempObjectForCommitText.clear(); - mTempObjectForCommitText.append(text); - final CharacterStyle[] spans = mTempObjectForCommitText.getSpans( - 0, text.length(), CharacterStyle.class); - for (final CharacterStyle span : spans) { - final int spanStart = mTempObjectForCommitText.getSpanStart(span); - final int spanEnd = mTempObjectForCommitText.getSpanEnd(span); - final int spanFlags = mTempObjectForCommitText.getSpanFlags(span); - // We have to adjust the end of the span to include an additional character. - // This is to avoid splitting a unicode surrogate pair. - // See com.android.inputmethod.latin.common.Constants.UnicodeSurrogate - // See https://b.corp.google.com/issues/19255233 - if (0 < spanEnd && spanEnd < mTempObjectForCommitText.length()) { - final char spanEndChar = mTempObjectForCommitText.charAt(spanEnd - 1); - final char nextChar = mTempObjectForCommitText.charAt(spanEnd); - if (UnicodeSurrogate.isLowSurrogate(spanEndChar) - && UnicodeSurrogate.isHighSurrogate(nextChar)) { - mTempObjectForCommitText.setSpan(span, spanStart, spanEnd + 1, spanFlags); - } - } - } - mIC.commitText(mTempObjectForCommitText, newCursorPosition); - } - } - - @Nullable - public CharSequence getSelectedText(final int flags) { - return isConnected() ? mIC.getSelectedText(flags) : null; - } - - public boolean canDeleteCharacters() { - return mExpectedSelStart > 0; - } - - /** - * Gets the caps modes we should be in after this specific string. - * - * This returns a bit set of TextUtils#CAP_MODE_*, masked by the inputType argument. - * This method also supports faking an additional space after the string passed in argument, - * to support cases where a space will be added automatically, like in phantom space - * state for example. - * Note that for English, we are using American typography rules (which are not specific to - * American English, it's just the most common set of rules for English). - * - * @param inputType a mask of the caps modes to test for. - * @param spacingAndPunctuations the values of the settings to use for locale and separators. - * @param hasSpaceBefore if we should consider there should be a space after the string. - * @return the caps modes that should be on as a set of bits - */ - public int getCursorCapsMode(final int inputType, - final SpacingAndPunctuations spacingAndPunctuations, final boolean hasSpaceBefore) { - mIC = mParent.getCurrentInputConnection(); - if (!isConnected()) { - return Constants.TextUtils.CAP_MODE_OFF; - } - if (!TextUtils.isEmpty(mComposingText)) { - if (hasSpaceBefore) { - // If we have some composing text and a space before, then we should have - // MODE_CHARACTERS and MODE_WORDS on. - return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & inputType; - } - // We have some composing text - we should be in MODE_CHARACTERS only. - return TextUtils.CAP_MODE_CHARACTERS & inputType; - } - // TODO: this will generally work, but there may be cases where the buffer contains SOME - // information but not enough to determine the caps mode accurately. This may happen after - // heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so. - // getCapsMode should be updated to be able to return a "not enough info" result so that - // we can get more context only when needed. - if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedSelStart) { - if (!reloadTextCache()) { - Log.w(TAG, "Unable to connect to the editor. " - + "Setting caps mode without knowing text."); - } - } - // This never calls InputConnection#getCapsMode - in fact, it's a static method that - // never blocks or initiates IPC. - // TODO: don't call #toString() here. Instead, all accesses to - // mCommittedTextBeforeComposingText should be done on the main thread. - return CapsModeUtils.getCapsMode(mCommittedTextBeforeComposingText.toString(), inputType, - spacingAndPunctuations, hasSpaceBefore); - } - - public int getCodePointBeforeCursor() { - final int length = mCommittedTextBeforeComposingText.length(); - if (length < 1) return Constants.NOT_A_CODE; - return Character.codePointBefore(mCommittedTextBeforeComposingText, length); - } - - public CharSequence getTextBeforeCursor(final int n, final int flags) { - final int cachedLength = - mCommittedTextBeforeComposingText.length() + mComposingText.length(); - // If we have enough characters to satisfy the request, or if we have all characters in - // the text field, then we can return the cached version right away. - // However, if we don't have an expected cursor position, then we should always - // go fetch the cache again (as it happens, INVALID_CURSOR_POSITION < 0, so we need to - // test for this explicitly) - if (INVALID_CURSOR_POSITION != mExpectedSelStart - && (cachedLength >= n || cachedLength >= mExpectedSelStart)) { - final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText); - // We call #toString() here to create a temporary object. - // In some situations, this method is called on a worker thread, and it's possible - // the main thread touches the contents of mComposingText while this worker thread - // is suspended, because mComposingText is a StringBuilder. This may lead to crashes, - // so we call #toString() on it. That will result in the return value being strictly - // speaking wrong, but since this is used for basing bigram probability off, and - // it's only going to matter for one getSuggestions call, it's fine in the practice. - s.append(mComposingText.toString()); - if (s.length() > n) { - s.delete(0, s.length() - n); - } - return s; - } - return getTextBeforeCursorAndDetectLaggyConnection( - OPERATION_GET_TEXT_BEFORE_CURSOR, - SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS, - n, flags); - } - - private CharSequence getTextBeforeCursorAndDetectLaggyConnection( - final int operation, final long timeout, final int n, final int flags) { - mIC = mParent.getCurrentInputConnection(); - if (!isConnected()) { - return null; - } - final long startTime = SystemClock.uptimeMillis(); - final CharSequence result = mIC.getTextBeforeCursor(n, flags); - detectLaggyConnection(operation, timeout, startTime); - return result; - } - - public CharSequence getTextAfterCursor(final int n, final int flags) { - return getTextAfterCursorAndDetectLaggyConnection( - OPERATION_GET_TEXT_AFTER_CURSOR, - SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS, - n, flags); - } - - private CharSequence getTextAfterCursorAndDetectLaggyConnection( - final int operation, final long timeout, final int n, final int flags) { - mIC = mParent.getCurrentInputConnection(); - if (!isConnected()) { - return null; - } - final long startTime = SystemClock.uptimeMillis(); - final CharSequence result = mIC.getTextAfterCursor(n, flags); - detectLaggyConnection(operation, timeout, startTime); - return result; - } - - private void detectLaggyConnection(final int operation, final long timeout, final long startTime) { - final long duration = SystemClock.uptimeMillis() - startTime; - if (duration >= timeout) { - final String operationName = OPERATION_NAMES[operation]; - Log.w(TAG, "Slow InputConnection: " + operationName + " took " + duration + " ms."); - StatsUtils.onInputConnectionLaggy(operation, duration); - mLastSlowInputConnectionTime = SystemClock.uptimeMillis(); - } - } - - public void deleteTextBeforeCursor(final int beforeLength) { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - // TODO: the following is incorrect if the cursor is not immediately after the composition. - // Right now we never come here in this case because we reset the composing state before we - // come here in this case, but we need to fix this. - final int remainingChars = mComposingText.length() - beforeLength; - if (remainingChars >= 0) { - mComposingText.setLength(remainingChars); - } else { - mComposingText.setLength(0); - // Never cut under 0 - final int len = Math.max(mCommittedTextBeforeComposingText.length() - + remainingChars, 0); - mCommittedTextBeforeComposingText.setLength(len); - } - if (mExpectedSelStart > beforeLength) { - mExpectedSelStart -= beforeLength; - mExpectedSelEnd -= beforeLength; - } else { - // There are fewer characters before the cursor in the buffer than we are being asked to - // delete. Only delete what is there, and update the end with the amount deleted. - mExpectedSelEnd -= mExpectedSelStart; - mExpectedSelStart = 0; - } - if (isConnected()) { - mIC.deleteSurroundingText(beforeLength, 0); - } - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - } - - public void performEditorAction(final int actionId) { - mIC = mParent.getCurrentInputConnection(); - if (isConnected()) { - mIC.performEditorAction(actionId); - } - } - - public void sendKeyEvent(final KeyEvent keyEvent) { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - // This method is only called for enter or backspace when speaking to old applications - // (target SDK <= 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)), or for digits. - // When talking to new applications we never use this method because it's inherently - // racy and has unpredictable results, but for backward compatibility we continue - // sending the key events for only Enter and Backspace because some applications - // mistakenly catch them to do some stuff. - switch (keyEvent.getKeyCode()) { - case KeyEvent.KEYCODE_ENTER: - mCommittedTextBeforeComposingText.append("\n"); - mExpectedSelStart += 1; - mExpectedSelEnd = mExpectedSelStart; - break; - case KeyEvent.KEYCODE_DEL: - if (0 == mComposingText.length()) { - if (mCommittedTextBeforeComposingText.length() > 0) { - mCommittedTextBeforeComposingText.delete( - mCommittedTextBeforeComposingText.length() - 1, - mCommittedTextBeforeComposingText.length()); - } - } else { - mComposingText.delete(mComposingText.length() - 1, mComposingText.length()); - } - if (mExpectedSelStart > 0 && mExpectedSelStart == mExpectedSelEnd) { - // TODO: Handle surrogate pairs. - mExpectedSelStart -= 1; - } - mExpectedSelEnd = mExpectedSelStart; - break; - case KeyEvent.KEYCODE_UNKNOWN: - if (null != keyEvent.getCharacters()) { - mCommittedTextBeforeComposingText.append(keyEvent.getCharacters()); - mExpectedSelStart += keyEvent.getCharacters().length(); - mExpectedSelEnd = mExpectedSelStart; - } - break; - default: - final String text = StringUtils.newSingleCodePointString(keyEvent.getUnicodeChar()); - mCommittedTextBeforeComposingText.append(text); - mExpectedSelStart += text.length(); - mExpectedSelEnd = mExpectedSelStart; - break; - } - } - if (isConnected()) { - mIC.sendKeyEvent(keyEvent); - } - } - - public void setComposingRegion(final int start, final int end) { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - final CharSequence textBeforeCursor = - getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE + (end - start), 0); - mCommittedTextBeforeComposingText.setLength(0); - if (!TextUtils.isEmpty(textBeforeCursor)) { - // The cursor is not necessarily at the end of the composing text, but we have its - // position in mExpectedSelStart and mExpectedSelEnd. In this case we want the start - // of the text, so we should use mExpectedSelStart. In other words, the composing - // text starts (mExpectedSelStart - start) characters before the end of textBeforeCursor - final int indexOfStartOfComposingText = - Math.max(textBeforeCursor.length() - (mExpectedSelStart - start), 0); - mComposingText.append(textBeforeCursor.subSequence(indexOfStartOfComposingText, - textBeforeCursor.length())); - mCommittedTextBeforeComposingText.append( - textBeforeCursor.subSequence(0, indexOfStartOfComposingText)); - } - if (isConnected()) { - mIC.setComposingRegion(start, end); - } - } - - public void setComposingText(final CharSequence text, final int newCursorPosition) { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - mExpectedSelStart += text.length() - mComposingText.length(); - mExpectedSelEnd = mExpectedSelStart; - mComposingText.setLength(0); - mComposingText.append(text); - // TODO: support values of newCursorPosition != 1. At this time, this is never called with - // newCursorPosition != 1. - if (isConnected()) { - mIC.setComposingText(text, newCursorPosition); - } - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - } - - /** - * Set the selection of the text editor. - * - * Calls through to {@link InputConnection#setSelection(int, int)}. - * - * @param start the character index where the selection should start. - * @param end the character index where the selection should end. - * @return Returns true on success, false on failure: either the input connection is no longer - * valid when setting the selection or when retrieving the text cache at that point, or - * invalid arguments were passed. - */ - public boolean setSelection(final int start, final int end) { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - if (start < 0 || end < 0) { - return false; - } - mExpectedSelStart = start; - mExpectedSelEnd = end; - if (isConnected()) { - final boolean isIcValid = mIC.setSelection(start, end); - if (!isIcValid) { - return false; - } - } - return reloadTextCache(); - } - - public void commitCorrection(final CorrectionInfo correctionInfo) { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - // This has no effect on the text field and does not change its content. It only makes - // TextView flash the text for a second based on indices contained in the argument. - if (isConnected()) { - mIC.commitCorrection(correctionInfo); - } - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - } - - public void commitCompletion(final CompletionInfo completionInfo) { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - CharSequence text = completionInfo.getText(); - // text should never be null, but just in case, it's better to insert nothing than to crash - if (null == text) text = ""; - mCommittedTextBeforeComposingText.append(text); - mExpectedSelStart += text.length() - mComposingText.length(); - mExpectedSelEnd = mExpectedSelStart; - mComposingText.setLength(0); - if (isConnected()) { - mIC.commitCompletion(completionInfo); - } - if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - } - - @SuppressWarnings("unused") - @Nonnull - public NgramContext getNgramContextFromNthPreviousWord( - final SpacingAndPunctuations spacingAndPunctuations, final int n) { - mIC = mParent.getCurrentInputConnection(); - if (!isConnected()) { - return NgramContext.EMPTY_PREV_WORDS_INFO; - } - final CharSequence prev = getTextBeforeCursor(NUM_CHARS_TO_GET_BEFORE_CURSOR, 0); - if (DEBUG_PREVIOUS_TEXT && null != prev) { - final int checkLength = NUM_CHARS_TO_GET_BEFORE_CURSOR - 1; - final String reference = prev.length() <= checkLength ? prev.toString() - : prev.subSequence(prev.length() - checkLength, prev.length()).toString(); - // TODO: right now the following works because mComposingText holds the part of the - // composing text that is before the cursor, but this is very confusing. We should - // fix it. - final StringBuilder internal = new StringBuilder() - .append(mCommittedTextBeforeComposingText).append(mComposingText); - if (internal.length() > checkLength) { - internal.delete(0, internal.length() - checkLength); - if (!(reference.equals(internal.toString()))) { - final String context = - "Expected text = " + internal + "\nActual text = " + reference; - ((LatinIME)mParent).debugDumpStateAndCrashWithException(context); - } - } - } - return NgramContextUtils.getNgramContextFromNthPreviousWord( - prev, spacingAndPunctuations, n); - } - - private static boolean isPartOfCompositionForScript(final int codePoint, - final SpacingAndPunctuations spacingAndPunctuations, final int scriptId) { - // We always consider word connectors part of compositions. - return spacingAndPunctuations.isWordConnector(codePoint) - // Otherwise, it's part of composition if it's part of script and not a separator. - || (!spacingAndPunctuations.isWordSeparator(codePoint) - && ScriptUtils.isLetterPartOfScript(codePoint, scriptId)); - } - - /** - * Returns the text surrounding the cursor. - * - * @param spacingAndPunctuations the rules for spacing and punctuation - * @param scriptId the script we consider to be writing words, as one of ScriptUtils.SCRIPT_* - * @return a range containing the text surrounding the cursor - */ - public TextRange getWordRangeAtCursor(final SpacingAndPunctuations spacingAndPunctuations, - final int scriptId) { - mIC = mParent.getCurrentInputConnection(); - if (!isConnected()) { - return null; - } - final CharSequence before = getTextBeforeCursorAndDetectLaggyConnection( - OPERATION_GET_WORD_RANGE_AT_CURSOR, - SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS, - NUM_CHARS_TO_GET_BEFORE_CURSOR, - InputConnection.GET_TEXT_WITH_STYLES); - final CharSequence after = getTextAfterCursorAndDetectLaggyConnection( - OPERATION_GET_WORD_RANGE_AT_CURSOR, - SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS, - NUM_CHARS_TO_GET_AFTER_CURSOR, - InputConnection.GET_TEXT_WITH_STYLES); - if (before == null || after == null) { - return null; - } - - // Going backward, find the first breaking point (separator) - int startIndexInBefore = before.length(); - while (startIndexInBefore > 0) { - final int codePoint = Character.codePointBefore(before, startIndexInBefore); - if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { - break; - } - --startIndexInBefore; - if (Character.isSupplementaryCodePoint(codePoint)) { - --startIndexInBefore; - } - } - - // Find last word separator after the cursor - int endIndexInAfter = -1; - while (++endIndexInAfter < after.length()) { - final int codePoint = Character.codePointAt(after, endIndexInAfter); - if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { - break; - } - if (Character.isSupplementaryCodePoint(codePoint)) { - ++endIndexInAfter; - } - } - - final boolean hasUrlSpans = - SpannableStringUtils.hasUrlSpans(before, startIndexInBefore, before.length()) - || SpannableStringUtils.hasUrlSpans(after, 0, endIndexInAfter); - // We don't use TextUtils#concat because it copies all spans without respect to their - // nature. If the text includes a PARAGRAPH span and it has been split, then - // TextUtils#concat will crash when it tries to concat both sides of it. - return new TextRange( - SpannableStringUtils.concatWithNonParagraphSuggestionSpansOnly(before, after), - startIndexInBefore, before.length() + endIndexInAfter, before.length(), - hasUrlSpans); - } - - public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations, - boolean checkTextAfter) { - if (checkTextAfter && isCursorFollowedByWordCharacter(spacingAndPunctuations)) { - // If what's after the cursor is a word character, then we're touching a word. - return true; - } - final String textBeforeCursor = mCommittedTextBeforeComposingText.toString(); - int indexOfCodePointInJavaChars = textBeforeCursor.length(); - int consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE - : textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars); - // Search for the first non word-connector char - if (spacingAndPunctuations.isWordConnector(consideredCodePoint)) { - indexOfCodePointInJavaChars -= Character.charCount(consideredCodePoint); - consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE - : textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars); - } - return !(Constants.NOT_A_CODE == consideredCodePoint - || spacingAndPunctuations.isWordSeparator(consideredCodePoint) - || spacingAndPunctuations.isWordConnector(consideredCodePoint)); - } - - public boolean isCursorFollowedByWordCharacter( - final SpacingAndPunctuations spacingAndPunctuations) { - final CharSequence after = getTextAfterCursor(1, 0); - if (TextUtils.isEmpty(after)) { - return false; - } - final int codePointAfterCursor = Character.codePointAt(after, 0); - if (spacingAndPunctuations.isWordSeparator(codePointAfterCursor) - || spacingAndPunctuations.isWordConnector(codePointAfterCursor)) { - return false; - } - return true; - } - - public void removeTrailingSpace() { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - final int codePointBeforeCursor = getCodePointBeforeCursor(); - if (Constants.CODE_SPACE == codePointBeforeCursor) { - deleteTextBeforeCursor(1); - } - } - - public boolean sameAsTextBeforeCursor(final CharSequence text) { - final CharSequence beforeText = getTextBeforeCursor(text.length(), 0); - return TextUtils.equals(text, beforeText); - } - - public boolean revertDoubleSpacePeriod(final SpacingAndPunctuations spacingAndPunctuations) { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - // Here we test whether we indeed have a period and a space before us. This should not - // be needed, but it's there just in case something went wrong. - final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0); - if (!TextUtils.equals(spacingAndPunctuations.mSentenceSeparatorAndSpace, - textBeforeCursor)) { - // Theoretically we should not be coming here if there isn't ". " before the - // cursor, but the application may be changing the text while we are typing, so - // anything goes. We should not crash. - Log.d(TAG, "Tried to revert double-space combo but we didn't find \"" - + spacingAndPunctuations.mSentenceSeparatorAndSpace - + "\" just before the cursor."); - return false; - } - // Double-space results in ". ". A backspace to cancel this should result in a single - // space in the text field, so we replace ". " with a single space. - deleteTextBeforeCursor(2); - final String singleSpace = " "; - commitText(singleSpace, 1); - return true; - } - - public boolean revertSwapPunctuation() { - if (DEBUG_BATCH_NESTING) checkBatchEdit(); - // Here we test whether we indeed have a space and something else before us. This should not - // be needed, but it's there just in case something went wrong. - final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0); - // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to - // enter surrogate pairs this code will have been removed. - if (TextUtils.isEmpty(textBeforeCursor) - || (Constants.CODE_SPACE != textBeforeCursor.charAt(1))) { - // We may only come here if the application is changing the text while we are typing. - // This is quite a broken case, but not logically impossible, so we shouldn't crash, - // but some debugging log may be in order. - Log.d(TAG, "Tried to revert a swap of punctuation but we didn't " - + "find a space just before the cursor."); - return false; - } - deleteTextBeforeCursor(2); - final String text = " " + textBeforeCursor.subSequence(0, 1); - commitText(text, 1); - return true; - } - - /** - * Heuristic to determine if this is an expected update of the cursor. - * - * Sometimes updates to the cursor position are late because of their asynchronous nature. - * This method tries to determine if this update is one, based on the values of the cursor - * position in the update, and the currently expected position of the cursor according to - * LatinIME's internal accounting. If this is not a belated expected update, then it should - * mean that the user moved the cursor explicitly. - * This is quite robust, but of course it's not perfect. In particular, it will fail in the - * case we get an update A, the user types in N characters so as to move the cursor to A+N but - * we don't get those, and then the user places the cursor between A and A+N, and we get only - * this update and not the ones in-between. This is almost impossible to achieve even trying - * very very hard. - * - * @param oldSelStart The value of the old selection in the update. - * @param newSelStart The value of the new selection in the update. - * @param oldSelEnd The value of the old selection end in the update. - * @param newSelEnd The value of the new selection end in the update. - * @return whether this is a belated expected update or not. - */ - public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart, - final int oldSelEnd, final int newSelEnd) { - // This update is "belated" if we are expecting it. That is, mExpectedSelStart and - // mExpectedSelEnd match the new values that the TextView is updating TO. - if (mExpectedSelStart == newSelStart && mExpectedSelEnd == newSelEnd) return true; - // This update is not belated if mExpectedSelStart and mExpectedSelEnd match the old - // values, and one of newSelStart or newSelEnd is updated to a different value. In this - // case, it is likely that something other than the IME has moved the selection endpoint - // to the new value. - if (mExpectedSelStart == oldSelStart && mExpectedSelEnd == oldSelEnd - && (oldSelStart != newSelStart || oldSelEnd != newSelEnd)) return false; - // If neither of the above two cases hold, then the system may be having trouble keeping up - // with updates. If 1) the selection is a cursor, 2) newSelStart is between oldSelStart - // and mExpectedSelStart, and 3) newSelEnd is between oldSelEnd and mExpectedSelEnd, then - // assume a belated update. - return (newSelStart == newSelEnd) - && (newSelStart - oldSelStart) * (mExpectedSelStart - newSelStart) >= 0 - && (newSelEnd - oldSelEnd) * (mExpectedSelEnd - newSelEnd) >= 0; - } - - /** - * Looks at the text just before the cursor to find out if it looks like a URL. - * - * The weakest point here is, if we don't have enough text bufferized, we may fail to realize - * we are in URL situation, but other places in this class have the same limitation and it - * does not matter too much in the practice. - */ - public boolean textBeforeCursorLooksLikeURL() { - return StringUtils.lastPartLooksLikeURL(mCommittedTextBeforeComposingText); - } - - /** - * Looks at the text just before the cursor to find out if we are inside a double quote. - * - * As with #textBeforeCursorLooksLikeURL, this is dependent on how much text we have cached. - * However this won't be a concrete problem in most situations, as the cache is almost always - * long enough for this use. - */ - public boolean isInsideDoubleQuoteOrAfterDigit() { - return StringUtils.isInsideDoubleQuoteOrAfterDigit(mCommittedTextBeforeComposingText); - } - - /** - * Try to get the text from the editor to expose lies the framework may have been - * telling us. Concretely, when the device rotates and when the keyboard reopens in the same - * text field after having been closed with the back key, the frameworks tells us about where - * the cursor used to be initially in the editor at the time it first received the focus; this - * may be completely different from the place it is upon rotation. Since we don't have any - * means to get the real value, try at least to ask the text view for some characters and - * detect the most damaging cases: when the cursor position is declared to be much smaller - * than it really is. - */ - public void tryFixLyingCursorPosition() { - mIC = mParent.getCurrentInputConnection(); - final CharSequence textBeforeCursor = getTextBeforeCursor( - Constants.EDITOR_CONTENTS_CACHE_SIZE, 0); - final CharSequence selectedText = isConnected() ? mIC.getSelectedText(0 /* flags */) : null; - if (null == textBeforeCursor || - (!TextUtils.isEmpty(selectedText) && mExpectedSelEnd == mExpectedSelStart)) { - // If textBeforeCursor is null, we have no idea what kind of text field we have or if - // thinking about the "cursor position" actually makes any sense. In this case we - // remember a meaningless cursor position. Contrast this with an empty string, which is - // valid and should mean the cursor is at the start of the text. - // Also, if we expect we don't have a selection but we DO have non-empty selected text, - // then the framework lied to us about the cursor position. In this case, we should just - // revert to the most basic behavior possible for the next action (backspace in - // particular comes to mind), so we remember a meaningless cursor position which should - // result in degraded behavior from the next input. - // Interestingly, in either case, chances are any action the user takes next will result - // in a call to onUpdateSelection, which should set things right. - mExpectedSelStart = mExpectedSelEnd = Constants.NOT_A_CURSOR_POSITION; - } else { - final int textLength = textBeforeCursor.length(); - if (textLength < Constants.EDITOR_CONTENTS_CACHE_SIZE - && (textLength > mExpectedSelStart - || mExpectedSelStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) { - // It should not be possible to have only one of those variables be - // NOT_A_CURSOR_POSITION, so if they are equal, either the selection is zero-sized - // (simple cursor, no selection) or there is no cursor/we don't know its pos - final boolean wasEqual = mExpectedSelStart == mExpectedSelEnd; - mExpectedSelStart = textLength; - // We can't figure out the value of mLastSelectionEnd :( - // But at least if it's smaller than mLastSelectionStart something is wrong, - // and if they used to be equal we also don't want to make it look like there is a - // selection. - if (wasEqual || mExpectedSelStart > mExpectedSelEnd) { - mExpectedSelEnd = mExpectedSelStart; - } - } - } - } - - @Override - public boolean performPrivateCommand(final String action, final Bundle data) { - mIC = mParent.getCurrentInputConnection(); - if (!isConnected()) { - return false; - } - return mIC.performPrivateCommand(action, data); - } - - public int getExpectedSelectionStart() { - return mExpectedSelStart; - } - - public int getExpectedSelectionEnd() { - return mExpectedSelEnd; - } - - /** - * @return whether there is a selection currently active. - */ - public boolean hasSelection() { - return mExpectedSelEnd != mExpectedSelStart; - } - - public boolean isCursorPositionKnown() { - return INVALID_CURSOR_POSITION != mExpectedSelStart; - } - - /** - * Work around a bug that was present before Jelly Bean upon rotation. - * - * Before Jelly Bean, there is a bug where setComposingRegion and other committing - * functions on the input connection get ignored until the cursor moves. This method works - * around the bug by wiggling the cursor first, which reactivates the connection and has - * the subsequent methods work, then restoring it to its original position. - * - * On platforms on which this method is not present, this is a no-op. - */ - public void maybeMoveTheCursorAroundAndRestoreToWorkaroundABug() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - if (mExpectedSelStart > 0) { - mIC.setSelection(mExpectedSelStart - 1, mExpectedSelStart - 1); - } else { - mIC.setSelection(mExpectedSelStart + 1, mExpectedSelStart + 1); - } - mIC.setSelection(mExpectedSelStart, mExpectedSelEnd); - } - } - - /** - * Requests the editor to call back {@link InputMethodManager#updateCursorAnchorInfo}. - * @param enableMonitor {@code true} to request the editor to call back the method whenever the - * cursor/anchor position is changed. - * @param requestImmediateCallback {@code true} to request the editor to call back the method - * as soon as possible to notify the current cursor/anchor position to the input method. - * @return {@code true} if the request is accepted. Returns {@code false} otherwise, which - * includes "not implemented" or "rejected" or "temporarily unavailable" or whatever which - * prevents the application from fulfilling the request. (TODO: Improve the API when it turns - * out that we actually need more detailed error codes) - */ - public boolean requestCursorUpdates(final boolean enableMonitor, - final boolean requestImmediateCallback) { - mIC = mParent.getCurrentInputConnection(); - if (!isConnected()) { - return false; - } - return InputConnectionCompatUtils.requestCursorUpdates( - mIC, enableMonitor, requestImmediateCallback); - } -} diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java deleted file mode 100644 index 3beb51d68..000000000 --- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java +++ /dev/null @@ -1,612 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin; - -import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE; - -import android.content.Context; -import android.content.SharedPreferences; -import android.inputmethodservice.InputMethodService; -import android.os.AsyncTask; -import android.os.Build; -import android.os.IBinder; -import android.preference.PreferenceManager; -import android.util.Log; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; -import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; -import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; -import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Enrichment class for InputMethodManager to simplify interaction and add functionality. - */ -// non final for easy mocking. -public class RichInputMethodManager { - private static final String TAG = RichInputMethodManager.class.getSimpleName(); - private static final boolean DEBUG = false; - - private RichInputMethodManager() { - // This utility class is not publicly instantiable. - } - - private static final RichInputMethodManager sInstance = new RichInputMethodManager(); - - private Context mContext; - private InputMethodManagerCompatWrapper mImmWrapper; - private InputMethodInfoCache mInputMethodInfoCache; - private RichInputMethodSubtype mCurrentRichInputMethodSubtype; - private InputMethodInfo mShortcutInputMethodInfo; - private InputMethodSubtype mShortcutSubtype; - - private static final int INDEX_NOT_FOUND = -1; - - public static RichInputMethodManager getInstance() { - sInstance.checkInitialized(); - return sInstance; - } - - public static void init(final Context context) { - sInstance.initInternal(context); - } - - private boolean isInitialized() { - return mImmWrapper != null; - } - - private void checkInitialized() { - if (!isInitialized()) { - throw new RuntimeException(TAG + " is used before initialization"); - } - } - - private void initInternal(final Context context) { - if (isInitialized()) { - return; - } - mImmWrapper = new InputMethodManagerCompatWrapper(context); - mContext = context; - mInputMethodInfoCache = new InputMethodInfoCache( - mImmWrapper.mImm, context.getPackageName()); - - // Initialize additional subtypes. - SubtypeLocaleUtils.init(context); - final InputMethodSubtype[] additionalSubtypes = getAdditionalSubtypes(); - mImmWrapper.mImm.setAdditionalInputMethodSubtypes( - getInputMethodIdOfThisIme(), additionalSubtypes); - - // Initialize the current input method subtype and the shortcut IME. - refreshSubtypeCaches(); - } - - public InputMethodSubtype[] getAdditionalSubtypes() { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); - final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes( - prefs, mContext.getResources()); - return AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes); - } - - public InputMethodManager getInputMethodManager() { - checkInitialized(); - return mImmWrapper.mImm; - } - - public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList( - boolean allowsImplicitlySelectedSubtypes) { - return getEnabledInputMethodSubtypeList( - getInputMethodInfoOfThisIme(), allowsImplicitlySelectedSubtypes); - } - - public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) { - if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) { - return true; - } - // Was not able to call {@link InputMethodManager#switchToNextInputMethodIBinder,boolean)} - // because the current device is running ICS or previous and lacks the API. - if (switchToNextInputSubtypeInThisIme(token, onlyCurrentIme)) { - return true; - } - return switchToNextInputMethodAndSubtype(token); - } - - private boolean switchToNextInputSubtypeInThisIme(final IBinder token, - final boolean onlyCurrentIme) { - final InputMethodManager imm = mImmWrapper.mImm; - final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype(); - final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList( - true /* allowsImplicitlySelectedSubtypes */); - final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes); - if (currentIndex == INDEX_NOT_FOUND) { - Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype=" - + SubtypeLocaleUtils.getSubtypeNameForLogging(currentSubtype)); - return false; - } - final int nextIndex = (currentIndex + 1) % enabledSubtypes.size(); - if (nextIndex <= currentIndex && !onlyCurrentIme) { - // The current subtype is the last or only enabled one and it needs to switch to - // next IME. - return false; - } - final InputMethodSubtype nextSubtype = enabledSubtypes.get(nextIndex); - setInputMethodAndSubtype(token, nextSubtype); - return true; - } - - private boolean switchToNextInputMethodAndSubtype(final IBinder token) { - final InputMethodManager imm = mImmWrapper.mImm; - final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList(); - final int currentIndex = getImiIndexInList(getInputMethodInfoOfThisIme(), enabledImis); - if (currentIndex == INDEX_NOT_FOUND) { - Log.w(TAG, "Can't find current IME in enabled IMEs: IME package=" - + getInputMethodInfoOfThisIme().getPackageName()); - return false; - } - final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis); - final List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeList(nextImi, - true /* allowsImplicitlySelectedSubtypes */); - if (enabledSubtypes.isEmpty()) { - // The next IME has no subtype. - imm.setInputMethod(token, nextImi.getId()); - return true; - } - final InputMethodSubtype firstSubtype = enabledSubtypes.get(0); - imm.setInputMethodAndSubtype(token, nextImi.getId(), firstSubtype); - return true; - } - - private static int getImiIndexInList(final InputMethodInfo inputMethodInfo, - final List<InputMethodInfo> imiList) { - final int count = imiList.size(); - for (int index = 0; index < count; index++) { - final InputMethodInfo imi = imiList.get(index); - if (imi.equals(inputMethodInfo)) { - return index; - } - } - return INDEX_NOT_FOUND; - } - - // This method mimics {@link InputMethodManager#switchToNextInputMethod(IBinder,boolean)}. - private static InputMethodInfo getNextNonAuxiliaryIme(final int currentIndex, - final List<InputMethodInfo> imiList) { - final int count = imiList.size(); - for (int i = 1; i < count; i++) { - final int nextIndex = (currentIndex + i) % count; - final InputMethodInfo nextImi = imiList.get(nextIndex); - if (!isAuxiliaryIme(nextImi)) { - return nextImi; - } - } - return imiList.get(currentIndex); - } - - // Copied from {@link InputMethodInfo}. See how auxiliary of IME is determined. - private static boolean isAuxiliaryIme(final InputMethodInfo imi) { - final int count = imi.getSubtypeCount(); - if (count == 0) { - return false; - } - for (int index = 0; index < count; index++) { - final InputMethodSubtype subtype = imi.getSubtypeAt(index); - if (!subtype.isAuxiliary()) { - return false; - } - } - return true; - } - - private static class InputMethodInfoCache { - private final InputMethodManager mImm; - private final String mImePackageName; - - private InputMethodInfo mCachedThisImeInfo; - private final HashMap<InputMethodInfo, List<InputMethodSubtype>> - mCachedSubtypeListWithImplicitlySelected; - private final HashMap<InputMethodInfo, List<InputMethodSubtype>> - mCachedSubtypeListOnlyExplicitlySelected; - - public InputMethodInfoCache(final InputMethodManager imm, final String imePackageName) { - mImm = imm; - mImePackageName = imePackageName; - mCachedSubtypeListWithImplicitlySelected = new HashMap<>(); - mCachedSubtypeListOnlyExplicitlySelected = new HashMap<>(); - } - - public synchronized InputMethodInfo getInputMethodOfThisIme() { - if (mCachedThisImeInfo != null) { - return mCachedThisImeInfo; - } - for (final InputMethodInfo imi : mImm.getInputMethodList()) { - if (imi.getPackageName().equals(mImePackageName)) { - mCachedThisImeInfo = imi; - return imi; - } - } - throw new RuntimeException("Input method id for " + mImePackageName + " not found."); - } - - public synchronized List<InputMethodSubtype> getEnabledInputMethodSubtypeList( - final InputMethodInfo imi, final boolean allowsImplicitlySelectedSubtypes) { - final HashMap<InputMethodInfo, List<InputMethodSubtype>> cache = - allowsImplicitlySelectedSubtypes - ? mCachedSubtypeListWithImplicitlySelected - : mCachedSubtypeListOnlyExplicitlySelected; - final List<InputMethodSubtype> cachedList = cache.get(imi); - if (cachedList != null) { - return cachedList; - } - final List<InputMethodSubtype> result = mImm.getEnabledInputMethodSubtypeList( - imi, allowsImplicitlySelectedSubtypes); - cache.put(imi, result); - return result; - } - - public synchronized void clear() { - mCachedThisImeInfo = null; - mCachedSubtypeListWithImplicitlySelected.clear(); - mCachedSubtypeListOnlyExplicitlySelected.clear(); - } - } - - public InputMethodInfo getInputMethodInfoOfThisIme() { - return mInputMethodInfoCache.getInputMethodOfThisIme(); - } - - public String getInputMethodIdOfThisIme() { - return getInputMethodInfoOfThisIme().getId(); - } - - public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) { - return checkIfSubtypeBelongsToList(subtype, - getEnabledInputMethodSubtypeList( - getInputMethodInfoOfThisIme(), - true /* allowsImplicitlySelectedSubtypes */)); - } - - public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( - final InputMethodSubtype subtype) { - final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype); - final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(subtype, - getMyEnabledInputMethodSubtypeList(false /* allowsImplicitlySelectedSubtypes */)); - return subtypeEnabled && !subtypeExplicitlyEnabled; - } - - private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype, - final List<InputMethodSubtype> subtypes) { - return getSubtypeIndexInList(subtype, subtypes) != INDEX_NOT_FOUND; - } - - private static int getSubtypeIndexInList(final InputMethodSubtype subtype, - final List<InputMethodSubtype> subtypes) { - final int count = subtypes.size(); - for (int index = 0; index < count; index++) { - final InputMethodSubtype ims = subtypes.get(index); - if (ims.equals(subtype)) { - return index; - } - } - return INDEX_NOT_FOUND; - } - - public void onSubtypeChanged(@Nonnull final InputMethodSubtype newSubtype) { - updateCurrentSubtype(newSubtype); - updateShortcutIme(); - if (DEBUG) { - Log.w(TAG, "onSubtypeChanged: " + mCurrentRichInputMethodSubtype.getNameForLogging()); - } - } - - private static RichInputMethodSubtype sForcedSubtypeForTesting = null; - - @UsedForTesting - static void forceSubtype(@Nonnull final InputMethodSubtype subtype) { - sForcedSubtypeForTesting = RichInputMethodSubtype.getRichInputMethodSubtype(subtype); - } - - @Nonnull - public Locale getCurrentSubtypeLocale() { - if (null != sForcedSubtypeForTesting) { - return sForcedSubtypeForTesting.getLocale(); - } - return getCurrentSubtype().getLocale(); - } - - @Nonnull - public RichInputMethodSubtype getCurrentSubtype() { - if (null != sForcedSubtypeForTesting) { - return sForcedSubtypeForTesting; - } - return mCurrentRichInputMethodSubtype; - } - - - public String getCombiningRulesExtraValueOfCurrentSubtype() { - return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype().getRawSubtype()); - } - - public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) { - final List<InputMethodInfo> enabledImis = mImmWrapper.mImm.getEnabledInputMethodList(); - return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis); - } - - public boolean hasMultipleEnabledSubtypesInThisIme( - final boolean shouldIncludeAuxiliarySubtypes) { - final List<InputMethodInfo> imiList = Collections.singletonList( - getInputMethodInfoOfThisIme()); - return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList); - } - - private boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes, - final List<InputMethodInfo> imiList) { - // Number of the filtered IMEs - int filteredImisCount = 0; - - for (InputMethodInfo imi : imiList) { - // We can return true immediately after we find two or more filtered IMEs. - if (filteredImisCount > 1) return true; - final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeList(imi, true); - // IMEs that have no subtypes should be counted. - if (subtypes.isEmpty()) { - ++filteredImisCount; - continue; - } - - int auxCount = 0; - for (InputMethodSubtype subtype : subtypes) { - if (subtype.isAuxiliary()) { - ++auxCount; - } - } - final int nonAuxCount = subtypes.size() - auxCount; - - // IMEs that have one or more non-auxiliary subtypes should be counted. - // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary - // subtypes should be counted as well. - if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { - ++filteredImisCount; - } - } - - if (filteredImisCount > 1) { - return true; - } - final List<InputMethodSubtype> subtypes = getMyEnabledInputMethodSubtypeList(true); - int keyboardCount = 0; - // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's - // both explicitly and implicitly enabled input method subtype. - // (The current IME should be LatinIME.) - for (InputMethodSubtype subtype : subtypes) { - if (KEYBOARD_MODE.equals(subtype.getMode())) { - ++keyboardCount; - } - } - return keyboardCount > 1; - } - - public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString, - final String keyboardLayoutSetName) { - final InputMethodInfo myImi = getInputMethodInfoOfThisIme(); - final int count = myImi.getSubtypeCount(); - for (int i = 0; i < count; i++) { - final InputMethodSubtype subtype = myImi.getSubtypeAt(i); - final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); - if (localeString.equals(subtype.getLocale()) - && keyboardLayoutSetName.equals(layoutName)) { - return subtype; - } - } - return null; - } - - public InputMethodSubtype findSubtypeByLocale(final Locale locale) { - // Find the best subtype based on a straightforward matching algorithm. - // TODO: Use LocaleList#getFirstMatch() instead. - final List<InputMethodSubtype> subtypes = - getMyEnabledInputMethodSubtypeList(true /* allowsImplicitlySelectedSubtypes */); - final int count = subtypes.size(); - for (int i = 0; i < count; ++i) { - final InputMethodSubtype subtype = subtypes.get(i); - final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); - if (subtypeLocale.equals(locale)) { - return subtype; - } - } - for (int i = 0; i < count; ++i) { - final InputMethodSubtype subtype = subtypes.get(i); - final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); - if (subtypeLocale.getLanguage().equals(locale.getLanguage()) && - subtypeLocale.getCountry().equals(locale.getCountry()) && - subtypeLocale.getVariant().equals(locale.getVariant())) { - return subtype; - } - } - for (int i = 0; i < count; ++i) { - final InputMethodSubtype subtype = subtypes.get(i); - final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); - if (subtypeLocale.getLanguage().equals(locale.getLanguage()) && - subtypeLocale.getCountry().equals(locale.getCountry())) { - return subtype; - } - } - for (int i = 0; i < count; ++i) { - final InputMethodSubtype subtype = subtypes.get(i); - final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); - if (subtypeLocale.getLanguage().equals(locale.getLanguage())) { - return subtype; - } - } - return null; - } - - public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) { - mImmWrapper.mImm.setInputMethodAndSubtype( - token, getInputMethodIdOfThisIme(), subtype); - } - - public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) { - mImmWrapper.mImm.setAdditionalInputMethodSubtypes( - getInputMethodIdOfThisIme(), subtypes); - // Clear the cache so that we go read the {@link InputMethodInfo} of this IME and list of - // subtypes again next time. - refreshSubtypeCaches(); - } - - private List<InputMethodSubtype> getEnabledInputMethodSubtypeList(final InputMethodInfo imi, - final boolean allowsImplicitlySelectedSubtypes) { - return mInputMethodInfoCache.getEnabledInputMethodSubtypeList( - imi, allowsImplicitlySelectedSubtypes); - } - - public void refreshSubtypeCaches() { - mInputMethodInfoCache.clear(); - updateCurrentSubtype(mImmWrapper.mImm.getCurrentInputMethodSubtype()); - updateShortcutIme(); - } - - public boolean shouldOfferSwitchingToNextInputMethod(final IBinder binder, - boolean defaultValue) { - // Use the default value instead on Jelly Bean MR2 and previous where - // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} isn't yet available - // and on KitKat where the API is still just a stub to return true always. - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - return defaultValue; - } - return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder); - } - - public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() { - final Locale systemLocale = mContext.getResources().getConfiguration().locale; - final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>(); - final InputMethodManager inputMethodManager = getInputMethodManager(); - final List<InputMethodInfo> enabledInputMethodInfoList = - inputMethodManager.getEnabledInputMethodList(); - for (final InputMethodInfo info : enabledInputMethodInfoList) { - final List<InputMethodSubtype> enabledSubtypes = - inputMethodManager.getEnabledInputMethodSubtypeList( - info, true /* allowsImplicitlySelectedSubtypes */); - if (enabledSubtypes.isEmpty()) { - // An IME with no subtypes is found. - return false; - } - enabledSubtypesOfEnabledImes.addAll(enabledSubtypes); - } - for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) { - if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty() - && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) { - return false; - } - } - return true; - } - - private void updateCurrentSubtype(@Nullable final InputMethodSubtype subtype) { - mCurrentRichInputMethodSubtype = RichInputMethodSubtype.getRichInputMethodSubtype(subtype); - } - - private void updateShortcutIme() { - if (DEBUG) { - Log.d(TAG, "Update shortcut IME from : " - + (mShortcutInputMethodInfo == null - ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " - + (mShortcutSubtype == null ? "<null>" : ( - mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); - } - final RichInputMethodSubtype richSubtype = mCurrentRichInputMethodSubtype; - final boolean implicitlyEnabledSubtype = checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( - richSubtype.getRawSubtype()); - final Locale systemLocale = mContext.getResources().getConfiguration().locale; - LanguageOnSpacebarUtils.onSubtypeChanged( - richSubtype, implicitlyEnabledSubtype, systemLocale); - LanguageOnSpacebarUtils.setEnabledSubtypes(getMyEnabledInputMethodSubtypeList( - true /* allowsImplicitlySelectedSubtypes */)); - - // TODO: Update an icon for shortcut IME - final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = - getInputMethodManager().getShortcutInputMethodsAndSubtypes(); - mShortcutInputMethodInfo = null; - mShortcutSubtype = null; - for (final InputMethodInfo imi : shortcuts.keySet()) { - final List<InputMethodSubtype> subtypes = shortcuts.get(imi); - // TODO: Returns the first found IMI for now. Should handle all shortcuts as - // appropriate. - mShortcutInputMethodInfo = imi; - // TODO: Pick up the first found subtype for now. Should handle all subtypes - // as appropriate. - mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null; - break; - } - if (DEBUG) { - Log.d(TAG, "Update shortcut IME to : " - + (mShortcutInputMethodInfo == null - ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " - + (mShortcutSubtype == null ? "<null>" : ( - mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); - } - } - - public void switchToShortcutIme(final InputMethodService context) { - if (mShortcutInputMethodInfo == null) { - return; - } - - final String imiId = mShortcutInputMethodInfo.getId(); - switchToTargetIME(imiId, mShortcutSubtype, context); - } - - private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype, - final InputMethodService context) { - final IBinder token = context.getWindow().getWindow().getAttributes().token; - if (token == null) { - return; - } - final InputMethodManager imm = getInputMethodManager(); - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - imm.setInputMethodAndSubtype(token, imiId, subtype); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public boolean isShortcutImeReady() { - if (mShortcutInputMethodInfo == null) { - return false; - } - if (mShortcutSubtype == null) { - return true; - } - return true; - } -} diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java deleted file mode 100644 index 941149895..000000000 --- a/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin; - -import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE; - -import android.os.Build; -import android.util.Log; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.compat.BuildCompatUtils; -import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.LocaleUtils; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; - -import java.util.HashMap; -import java.util.Locale; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Enrichment class for InputMethodSubtype to enable concurrent multi-lingual input. - * - * Right now, this returns the extra value of its primary subtype. - */ -// non final for easy mocking. -public class RichInputMethodSubtype { - private static final String TAG = RichInputMethodSubtype.class.getSimpleName(); - - private static final HashMap<Locale, Locale> sLocaleMap = initializeLocaleMap(); - private static final HashMap<Locale, Locale> initializeLocaleMap() { - final HashMap<Locale, Locale> map = new HashMap<>(); - if (BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - // Locale#forLanguageTag is available on API Level 21+. - // TODO: Remove this workaround once when we become able to deal with "sr-Latn". - map.put(Locale.forLanguageTag("sr-Latn"), new Locale("sr_ZZ")); - } - return map; - } - - @Nonnull - private final InputMethodSubtype mSubtype; - @Nonnull - private final Locale mLocale; - @Nonnull - private final Locale mOriginalLocale; - - public RichInputMethodSubtype(@Nonnull final InputMethodSubtype subtype) { - mSubtype = subtype; - mOriginalLocale = InputMethodSubtypeCompatUtils.getLocaleObject(mSubtype); - final Locale mappedLocale = sLocaleMap.get(mOriginalLocale); - mLocale = mappedLocale != null ? mappedLocale : mOriginalLocale; - } - - // Extra values are determined by the primary subtype. This is probably right, but - // we may have to revisit this later. - public String getExtraValueOf(@Nonnull final String key) { - return mSubtype.getExtraValueOf(key); - } - - // The mode is also determined by the primary subtype. - public String getMode() { - return mSubtype.getMode(); - } - - public boolean isNoLanguage() { - return SubtypeLocaleUtils.NO_LANGUAGE.equals(mSubtype.getLocale()); - } - - public String getNameForLogging() { - return toString(); - } - - // InputMethodSubtype's display name for spacebar text in its locale. - // isAdditionalSubtype (T=true, F=false) - // locale layout | Middle Full - // ------ ------- - --------- ---------------------- - // en_US qwerty F English English (US) exception - // en_GB qwerty F English English (UK) exception - // es_US spanish F Español Español (EE.UU.) exception - // fr azerty F Français Français - // fr_CA qwerty F Français Français (Canada) - // fr_CH swiss F Français Français (Suisse) - // de qwertz F Deutsch Deutsch - // de_CH swiss T Deutsch Deutsch (Schweiz) - // zz qwerty F QWERTY QWERTY - // fr qwertz T Français Français - // de qwerty T Deutsch Deutsch - // en_US azerty T English English (US) - // zz azerty T AZERTY AZERTY - // Get the RichInputMethodSubtype's full display name in its locale. - @Nonnull - public String getFullDisplayName() { - if (isNoLanguage()) { - return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype); - } - return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(mSubtype.getLocale()); - } - - // Get the RichInputMethodSubtype's middle display name in its locale. - @Nonnull - public String getMiddleDisplayName() { - if (isNoLanguage()) { - return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype); - } - return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(mSubtype.getLocale()); - } - - @Override - public boolean equals(final Object o) { - if (!(o instanceof RichInputMethodSubtype)) { - return false; - } - final RichInputMethodSubtype other = (RichInputMethodSubtype)o; - return mSubtype.equals(other.mSubtype) && mLocale.equals(other.mLocale); - } - - @Override - public int hashCode() { - return mSubtype.hashCode() + mLocale.hashCode(); - } - - @Override - public String toString() { - return "Multi-lingual subtype: " + mSubtype + ", " + mLocale; - } - - @Nonnull - public Locale getLocale() { - return mLocale; - } - - @Nonnull - public Locale getOriginalLocale() { - return mOriginalLocale; - } - - public boolean isRtlSubtype() { - // The subtype is considered RTL if the language of the main subtype is RTL. - return LocaleUtils.isRtlLanguage(mLocale); - } - - // TODO: remove this method - @Nonnull - public InputMethodSubtype getRawSubtype() { return mSubtype; } - - @Nonnull - public String getKeyboardLayoutSetName() { - return SubtypeLocaleUtils.getKeyboardLayoutSetName(mSubtype); - } - - public static RichInputMethodSubtype getRichInputMethodSubtype( - @Nullable final InputMethodSubtype subtype) { - if (subtype == null) { - return getNoLanguageSubtype(); - } else { - return new RichInputMethodSubtype(subtype); - } - } - - // Placeholer for no language QWERTY subtype. See {@link R.xml.method}. - private static final int SUBTYPE_ID_OF_PLACEHOLDER_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3; - private static final String EXTRA_VALUE_OF_PLACEHOLDER_NO_LANGUAGE_SUBTYPE = - "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY - + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE - + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE - + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE; - @Nonnull - private static final RichInputMethodSubtype PLACEHOLDER_NO_LANGUAGE_SUBTYPE = - new RichInputMethodSubtype(InputMethodSubtypeCompatUtils.newInputMethodSubtype( - R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark, - SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE, - EXTRA_VALUE_OF_PLACEHOLDER_NO_LANGUAGE_SUBTYPE, - false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, - SUBTYPE_ID_OF_PLACEHOLDER_NO_LANGUAGE_SUBTYPE)); - // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}. - // Placeholder Emoji subtype. See {@link R.xml.method}. - private static final int SUBTYPE_ID_OF_PLACEHOLDER_EMOJI_SUBTYPE = 0xd78b2ed0; - private static final String EXTRA_VALUE_OF_PLACEHOLDER_EMOJI_SUBTYPE = - "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI - + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE; - @Nonnull - private static final RichInputMethodSubtype PLACEHOLDER_EMOJI_SUBTYPE = new RichInputMethodSubtype( - InputMethodSubtypeCompatUtils.newInputMethodSubtype( - R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark, - SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE, - EXTRA_VALUE_OF_PLACEHOLDER_EMOJI_SUBTYPE, - false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, - SUBTYPE_ID_OF_PLACEHOLDER_EMOJI_SUBTYPE)); - private static RichInputMethodSubtype sNoLanguageSubtype; - private static RichInputMethodSubtype sEmojiSubtype; - - @Nonnull - public static RichInputMethodSubtype getNoLanguageSubtype() { - RichInputMethodSubtype noLanguageSubtype = sNoLanguageSubtype; - if (noLanguageSubtype == null) { - final InputMethodSubtype rawNoLanguageSubtype = RichInputMethodManager.getInstance() - .findSubtypeByLocaleAndKeyboardLayoutSet( - SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY); - if (rawNoLanguageSubtype != null) { - noLanguageSubtype = new RichInputMethodSubtype(rawNoLanguageSubtype); - } - } - if (noLanguageSubtype != null) { - sNoLanguageSubtype = noLanguageSubtype; - return noLanguageSubtype; - } - Log.w(TAG, "Can't find any language with QWERTY subtype"); - Log.w(TAG, "No input method subtype found; returning placeholder subtype: " - + PLACEHOLDER_NO_LANGUAGE_SUBTYPE); - return PLACEHOLDER_NO_LANGUAGE_SUBTYPE; - } - - @Nonnull - public static RichInputMethodSubtype getEmojiSubtype() { - RichInputMethodSubtype emojiSubtype = sEmojiSubtype; - if (emojiSubtype == null) { - final InputMethodSubtype rawEmojiSubtype = RichInputMethodManager.getInstance() - .findSubtypeByLocaleAndKeyboardLayoutSet( - SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI); - if (rawEmojiSubtype != null) { - emojiSubtype = new RichInputMethodSubtype(rawEmojiSubtype); - } - } - if (emojiSubtype != null) { - sEmojiSubtype = emojiSubtype; - return emojiSubtype; - } - Log.w(TAG, "Can't find emoji subtype"); - Log.w(TAG, "No input method subtype found; returning placeholder subtype: " - + PLACEHOLDER_EMOJI_SUBTYPE); - return PLACEHOLDER_EMOJI_SUBTYPE; - } -} diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java deleted file mode 100644 index da23617af..000000000 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.inputmethod.latin; - -import android.text.TextUtils; - -import static com.android.inputmethod.latin.define.DecoderSpecificConstants.SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION; -import static com.android.inputmethod.latin.define.DecoderSpecificConstants.SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION; - -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; -import com.android.inputmethod.latin.utils.AutoCorrectionUtils; -import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.SuggestionResults; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Locale; - -import javax.annotation.Nonnull; - -/** - * This class loads a dictionary and provides a list of suggestions for a given sequence of - * characters. This includes corrections and completions. - */ -public final class Suggest { - public static final String TAG = Suggest.class.getSimpleName(); - - // Session id for - // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}. - // We are sharing the same ID between typing and gesture to save RAM footprint. - public static final int SESSION_ID_TYPING = 0; - public static final int SESSION_ID_GESTURE = 0; - - // Close to -2**31 - private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000; - - private static final boolean DBG = DebugFlags.DEBUG_ENABLED; - private final DictionaryFacilitator mDictionaryFacilitator; - - private static final int MAXIMUM_AUTO_CORRECT_LENGTH_FOR_GERMAN = 12; - private static final HashMap<String, Integer> sLanguageToMaximumAutoCorrectionWithSpaceLength = - new HashMap<>(); - static { - // TODO: should we add Finnish here? - // TODO: This should not be hardcoded here but be written in the dictionary header - sLanguageToMaximumAutoCorrectionWithSpaceLength.put(Locale.GERMAN.getLanguage(), - MAXIMUM_AUTO_CORRECT_LENGTH_FOR_GERMAN); - } - - private float mAutoCorrectionThreshold; - private float mPlausibilityThreshold; - - public Suggest(final DictionaryFacilitator dictionaryFacilitator) { - mDictionaryFacilitator = dictionaryFacilitator; - } - - /** - * Set the normalized-score threshold for a suggestion to be considered strong enough that we - * will auto-correct to this. - * @param threshold the threshold - */ - public void setAutoCorrectionThreshold(final float threshold) { - mAutoCorrectionThreshold = threshold; - } - - /** - * Set the normalized-score threshold for what we consider a "plausible" suggestion, in - * the same dimension as the auto-correction threshold. - * @param threshold the threshold - */ - public void setPlausibilityThreshold(final float threshold) { - mPlausibilityThreshold = threshold; - } - - public interface OnGetSuggestedWordsCallback { - public void onGetSuggestedWords(final SuggestedWords suggestedWords); - } - - public void getSuggestedWords(final WordComposer wordComposer, - final NgramContext ngramContext, final Keyboard keyboard, - final SettingsValuesForSuggestion settingsValuesForSuggestion, - final boolean isCorrectionEnabled, final int inputStyle, final int sequenceNumber, - final OnGetSuggestedWordsCallback callback) { - if (wordComposer.isBatchMode()) { - getSuggestedWordsForBatchInput(wordComposer, ngramContext, keyboard, - settingsValuesForSuggestion, inputStyle, sequenceNumber, callback); - } else { - getSuggestedWordsForNonBatchInput(wordComposer, ngramContext, keyboard, - settingsValuesForSuggestion, inputStyle, isCorrectionEnabled, - sequenceNumber, callback); - } - } - - private static ArrayList<SuggestedWordInfo> getTransformedSuggestedWordInfoList( - final WordComposer wordComposer, final SuggestionResults results, - final int trailingSingleQuotesCount, final Locale defaultLocale) { - final boolean shouldMakeSuggestionsAllUpperCase = wordComposer.isAllUpperCase() - && !wordComposer.isResumed(); - final boolean isOnlyFirstCharCapitalized = - wordComposer.isOrWillBeOnlyFirstCharCapitalized(); - - final ArrayList<SuggestedWordInfo> suggestionsContainer = new ArrayList<>(results); - final int suggestionsCount = suggestionsContainer.size(); - if (isOnlyFirstCharCapitalized || shouldMakeSuggestionsAllUpperCase - || 0 != trailingSingleQuotesCount) { - for (int i = 0; i < suggestionsCount; ++i) { - final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); - final Locale wordLocale = wordInfo.mSourceDict.mLocale; - final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( - wordInfo, null == wordLocale ? defaultLocale : wordLocale, - shouldMakeSuggestionsAllUpperCase, isOnlyFirstCharCapitalized, - trailingSingleQuotesCount); - suggestionsContainer.set(i, transformedWordInfo); - } - } - return suggestionsContainer; - } - - private static SuggestedWordInfo getWhitelistedWordInfoOrNull( - @Nonnull final ArrayList<SuggestedWordInfo> suggestions) { - if (suggestions.isEmpty()) { - return null; - } - final SuggestedWordInfo firstSuggestedWordInfo = suggestions.get(0); - if (!firstSuggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) { - return null; - } - return firstSuggestedWordInfo; - } - - // Retrieves suggestions for non-batch input (typing, recorrection, predictions...) - // and calls the callback function with the suggestions. - private void getSuggestedWordsForNonBatchInput(final WordComposer wordComposer, - final NgramContext ngramContext, final Keyboard keyboard, - final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int inputStyleIfNotPrediction, final boolean isCorrectionEnabled, - final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { - final String typedWordString = wordComposer.getTypedWord(); - final int trailingSingleQuotesCount = - StringUtils.getTrailingSingleQuotesCount(typedWordString); - final String consideredWord = trailingSingleQuotesCount > 0 - ? typedWordString.substring(0, typedWordString.length() - trailingSingleQuotesCount) - : typedWordString; - - final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults( - wordComposer.getComposedDataSnapshot(), ngramContext, keyboard, - settingsValuesForSuggestion, SESSION_ID_TYPING, inputStyleIfNotPrediction); - final Locale locale = mDictionaryFacilitator.getLocale(); - final ArrayList<SuggestedWordInfo> suggestionsContainer = - getTransformedSuggestedWordInfoList(wordComposer, suggestionResults, - trailingSingleQuotesCount, locale); - - boolean foundInDictionary = false; - Dictionary sourceDictionaryOfRemovedWord = null; - for (final SuggestedWordInfo info : suggestionsContainer) { - // Search for the best dictionary, defined as the first one with the highest match - // quality we can find. - if (!foundInDictionary && typedWordString.equals(info.mWord)) { - // Use this source if the old match had lower quality than this match - sourceDictionaryOfRemovedWord = info.mSourceDict; - foundInDictionary = true; - break; - } - } - - final int firstOcurrenceOfTypedWordInSuggestions = - SuggestedWordInfo.removeDups(typedWordString, suggestionsContainer); - - final SuggestedWordInfo whitelistedWordInfo = - getWhitelistedWordInfoOrNull(suggestionsContainer); - final String whitelistedWord = whitelistedWordInfo == null - ? null : whitelistedWordInfo.mWord; - final boolean resultsArePredictions = !wordComposer.isComposingWord(); - - // We allow auto-correction if whitelisting is not required or the word is whitelisted, - // or if the word had more than one char and was not suggested. - final boolean allowsToBeAutoCorrected = - (SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION || whitelistedWord != null) - || (consideredWord.length() > 1 && (sourceDictionaryOfRemovedWord == null)); - - final boolean hasAutoCorrection; - // If correction is not enabled, we never auto-correct. This is for example for when - // the setting "Auto-correction" is "off": we still suggest, but we don't auto-correct. - if (!isCorrectionEnabled - // If the word does not allow to be auto-corrected, then we don't auto-correct. - || !allowsToBeAutoCorrected - // If we are doing prediction, then we never auto-correct of course - || resultsArePredictions - // If we don't have suggestion results, we can't evaluate the first suggestion - // for auto-correction - || suggestionResults.isEmpty() - // If the word has digits, we never auto-correct because it's likely the word - // was type with a lot of care - || wordComposer.hasDigits() - // If the word is mostly caps, we never auto-correct because this is almost - // certainly intentional (and careful input) - || wordComposer.isMostlyCaps() - // We never auto-correct when suggestions are resumed because it would be unexpected - || wordComposer.isResumed() - // If we don't have a main dictionary, we never want to auto-correct. The reason - // for this is, the user may have a contact whose name happens to match a valid - // word in their language, and it will unexpectedly auto-correct. For example, if - // the user types in English with no dictionary and has a "Will" in their contact - // list, "will" would always auto-correct to "Will" which is unwanted. Hence, no - // main dict => no auto-correct. Also, it would probably get obnoxious quickly. - // TODO: now that we have personalization, we may want to re-evaluate this decision - || !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary() - // If the first suggestion is a shortcut we never auto-correct to it, regardless - // of how strong it is (allowlist entries are not KIND_SHORTCUT but KIND_WHITELIST). - // TODO: we may want to have shortcut-only entries auto-correct in the future. - || suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) { - hasAutoCorrection = false; - } else { - final SuggestedWordInfo firstSuggestion = suggestionResults.first(); - if (suggestionResults.mFirstSuggestionExceedsConfidenceThreshold - && firstOcurrenceOfTypedWordInSuggestions != 0) { - hasAutoCorrection = true; - } else if (!AutoCorrectionUtils.suggestionExceedsThreshold( - firstSuggestion, consideredWord, mAutoCorrectionThreshold)) { - // Score is too low for autocorrect - hasAutoCorrection = false; - } else { - // We have a high score, so we need to check if this suggestion is in the correct - // form to allow auto-correcting to it in this language. For details of how this - // is determined, see #isAllowedByAutoCorrectionWithSpaceFilter. - // TODO: this should not have its own logic here but be handled by the dictionary. - hasAutoCorrection = isAllowedByAutoCorrectionWithSpaceFilter(firstSuggestion); - } - } - - final SuggestedWordInfo typedWordInfo = new SuggestedWordInfo(typedWordString, - "" /* prevWordsContext */, SuggestedWordInfo.MAX_SCORE, - SuggestedWordInfo.KIND_TYPED, - null == sourceDictionaryOfRemovedWord ? Dictionary.DICTIONARY_USER_TYPED - : sourceDictionaryOfRemovedWord, - SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, - SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */); - if (!TextUtils.isEmpty(typedWordString)) { - suggestionsContainer.add(0, typedWordInfo); - } - - final ArrayList<SuggestedWordInfo> suggestionsList; - if (DBG && !suggestionsContainer.isEmpty()) { - suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWordString, - suggestionsContainer); - } else { - suggestionsList = suggestionsContainer; - } - - final int inputStyle; - if (resultsArePredictions) { - inputStyle = suggestionResults.mIsBeginningOfSentence - ? SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION - : SuggestedWords.INPUT_STYLE_PREDICTION; - } else { - inputStyle = inputStyleIfNotPrediction; - } - - final boolean isTypedWordValid = firstOcurrenceOfTypedWordInSuggestions > -1 - || (!resultsArePredictions && !allowsToBeAutoCorrected); - callback.onGetSuggestedWords(new SuggestedWords(suggestionsList, - suggestionResults.mRawSuggestions, typedWordInfo, - isTypedWordValid, - hasAutoCorrection /* willAutoCorrect */, - false /* isObsoleteSuggestions */, inputStyle, sequenceNumber)); - } - - // Retrieves suggestions for the batch input - // and calls the callback function with the suggestions. - private void getSuggestedWordsForBatchInput(final WordComposer wordComposer, - final NgramContext ngramContext, final Keyboard keyboard, - final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int inputStyle, final int sequenceNumber, - final OnGetSuggestedWordsCallback callback) { - final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults( - wordComposer.getComposedDataSnapshot(), ngramContext, keyboard, - settingsValuesForSuggestion, SESSION_ID_GESTURE, inputStyle); - // For transforming words that don't come from a dictionary, because it's our best bet - final Locale locale = mDictionaryFacilitator.getLocale(); - final ArrayList<SuggestedWordInfo> suggestionsContainer = - new ArrayList<>(suggestionResults); - final int suggestionsCount = suggestionsContainer.size(); - final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock(); - final boolean isAllUpperCase = wordComposer.isAllUpperCase(); - if (isFirstCharCapitalized || isAllUpperCase) { - for (int i = 0; i < suggestionsCount; ++i) { - final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); - final Locale wordlocale = wordInfo.mSourceDict.mLocale; - final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( - wordInfo, null == wordlocale ? locale : wordlocale, isAllUpperCase, - isFirstCharCapitalized, 0 /* trailingSingleQuotesCount */); - suggestionsContainer.set(i, transformedWordInfo); - } - } - - if (SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION - && suggestionsContainer.size() > 1 - && TextUtils.equals(suggestionsContainer.get(0).mWord, - wordComposer.getRejectedBatchModeSuggestion())) { - final SuggestedWordInfo rejected = suggestionsContainer.remove(0); - suggestionsContainer.add(1, rejected); - } - SuggestedWordInfo.removeDups(null /* typedWord */, suggestionsContainer); - - // For some reason some suggestions with MIN_VALUE are making their way here. - // TODO: Find a more robust way to detect distracters. - for (int i = suggestionsContainer.size() - 1; i >= 0; --i) { - if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) { - suggestionsContainer.remove(i); - } - } - - // In the batch input mode, the most relevant suggested word should act as a "typed word" - // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false). - // Note that because this method is never used to get predictions, there is no need to - // modify inputType such in getSuggestedWordsForNonBatchInput. - final SuggestedWordInfo pseudoTypedWordInfo = suggestionsContainer.isEmpty() ? null - : suggestionsContainer.get(0); - - callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer, - suggestionResults.mRawSuggestions, - pseudoTypedWordInfo, - true /* typedWordValid */, - false /* willAutoCorrect */, - false /* isObsoleteSuggestions */, - inputStyle, sequenceNumber)); - } - - private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo( - final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) { - final SuggestedWordInfo typedWordInfo = suggestions.get(0); - typedWordInfo.setDebugString("+"); - final int suggestionsSize = suggestions.size(); - final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>(suggestionsSize); - suggestionsList.add(typedWordInfo); - // Note: i here is the index in mScores[], but the index in mSuggestions is one more - // than i because we added the typed word to mSuggestions without touching mScores. - for (int i = 0; i < suggestionsSize - 1; ++i) { - final SuggestedWordInfo cur = suggestions.get(i + 1); - final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore( - typedWord, cur.toString(), cur.mScore); - final String scoreInfoString; - if (normalizedScore > 0) { - scoreInfoString = String.format( - Locale.ROOT, "%d (%4.2f), %s", cur.mScore, normalizedScore, - cur.mSourceDict.mDictType); - } else { - scoreInfoString = Integer.toString(cur.mScore); - } - cur.setDebugString(scoreInfoString); - suggestionsList.add(cur); - } - return suggestionsList; - } - - /** - * Computes whether this suggestion should be blocked or not in this language - * - * This function implements a filter that avoids auto-correcting to suggestions that contain - * spaces that are above a certain language-dependent character limit. In languages like German - * where it's possible to concatenate many words, it often happens our dictionary does not - * have the longer words. In this case, we offer a lot of unhelpful suggestions that contain - * one or several spaces. Ideally we should understand what the user wants and display useful - * suggestions by improving the dictionary and possibly having some specific logic. Until - * that's possible we should avoid displaying unhelpful suggestions. But it's hard to tell - * whether a suggestion is useful or not. So at least for the time being we block - * auto-correction when the suggestion is long and contains a space, which should avoid the - * worst damage. - * This function is implementing that filter. If the language enforces no such limit, then it - * always returns true. If the suggestion contains no space, it also returns true. Otherwise, - * it checks the length against the language-specific limit. - * - * @param info the suggestion info - * @return whether it's fine to auto-correct to this. - */ - private static boolean isAllowedByAutoCorrectionWithSpaceFilter(final SuggestedWordInfo info) { - final Locale locale = info.mSourceDict.mLocale; - if (null == locale) { - return true; - } - final Integer maximumLengthForThisLanguage = - sLanguageToMaximumAutoCorrectionWithSpaceLength.get(locale.getLanguage()); - if (null == maximumLengthForThisLanguage) { - // This language does not enforce a maximum length to auto-correction - return true; - } - return info.mWord.length() <= maximumLengthForThisLanguage - || -1 == info.mWord.indexOf(Constants.CODE_SPACE); - } - - /* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo( - final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase, - final boolean isOnlyFirstCharCapitalized, final int trailingSingleQuotesCount) { - final StringBuilder sb = new StringBuilder(wordInfo.mWord.length()); - if (isAllUpperCase) { - sb.append(wordInfo.mWord.toUpperCase(locale)); - } else if (isOnlyFirstCharCapitalized) { - sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale)); - } else { - sb.append(wordInfo.mWord); - } - // Appending quotes is here to help people quote words. However, it's not helpful - // when they type words with quotes toward the end like "it's" or "didn't", where - // it's more likely the user missed the last character (or didn't type it yet). - final int quotesToAppend = trailingSingleQuotesCount - - (-1 == wordInfo.mWord.indexOf(Constants.CODE_SINGLE_QUOTE) ? 0 : 1); - for (int i = quotesToAppend - 1; i >= 0; --i) { - sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE); - } - return new SuggestedWordInfo(sb.toString(), wordInfo.mPrevWordsContext, - wordInfo.mScore, wordInfo.mKindAndFlags, - wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord, - wordInfo.mAutoCommitFirstWordConfidence); - } -} diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java deleted file mode 100644 index bcd4d5f69..000000000 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.latin; - -import android.text.TextUtils; -import android.view.inputmethod.CompletionInfo; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.define.DebugFlags; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class SuggestedWords { - public static final int INDEX_OF_TYPED_WORD = 0; - public static final int INDEX_OF_AUTO_CORRECTION = 1; - public static final int NOT_A_SEQUENCE_NUMBER = -1; - - public static final int INPUT_STYLE_NONE = 0; - public static final int INPUT_STYLE_TYPING = 1; - public static final int INPUT_STYLE_UPDATE_BATCH = 2; - public static final int INPUT_STYLE_TAIL_BATCH = 3; - public static final int INPUT_STYLE_APPLICATION_SPECIFIED = 4; - public static final int INPUT_STYLE_RECORRECTION = 5; - public static final int INPUT_STYLE_PREDICTION = 6; - public static final int INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION = 7; - - // The maximum number of suggestions available. - public static final int MAX_SUGGESTIONS = 18; - - private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = new ArrayList<>(0); - @Nonnull - private static final SuggestedWords EMPTY = new SuggestedWords( - EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, null /* typedWord */, - false /* typedWordValid */, false /* willAutoCorrect */, - false /* isObsoleteSuggestions */, INPUT_STYLE_NONE, NOT_A_SEQUENCE_NUMBER); - - @Nullable - public final SuggestedWordInfo mTypedWordInfo; - public final boolean mTypedWordValid; - // Note: this INCLUDES cases where the word will auto-correct to itself. A good definition - // of what this flag means would be "the top suggestion is strong enough to auto-correct", - // whether this exactly matches the user entry or not. - public final boolean mWillAutoCorrect; - public final boolean mIsObsoleteSuggestions; - // How the input for these suggested words was done by the user. Must be one of the - // INPUT_STYLE_* constants above. - public final int mInputStyle; - public final int mSequenceNumber; // Sequence number for auto-commit. - @Nonnull - protected final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList; - @Nullable - public final ArrayList<SuggestedWordInfo> mRawSuggestions; - - public SuggestedWords(@Nonnull final ArrayList<SuggestedWordInfo> suggestedWordInfoList, - @Nullable final ArrayList<SuggestedWordInfo> rawSuggestions, - @Nullable final SuggestedWordInfo typedWordInfo, - final boolean typedWordValid, - final boolean willAutoCorrect, - final boolean isObsoleteSuggestions, - final int inputStyle, - final int sequenceNumber) { - mSuggestedWordInfoList = suggestedWordInfoList; - mRawSuggestions = rawSuggestions; - mTypedWordValid = typedWordValid; - mWillAutoCorrect = willAutoCorrect; - mIsObsoleteSuggestions = isObsoleteSuggestions; - mInputStyle = inputStyle; - mSequenceNumber = sequenceNumber; - mTypedWordInfo = typedWordInfo; - } - - public boolean isEmpty() { - return mSuggestedWordInfoList.isEmpty(); - } - - public int size() { - return mSuggestedWordInfoList.size(); - } - - /** - * Get suggested word to show as suggestions to UI. - * - * @param shouldShowLxxSuggestionUi true if showing suggestion UI introduced in LXX and later. - * @return the count of suggested word to show as suggestions to UI. - */ - public int getWordCountToShow(final boolean shouldShowLxxSuggestionUi) { - if (isPrediction() || !shouldShowLxxSuggestionUi) { - return size(); - } - return size() - /* typed word */ 1; - } - - /** - * Get {@link SuggestedWordInfo} object for the typed word. - * @return The {@link SuggestedWordInfo} object for the typed word. - */ - public SuggestedWordInfo getTypedWordInfo() { - return mTypedWordInfo; - } - - /** - * Get suggested word at <code>index</code>. - * @param index The index of the suggested word. - * @return The suggested word. - */ - public String getWord(final int index) { - return mSuggestedWordInfoList.get(index).mWord; - } - - /** - * Get displayed text at <code>index</code>. - * In RTL languages, the displayed text on the suggestion strip may be different from the - * suggested word that is returned from {@link #getWord(int)}. For example the displayed text - * of punctuation suggestion "(" should be ")". - * @param index The index of the text to display. - * @return The text to be displayed. - */ - public String getLabel(final int index) { - return mSuggestedWordInfoList.get(index).mWord; - } - - /** - * Get {@link SuggestedWordInfo} object at <code>index</code>. - * @param index The index of the {@link SuggestedWordInfo}. - * @return The {@link SuggestedWordInfo} object. - */ - public SuggestedWordInfo getInfo(final int index) { - return mSuggestedWordInfoList.get(index); - } - - /** - * Gets the suggestion index from the suggestions list. - * @param suggestedWordInfo The {@link SuggestedWordInfo} to find the index. - * @return The position of the suggestion in the suggestion list. - */ - public int indexOf(SuggestedWordInfo suggestedWordInfo) { - return mSuggestedWordInfoList.indexOf(suggestedWordInfo); - } - - public String getDebugString(final int pos) { - if (!DebugFlags.DEBUG_ENABLED) { - return null; - } - final SuggestedWordInfo wordInfo = getInfo(pos); - if (wordInfo == null) { - return null; - } - final String debugString = wordInfo.getDebugString(); - if (TextUtils.isEmpty(debugString)) { - return null; - } - return debugString; - } - - /** - * The predicator to tell whether this object represents punctuation suggestions. - * @return false if this object desn't represent punctuation suggestions. - */ - public boolean isPunctuationSuggestions() { - return false; - } - - @Override - public String toString() { - // Pretty-print method to help debug - return "SuggestedWords:" - + " mTypedWordValid=" + mTypedWordValid - + " mWillAutoCorrect=" + mWillAutoCorrect - + " mInputStyle=" + mInputStyle - + " words=" + Arrays.toString(mSuggestedWordInfoList.toArray()); - } - - public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions( - final CompletionInfo[] infos) { - final ArrayList<SuggestedWordInfo> result = new ArrayList<>(); - for (final CompletionInfo info : infos) { - if (null == info || null == info.getText()) { - continue; - } - result.add(new SuggestedWordInfo(info)); - } - return result; - } - - @Nonnull - public static final SuggestedWords getEmptyInstance() { - return SuggestedWords.EMPTY; - } - - // Should get rid of the first one (what the user typed previously) from suggestions - // and replace it with what the user currently typed. - public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions( - @Nonnull final SuggestedWordInfo typedWordInfo, - @Nonnull final SuggestedWords previousSuggestions) { - final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>(); - final HashSet<String> alreadySeen = new HashSet<>(); - suggestionsList.add(typedWordInfo); - alreadySeen.add(typedWordInfo.mWord); - final int previousSize = previousSuggestions.size(); - for (int index = 1; index < previousSize; index++) { - final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(index); - final String prevWord = prevWordInfo.mWord; - // Filter out duplicate suggestions. - if (!alreadySeen.contains(prevWord)) { - suggestionsList.add(prevWordInfo); - alreadySeen.add(prevWord); - } - } - return suggestionsList; - } - - public SuggestedWordInfo getAutoCommitCandidate() { - if (mSuggestedWordInfoList.size() <= 0) return null; - final SuggestedWordInfo candidate = mSuggestedWordInfoList.get(0); - return candidate.isEligibleForAutoCommit() ? candidate : null; - } - - // non-final for testability. - public static class SuggestedWordInfo { - public static final int NOT_AN_INDEX = -1; - public static final int NOT_A_CONFIDENCE = -1; - public static final int MAX_SCORE = Integer.MAX_VALUE; - - private static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind - public static final int KIND_TYPED = 0; // What user typed - public static final int KIND_CORRECTION = 1; // Simple correction/suggestion - public static final int KIND_COMPLETION = 2; // Completion (suggestion with appended chars) - public static final int KIND_WHITELIST = 3; // Whitelisted word - public static final int KIND_BLACKLIST = 4; // Blacklisted word - public static final int KIND_HARDCODED = 5; // Hardcoded suggestion, e.g. punctuation - public static final int KIND_APP_DEFINED = 6; // Suggested by the application - public static final int KIND_SHORTCUT = 7; // A shortcut - public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input) - // KIND_RESUMED: A resumed suggestion (comes from a span, currently this type is used only - // in java for re-correction) - public static final int KIND_RESUMED = 9; - public static final int KIND_OOV_CORRECTION = 10; // Most probable string correction - - public static final int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000; - public static final int KIND_FLAG_EXACT_MATCH = 0x40000000; - public static final int KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION = 0x20000000; - public static final int KIND_FLAG_APPROPRIATE_FOR_AUTO_CORRECTION = 0x10000000; - - public final String mWord; - public final String mPrevWordsContext; - // The completion info from the application. Null for suggestions that don't come from - // the application (including keyboard-computed ones, so this is almost always null) - public final CompletionInfo mApplicationSpecifiedCompletionInfo; - public final int mScore; - public final int mKindAndFlags; - public final int mCodePointCount; - @Deprecated - public final Dictionary mSourceDict; - // For auto-commit. This keeps track of the index inside the touch coordinates array - // passed to native code to get suggestions for a gesture that corresponds to the first - // letter of the second word. - public final int mIndexOfTouchPointOfSecondWord; - // For auto-commit. This is a measure of how confident we are that we can commit the - // first word of this suggestion. - public final int mAutoCommitFirstWordConfidence; - private String mDebugString = ""; - - /** - * Create a new suggested word info. - * @param word The string to suggest. - * @param prevWordsContext previous words context. - * @param score A measure of how likely this suggestion is. - * @param kindAndFlags The kind of suggestion, as one of the above KIND_* constants with - * flags. - * @param sourceDict What instance of Dictionary produced this suggestion. - * @param indexOfTouchPointOfSecondWord See mIndexOfTouchPointOfSecondWord. - * @param autoCommitFirstWordConfidence See mAutoCommitFirstWordConfidence. - */ - public SuggestedWordInfo(final String word, final String prevWordsContext, - final int score, final int kindAndFlags, - final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord, - final int autoCommitFirstWordConfidence) { - mWord = word; - mPrevWordsContext = prevWordsContext; - mApplicationSpecifiedCompletionInfo = null; - mScore = score; - mKindAndFlags = kindAndFlags; - mSourceDict = sourceDict; - mCodePointCount = StringUtils.codePointCount(mWord); - mIndexOfTouchPointOfSecondWord = indexOfTouchPointOfSecondWord; - mAutoCommitFirstWordConfidence = autoCommitFirstWordConfidence; - } - - /** - * Create a new suggested word info from an application-specified completion. - * If the passed argument or its contained text is null, this throws a NPE. - * @param applicationSpecifiedCompletion The application-specified completion info. - */ - public SuggestedWordInfo(final CompletionInfo applicationSpecifiedCompletion) { - mWord = applicationSpecifiedCompletion.getText().toString(); - mPrevWordsContext = ""; - mApplicationSpecifiedCompletionInfo = applicationSpecifiedCompletion; - mScore = SuggestedWordInfo.MAX_SCORE; - mKindAndFlags = SuggestedWordInfo.KIND_APP_DEFINED; - mSourceDict = Dictionary.DICTIONARY_APPLICATION_DEFINED; - mCodePointCount = StringUtils.codePointCount(mWord); - mIndexOfTouchPointOfSecondWord = SuggestedWordInfo.NOT_AN_INDEX; - mAutoCommitFirstWordConfidence = SuggestedWordInfo.NOT_A_CONFIDENCE; - } - - public boolean isEligibleForAutoCommit() { - return (isKindOf(KIND_CORRECTION) && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord); - } - - public int getKind() { - return (mKindAndFlags & KIND_MASK_KIND); - } - - public boolean isKindOf(final int kind) { - return getKind() == kind; - } - - public boolean isPossiblyOffensive() { - return (mKindAndFlags & KIND_FLAG_POSSIBLY_OFFENSIVE) != 0; - } - - public boolean isExactMatch() { - return (mKindAndFlags & KIND_FLAG_EXACT_MATCH) != 0; - } - - public boolean isExactMatchWithIntentionalOmission() { - return (mKindAndFlags & KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION) != 0; - } - - public boolean isAprapreateForAutoCorrection() { - return (mKindAndFlags & KIND_FLAG_APPROPRIATE_FOR_AUTO_CORRECTION) != 0; - } - - public void setDebugString(final String str) { - if (null == str) throw new NullPointerException("Debug info is null"); - mDebugString = str; - } - - public String getDebugString() { - return mDebugString; - } - - public String getWord() { - return mWord; - } - - @Deprecated - public Dictionary getSourceDictionary() { - return mSourceDict; - } - - public int codePointAt(int i) { - return mWord.codePointAt(i); - } - - @Override - public String toString() { - if (TextUtils.isEmpty(mDebugString)) { - return mWord; - } - return mWord + " (" + mDebugString + ")"; - } - - /** - * This will always remove the higher index if a duplicate is found. - * - * @return position of typed word in the candidate list - */ - public static int removeDups( - @Nullable final String typedWord, - @Nonnull final ArrayList<SuggestedWordInfo> candidates) { - if (candidates.isEmpty()) { - return -1; - } - int firstOccurrenceOfWord = -1; - if (!TextUtils.isEmpty(typedWord)) { - firstOccurrenceOfWord = removeSuggestedWordInfoFromList( - typedWord, candidates, -1 /* startIndexExclusive */); - } - for (int i = 0; i < candidates.size(); ++i) { - removeSuggestedWordInfoFromList( - candidates.get(i).mWord, candidates, i /* startIndexExclusive */); - } - return firstOccurrenceOfWord; - } - - private static int removeSuggestedWordInfoFromList( - @Nonnull final String word, - @Nonnull final ArrayList<SuggestedWordInfo> candidates, - final int startIndexExclusive) { - int firstOccurrenceOfWord = -1; - for (int i = startIndexExclusive + 1; i < candidates.size(); ++i) { - final SuggestedWordInfo previous = candidates.get(i); - if (word.equals(previous.mWord)) { - if (firstOccurrenceOfWord == -1) { - firstOccurrenceOfWord = i; - } - candidates.remove(i); - --i; - } - } - return firstOccurrenceOfWord; - } - } - - private static boolean isPrediction(final int inputStyle) { - return INPUT_STYLE_PREDICTION == inputStyle - || INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION == inputStyle; - } - - public boolean isPrediction() { - return isPrediction(mInputStyle); - } - - /** - * @return the {@link SuggestedWordInfo} which corresponds to the word that is originally - * typed by the user. Otherwise returns {@code null}. Note that gesture input is not - * considered to be a typed word. - */ - @UsedForTesting - public SuggestedWordInfo getTypedWordInfoOrNull() { - if (SuggestedWords.INDEX_OF_TYPED_WORD >= size()) { - return null; - } - final SuggestedWordInfo info = getInfo(SuggestedWords.INDEX_OF_TYPED_WORD); - return (info.getKind() == SuggestedWordInfo.KIND_TYPED) ? info : null; - } -} diff --git a/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java deleted file mode 100644 index 90221512f..000000000 --- a/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin; - -import android.app.DownloadManager; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.os.Process; -import android.preference.PreferenceManager; -import android.util.Log; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.dictionarypack.DictionaryPackConstants; -import com.android.inputmethod.dictionarypack.DownloadManagerWrapper; -import com.android.inputmethod.keyboard.KeyboardLayoutSet; -import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.setup.SetupActivity; -import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils; - -/** - * This class detects the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME - * package has been replaced by a newer version of the same package. This class also detects - * {@link Intent#ACTION_BOOT_COMPLETED} and {@link Intent#ACTION_USER_INITIALIZE} broadcast intent. - * - * If this IME has already been installed in the system image and a new version of this IME has - * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver and it - * will hide the setup wizard's icon. - * - * If this IME has already been installed in the data partition and a new version of this IME has - * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver but it - * will not hide the setup wizard's icon, and the icon will appear on the launcher. - * - * If this IME hasn't been installed yet and has been newly installed, no - * {@link Intent#ACTION_MY_PACKAGE_REPLACED} will be sent and the setup wizard's icon will appear - * on the launcher. - * - * When the device has been booted, {@link Intent#ACTION_BOOT_COMPLETED} is received by this - * receiver and it checks whether the setup wizard's icon should be appeared or not on the launcher - * depending on which partition this IME is installed. - * - * When the system locale has been changed, {@link Intent#ACTION_LOCALE_CHANGED} is received by - * this receiver and the {@link KeyboardLayoutSet}'s cache is cleared. - */ -public final class SystemBroadcastReceiver extends BroadcastReceiver { - private static final String TAG = SystemBroadcastReceiver.class.getSimpleName(); - - @Override - public void onReceive(final Context context, final Intent intent) { - final String intentAction = intent.getAction(); - if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(intentAction)) { - Log.i(TAG, "Package has been replaced: " + context.getPackageName()); - // Need to restore additional subtypes because system always clears additional - // subtypes when the package is replaced. - RichInputMethodManager.init(context); - final RichInputMethodManager richImm = RichInputMethodManager.getInstance(); - final InputMethodSubtype[] additionalSubtypes = richImm.getAdditionalSubtypes(); - richImm.setAdditionalInputMethodSubtypes(additionalSubtypes); - toggleAppIcon(context); - - // Remove all the previously scheduled downloads. This will also makes sure - // that any erroneously stuck downloads will get cleared. (b/21797386) - removeOldDownloads(context); - // b/21797386 - // downloadLatestDictionaries(context); - } else if (Intent.ACTION_BOOT_COMPLETED.equals(intentAction)) { - Log.i(TAG, "Boot has been completed"); - toggleAppIcon(context); - } else if (Intent.ACTION_LOCALE_CHANGED.equals(intentAction)) { - Log.i(TAG, "System locale changed"); - KeyboardLayoutSet.onSystemLocaleChanged(); - } - - // The process that hosts this broadcast receiver is invoked and remains alive even after - // 1) the package has been re-installed, - // 2) the device has just booted, - // 3) a new user has been created. - // There is no good reason to keep the process alive if this IME isn't a current IME. - final InputMethodManager imm = (InputMethodManager) - context.getSystemService(Context.INPUT_METHOD_SERVICE); - // Called to check whether this IME has been triggered by the current user or not - final boolean isInputMethodManagerValidForUserOfThisProcess = - !imm.getInputMethodList().isEmpty(); - final boolean isCurrentImeOfCurrentUser = isInputMethodManagerValidForUserOfThisProcess - && UncachedInputMethodManagerUtils.isThisImeCurrent(context, imm); - if (!isCurrentImeOfCurrentUser) { - final int myPid = Process.myPid(); - Log.i(TAG, "Killing my process: pid=" + myPid); - Process.killProcess(myPid); - } - } - - private void removeOldDownloads(Context context) { - try { - Log.i(TAG, "Removing the old downloads in progress of the previous keyboard version."); - final DownloadManagerWrapper downloadManagerWrapper = new DownloadManagerWrapper( - context); - final DownloadManager.Query q = new DownloadManager.Query(); - // Query all the download statuses except the succeeded ones. - q.setFilterByStatus(DownloadManager.STATUS_FAILED - | DownloadManager.STATUS_PAUSED - | DownloadManager.STATUS_PENDING - | DownloadManager.STATUS_RUNNING); - final Cursor c = downloadManagerWrapper.query(q); - if (c != null) { - for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) { - final long downloadId = c - .getLong(c.getColumnIndex(DownloadManager.COLUMN_ID)); - downloadManagerWrapper.remove(downloadId); - Log.i(TAG, "Removed the download with Id: " + downloadId); - } - c.close(); - } - } catch (Exception e) { - Log.e(TAG, "Exception while removing old downloads."); - } - } - - private void downloadLatestDictionaries(Context context) { - final Intent updateIntent = new Intent( - DictionaryPackConstants.INIT_AND_UPDATE_NOW_INTENT_ACTION); - context.sendBroadcast(updateIntent); - } - - public static void toggleAppIcon(final Context context) { - final int appInfoFlags = context.getApplicationInfo().flags; - final boolean isSystemApp = (appInfoFlags & ApplicationInfo.FLAG_SYSTEM) > 0; - if (Log.isLoggable(TAG, Log.INFO)) { - Log.i(TAG, "toggleAppIcon() : FLAG_SYSTEM = " + isSystemApp); - } - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - context.getPackageManager().setComponentEnabledSetting( - new ComponentName(context, SetupActivity.class), - Settings.readShowSetupWizardIcon(prefs, context) - ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - } -} diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java deleted file mode 100644 index fe24ccfc2..000000000 --- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.net.Uri; -import android.provider.UserDictionary.Words; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.annotations.ExternallyReferenced; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; - -import java.io.File; -import java.util.Arrays; -import java.util.Locale; - -import javax.annotation.Nullable; - -/** - * An expandable dictionary that stores the words in the user dictionary provider into a binary - * dictionary file to use it from native code. - */ -public class UserBinaryDictionary extends ExpandableBinaryDictionary { - private static final String TAG = ExpandableBinaryDictionary.class.getSimpleName(); - - // The user dictionary provider uses an empty string to mean "all languages". - private static final String USER_DICTIONARY_ALL_LANGUAGES = ""; - private static final int HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY = 250; - private static final int LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY = 160; - - private static final String[] PROJECTION_QUERY = new String[] {Words.WORD, Words.FREQUENCY}; - - private static final String NAME = "userunigram"; - - private ContentObserver mObserver; - final private String mLocaleString; - final private boolean mAlsoUseMoreRestrictiveLocales; - - protected UserBinaryDictionary(final Context context, final Locale locale, - final boolean alsoUseMoreRestrictiveLocales, - final File dictFile, final String name) { - super(context, getDictName(name, locale, dictFile), locale, Dictionary.TYPE_USER, dictFile); - if (null == locale) throw new NullPointerException(); // Catch the error earlier - final String localeStr = locale.toString(); - if (SubtypeLocaleUtils.NO_LANGUAGE.equals(localeStr)) { - // If we don't have a locale, insert into the "all locales" user dictionary. - mLocaleString = USER_DICTIONARY_ALL_LANGUAGES; - } else { - mLocaleString = localeStr; - } - mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales; - ContentResolver cres = context.getContentResolver(); - - mObserver = new ContentObserver(null) { - @Override - public void onChange(final boolean self) { - // This hook is deprecated as of API level 16 (Build.VERSION_CODES.JELLY_BEAN), - // but should still be supported for cases where the IME is running on an older - // version of the platform. - onChange(self, null); - } - // The following hook is only available as of API level 16 - // (Build.VERSION_CODES.JELLY_BEAN), and as such it will only work on JellyBean+ - // devices. On older versions of the platform, the hook above will be called instead. - @Override - public void onChange(final boolean self, final Uri uri) { - setNeedsToRecreate(); - } - }; - cres.registerContentObserver(Words.CONTENT_URI, true, mObserver); - reloadDictionaryIfRequired(); - } - - // Note: This method is called by {@link DictionaryFacilitator} using Java reflection. - @ExternallyReferenced - public static UserBinaryDictionary getDictionary( - final Context context, final Locale locale, final File dictFile, - final String dictNamePrefix, @Nullable final String account) { - return new UserBinaryDictionary( - context, locale, false /* alsoUseMoreRestrictiveLocales */, - dictFile, dictNamePrefix + NAME); - } - - @Override - public synchronized void close() { - if (mObserver != null) { - mContext.getContentResolver().unregisterContentObserver(mObserver); - mObserver = null; - } - super.close(); - } - - @Override - public void loadInitialContentsLocked() { - // Split the locale. For example "en" => ["en"], "de_DE" => ["de", "DE"], - // "en_US_foo_bar_qux" => ["en", "US", "foo_bar_qux"] because of the limit of 3. - // This is correct for locale processing. - // For this example, we'll look at the "en_US_POSIX" case. - final String[] localeElements = - TextUtils.isEmpty(mLocaleString) ? new String[] {} : mLocaleString.split("_", 3); - final int length = localeElements.length; - - final StringBuilder request = new StringBuilder("(locale is NULL)"); - String localeSoFar = ""; - // At start, localeElements = ["en", "US", "POSIX"] ; localeSoFar = "" ; - // and request = "(locale is NULL)" - for (int i = 0; i < length; ++i) { - // i | localeSoFar | localeElements - // 0 | "" | ["en", "US", "POSIX"] - // 1 | "en_" | ["en", "US", "POSIX"] - // 2 | "en_US_" | ["en", "en_US", "POSIX"] - localeElements[i] = localeSoFar + localeElements[i]; - localeSoFar = localeElements[i] + "_"; - // i | request - // 0 | "(locale is NULL)" - // 1 | "(locale is NULL) or (locale=?)" - // 2 | "(locale is NULL) or (locale=?) or (locale=?)" - request.append(" or (locale=?)"); - } - // At the end, localeElements = ["en", "en_US", "en_US_POSIX"]; localeSoFar = en_US_POSIX_" - // and request = "(locale is NULL) or (locale=?) or (locale=?) or (locale=?)" - - final String[] requestArguments; - // If length == 3, we already have all the arguments we need (common prefix is meaningless - // inside variants - if (mAlsoUseMoreRestrictiveLocales && length < 3) { - request.append(" or (locale like ?)"); - // The following creates an array with one more (null) position - final String[] localeElementsWithMoreRestrictiveLocalesIncluded = - Arrays.copyOf(localeElements, length + 1); - localeElementsWithMoreRestrictiveLocalesIncluded[length] = - localeElements[length - 1] + "_%"; - requestArguments = localeElementsWithMoreRestrictiveLocalesIncluded; - // If for example localeElements = ["en"] - // then requestArguments = ["en", "en_%"] - // and request = (locale is NULL) or (locale=?) or (locale like ?) - // If localeElements = ["en", "en_US"] - // then requestArguments = ["en", "en_US", "en_US_%"] - } else { - requestArguments = localeElements; - } - final String requestString = request.toString(); - addWordsFromProjectionLocked(PROJECTION_QUERY, requestString, requestArguments); - } - - private void addWordsFromProjectionLocked(final String[] query, String request, - final String[] requestArguments) - throws IllegalArgumentException { - Cursor cursor = null; - try { - cursor = mContext.getContentResolver().query( - Words.CONTENT_URI, query, request, requestArguments, null); - addWordsLocked(cursor); - } catch (final SQLiteException e) { - Log.e(TAG, "SQLiteException in the remote User dictionary process.", e); - } finally { - try { - if (null != cursor) cursor.close(); - } catch (final SQLiteException e) { - Log.e(TAG, "SQLiteException in the remote User dictionary process.", e); - } - } - } - - private static int scaleFrequencyFromDefaultToLatinIme(final int defaultFrequency) { - // The default frequency for the user dictionary is 250 for historical reasons. - // Latin IME considers a good value for the default user dictionary frequency - // is about 160 considering the scale we use. So we are scaling down the values. - if (defaultFrequency > Integer.MAX_VALUE / LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY) { - return (defaultFrequency / HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY) - * LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY; - } - return (defaultFrequency * LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY) - / HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY; - } - - private void addWordsLocked(final Cursor cursor) { - if (cursor == null) return; - if (cursor.moveToFirst()) { - final int indexWord = cursor.getColumnIndex(Words.WORD); - final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY); - while (!cursor.isAfterLast()) { - final String word = cursor.getString(indexWord); - final int frequency = cursor.getInt(indexFrequency); - final int adjustedFrequency = scaleFrequencyFromDefaultToLatinIme(frequency); - // Safeguard against adding really long words. - if (word.length() <= MAX_WORD_LENGTH) { - runGCIfRequiredLocked(true /* mindsBlockByGC */); - addUnigramLocked(word, adjustedFrequency, false /* isNotAWord */, - false /* isPossiblyOffensive */, - BinaryDictionary.NOT_A_VALID_TIMESTAMP); - } - cursor.moveToNext(); - } - } - } -} diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java deleted file mode 100644 index 8803edc88..000000000 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ /dev/null @@ -1,481 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.inputmethod.latin; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.event.CombinerChain; -import com.android.inputmethod.event.Event; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.ComposedData; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.CoordinateUtils; -import com.android.inputmethod.latin.common.InputPointers; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; - -import java.util.ArrayList; -import java.util.Collections; - -import javax.annotation.Nonnull; - -/** - * A place to store the currently composing word with information such as adjacent key codes as well - */ -public final class WordComposer { - private static final int MAX_WORD_LENGTH = DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH; - private static final boolean DBG = DebugFlags.DEBUG_ENABLED; - - public static final int CAPS_MODE_OFF = 0; - // 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits - // aren't used anywhere in the code - public static final int CAPS_MODE_MANUAL_SHIFTED = 0x1; - public static final int CAPS_MODE_MANUAL_SHIFT_LOCKED = 0x3; - public static final int CAPS_MODE_AUTO_SHIFTED = 0x5; - public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7; - - private CombinerChain mCombinerChain; - private String mCombiningSpec; // Memory so that we don't uselessly recreate the combiner chain - - // The list of events that served to compose this string. - private final ArrayList<Event> mEvents; - private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH); - private SuggestedWordInfo mAutoCorrection; - private boolean mIsResumed; - private boolean mIsBatchMode; - // A memory of the last rejected batch mode suggestion, if any. This goes like this: the user - // gestures a word, is displeased with the results and hits backspace, then gestures again. - // At the very least we should avoid re-suggesting the same thing, and to do that we memorize - // the rejected suggestion in this variable. - // TODO: this should be done in a comprehensive way by the User History feature instead of - // as an ad-hockery here. - private String mRejectedBatchModeSuggestion; - - // Cache these values for performance - private CharSequence mTypedWordCache; - private int mCapsCount; - private int mDigitsCount; - private int mCapitalizedMode; - // This is the number of code points entered so far. This is not limited to MAX_WORD_LENGTH. - // In general, this contains the size of mPrimaryKeyCodes, except when this is greater than - // MAX_WORD_LENGTH in which case mPrimaryKeyCodes only contain the first MAX_WORD_LENGTH - // code points. - private int mCodePointSize; - private int mCursorPositionWithinWord; - - /** - * Whether the composing word has the only first char capitalized. - */ - private boolean mIsOnlyFirstCharCapitalized; - - public WordComposer() { - mCombinerChain = new CombinerChain(""); - mEvents = new ArrayList<>(); - mAutoCorrection = null; - mIsResumed = false; - mIsBatchMode = false; - mCursorPositionWithinWord = 0; - mRejectedBatchModeSuggestion = null; - refreshTypedWordCache(); - } - - public ComposedData getComposedDataSnapshot() { - return new ComposedData(getInputPointers(), isBatchMode(), mTypedWordCache.toString()); - } - - /** - * Restart the combiners, possibly with a new spec. - * @param combiningSpec The spec string for combining. This is found in the extra value. - */ - public void restartCombining(final String combiningSpec) { - final String nonNullCombiningSpec = null == combiningSpec ? "" : combiningSpec; - if (!nonNullCombiningSpec.equals(mCombiningSpec)) { - mCombinerChain = new CombinerChain( - mCombinerChain.getComposingWordWithCombiningFeedback().toString()); - mCombiningSpec = nonNullCombiningSpec; - } - } - - /** - * Clear out the keys registered so far. - */ - public void reset() { - mCombinerChain.reset(); - mEvents.clear(); - mAutoCorrection = null; - mCapsCount = 0; - mDigitsCount = 0; - mIsOnlyFirstCharCapitalized = false; - mIsResumed = false; - mIsBatchMode = false; - mCursorPositionWithinWord = 0; - mRejectedBatchModeSuggestion = null; - refreshTypedWordCache(); - } - - private final void refreshTypedWordCache() { - mTypedWordCache = mCombinerChain.getComposingWordWithCombiningFeedback(); - mCodePointSize = Character.codePointCount(mTypedWordCache, 0, mTypedWordCache.length()); - } - - /** - * Number of keystrokes in the composing word. - * @return the number of keystrokes - */ - public int size() { - return mCodePointSize; - } - - public boolean isSingleLetter() { - return size() == 1; - } - - public final boolean isComposingWord() { - return size() > 0; - } - - public InputPointers getInputPointers() { - return mInputPointers; - } - - /** - * Process an event and return an event, and return a processed event to apply. - * @param event the unprocessed event. - * @return the processed event. Never null, but may be marked as consumed. - */ - @Nonnull - public Event processEvent(@Nonnull final Event event) { - final Event processedEvent = mCombinerChain.processEvent(mEvents, event); - // The retained state of the combiner chain may have changed while processing the event, - // so we need to update our cache. - refreshTypedWordCache(); - mEvents.add(event); - return processedEvent; - } - - /** - * Apply a processed input event. - * - * All input events should be supported, including software/hardware events, characters as well - * as deletions, multiple inputs and gestures. - * - * @param event the event to apply. Must not be null. - */ - public void applyProcessedEvent(final Event event) { - mCombinerChain.applyProcessedEvent(event); - final int primaryCode = event.mCodePoint; - final int keyX = event.mX; - final int keyY = event.mY; - final int newIndex = size(); - refreshTypedWordCache(); - mCursorPositionWithinWord = mCodePointSize; - // We may have deleted the last one. - if (0 == mCodePointSize) { - mIsOnlyFirstCharCapitalized = false; - } - if (Constants.CODE_DELETE != event.mKeyCode) { - if (newIndex < MAX_WORD_LENGTH) { - // In the batch input mode, the {@code mInputPointers} holds batch input points and - // shouldn't be overridden by the "typed key" coordinates - // (See {@link #setBatchInputWord}). - if (!mIsBatchMode) { - // TODO: Set correct pointer id and time - mInputPointers.addPointerAt(newIndex, keyX, keyY, 0, 0); - } - } - if (0 == newIndex) { - mIsOnlyFirstCharCapitalized = Character.isUpperCase(primaryCode); - } else { - mIsOnlyFirstCharCapitalized = mIsOnlyFirstCharCapitalized - && !Character.isUpperCase(primaryCode); - } - if (Character.isUpperCase(primaryCode)) mCapsCount++; - if (Character.isDigit(primaryCode)) mDigitsCount++; - } - mAutoCorrection = null; - } - - public void setCursorPositionWithinWord(final int posWithinWord) { - mCursorPositionWithinWord = posWithinWord; - // TODO: compute where that puts us inside the events - } - - public boolean isCursorFrontOrMiddleOfComposingWord() { - if (DBG && mCursorPositionWithinWord > mCodePointSize) { - throw new RuntimeException("Wrong cursor position : " + mCursorPositionWithinWord - + "in a word of size " + mCodePointSize); - } - return mCursorPositionWithinWord != mCodePointSize; - } - - /** - * When the cursor is moved by the user, we need to update its position. - * If it falls inside the currently composing word, we don't reset the composition, and - * only update the cursor position. - * - * @param expectedMoveAmount How many java chars to move the cursor. Negative values move - * the cursor backward, positive values move the cursor forward. - * @return true if the cursor is still inside the composing word, false otherwise. - */ - public boolean moveCursorByAndReturnIfInsideComposingWord(final int expectedMoveAmount) { - int actualMoveAmount = 0; - int cursorPos = mCursorPositionWithinWord; - // TODO: Don't make that copy. We can do this directly from mTypedWordCache. - final int[] codePoints = StringUtils.toCodePointArray(mTypedWordCache); - if (expectedMoveAmount >= 0) { - // Moving the cursor forward for the expected amount or until the end of the word has - // been reached, whichever comes first. - while (actualMoveAmount < expectedMoveAmount && cursorPos < codePoints.length) { - actualMoveAmount += Character.charCount(codePoints[cursorPos]); - ++cursorPos; - } - } else { - // Moving the cursor backward for the expected amount or until the start of the word - // has been reached, whichever comes first. - while (actualMoveAmount > expectedMoveAmount && cursorPos > 0) { - --cursorPos; - actualMoveAmount -= Character.charCount(codePoints[cursorPos]); - } - } - // If the actual and expected amounts differ, we crossed the start or the end of the word - // so the result would not be inside the composing word. - if (actualMoveAmount != expectedMoveAmount) { - return false; - } - mCursorPositionWithinWord = cursorPos; - mCombinerChain.applyProcessedEvent(mCombinerChain.processEvent( - mEvents, Event.createCursorMovedEvent(cursorPos))); - return true; - } - - public void setBatchInputPointers(final InputPointers batchPointers) { - mInputPointers.set(batchPointers); - mIsBatchMode = true; - } - - public void setBatchInputWord(final String word) { - reset(); - mIsBatchMode = true; - final int length = word.length(); - for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) { - final int codePoint = Character.codePointAt(word, i); - // We don't want to override the batch input points that are held in mInputPointers - // (See {@link #add(int,int,int)}). - final Event processedEvent = - processEvent(Event.createEventForCodePointFromUnknownSource(codePoint)); - applyProcessedEvent(processedEvent); - } - } - - /** - * Set the currently composing word to the one passed as an argument. - * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity. - * @param codePoints the code points to set as the composing word. - * @param coordinates the x, y coordinates of the key in the CoordinateUtils format - */ - public void setComposingWord(final int[] codePoints, final int[] coordinates) { - reset(); - final int length = codePoints.length; - for (int i = 0; i < length; ++i) { - final Event processedEvent = - processEvent(Event.createEventForCodePointFromAlreadyTypedText(codePoints[i], - CoordinateUtils.xFromArray(coordinates, i), - CoordinateUtils.yFromArray(coordinates, i))); - applyProcessedEvent(processedEvent); - } - mIsResumed = true; - } - - /** - * Returns the word as it was typed, without any correction applied. - * @return the word that was typed so far. Never returns null. - */ - public String getTypedWord() { - return mTypedWordCache.toString(); - } - - /** - * Whether this composer is composing or about to compose a word in which only the first letter - * is a capital. - * - * If we do have a composing word, we just return whether the word has indeed only its first - * character capitalized. If we don't, then we return a value based on the capitalized mode, - * which tell us what is likely to happen for the next composing word. - * - * @return capitalization preference - */ - public boolean isOrWillBeOnlyFirstCharCapitalized() { - return isComposingWord() ? mIsOnlyFirstCharCapitalized - : (CAPS_MODE_OFF != mCapitalizedMode); - } - - /** - * Whether or not all of the user typed chars are upper case - * @return true if all user typed chars are upper case, false otherwise - */ - public boolean isAllUpperCase() { - if (size() <= 1) { - return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED - || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFT_LOCKED; - } - return mCapsCount == size(); - } - - public boolean wasShiftedNoLock() { - return mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED - || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFTED; - } - - /** - * Returns true if more than one character is upper case, otherwise returns false. - */ - public boolean isMostlyCaps() { - return mCapsCount > 1; - } - - /** - * Returns true if we have digits in the composing word. - */ - public boolean hasDigits() { - return mDigitsCount > 0; - } - - /** - * Saves the caps mode at the start of composing. - * - * WordComposer needs to know about the caps mode for several reasons. The first is, we need - * to know after the fact what the reason was, to register the correct form into the user - * history dictionary: if the word was automatically capitalized, we should insert it in - * all-lower case but if it's a manual pressing of shift, then it should be inserted as is. - * Also, batch input needs to know about the current caps mode to display correctly - * capitalized suggestions. - * @param mode the mode at the time of start - */ - public void setCapitalizedModeAtStartComposingTime(final int mode) { - mCapitalizedMode = mode; - } - - /** - * Before fetching suggestions, we don't necessarily know about the capitalized mode yet. - * - * If we don't have a composing word yet, we take a note of this mode so that we can then - * supply this information to the suggestion process. If we have a composing word, then - * the previous mode has priority over this. - * @param mode the mode just before fetching suggestions - */ - public void adviseCapitalizedModeBeforeFetchingSuggestions(final int mode) { - if (!isComposingWord()) { - mCapitalizedMode = mode; - } - } - - /** - * Returns whether the word was automatically capitalized. - * @return whether the word was automatically capitalized - */ - public boolean wasAutoCapitalized() { - return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED - || mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED; - } - - /** - * Sets the auto-correction for this word. - */ - public void setAutoCorrection(final SuggestedWordInfo autoCorrection) { - mAutoCorrection = autoCorrection; - } - - /** - * @return the auto-correction for this word, or null if none. - */ - public SuggestedWordInfo getAutoCorrectionOrNull() { - return mAutoCorrection; - } - - /** - * @return whether we started composing this word by resuming suggestion on an existing string - */ - public boolean isResumed() { - return mIsResumed; - } - - // `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above. - // committedWord should contain suggestion spans if applicable. - public LastComposedWord commitWord(final int type, final CharSequence committedWord, - final String separatorString, final NgramContext ngramContext) { - // Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK - // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate - // the last composed word to ensure this does not happen. - final LastComposedWord lastComposedWord = new LastComposedWord(mEvents, - mInputPointers, mTypedWordCache.toString(), committedWord, separatorString, - ngramContext, mCapitalizedMode); - mInputPointers.reset(); - if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD - && type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) { - lastComposedWord.deactivate(); - } - mCapsCount = 0; - mDigitsCount = 0; - mIsBatchMode = false; - mCombinerChain.reset(); - mEvents.clear(); - mCodePointSize = 0; - mIsOnlyFirstCharCapitalized = false; - mCapitalizedMode = CAPS_MODE_OFF; - refreshTypedWordCache(); - mAutoCorrection = null; - mCursorPositionWithinWord = 0; - mIsResumed = false; - mRejectedBatchModeSuggestion = null; - return lastComposedWord; - } - - public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) { - mEvents.clear(); - Collections.copy(mEvents, lastComposedWord.mEvents); - mInputPointers.set(lastComposedWord.mInputPointers); - mCombinerChain.reset(); - refreshTypedWordCache(); - mCapitalizedMode = lastComposedWord.mCapitalizedMode; - mAutoCorrection = null; // This will be filled by the next call to updateSuggestion. - mCursorPositionWithinWord = mCodePointSize; - mRejectedBatchModeSuggestion = null; - mIsResumed = true; - } - - public boolean isBatchMode() { - return mIsBatchMode; - } - - public void setRejectedBatchModeSuggestion(final String rejectedSuggestion) { - mRejectedBatchModeSuggestion = rejectedSuggestion; - } - - public String getRejectedBatchModeSuggestion() { - return mRejectedBatchModeSuggestion; - } - - @UsedForTesting - void addInputPointerForTest(int index, int keyX, int keyY) { - mInputPointers.addPointerAt(index, keyX, keyY, 0, 0); - } - - @UsedForTesting - void setTypedWordCacheForTests(String typedWordCacheForTests) { - mTypedWordCache = typedWordCacheForTests; - } -} diff --git a/java/src/com/android/inputmethod/latin/WordListInfo.java b/java/src/com/android/inputmethod/latin/WordListInfo.java deleted file mode 100644 index 268fe9818..000000000 --- a/java/src/com/android/inputmethod/latin/WordListInfo.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin; - -/** - * Information container for a word list. - */ -public final class WordListInfo { - public final String mId; - public final String mLocale; - public final String mRawChecksum; - public WordListInfo(final String id, final String locale, final String rawChecksum) { - mId = id; - mLocale = locale; - mRawChecksum = rawChecksum; - } -} diff --git a/java/src/com/android/inputmethod/latin/about/AboutPreferences.java b/java/src/com/android/inputmethod/latin/about/AboutPreferences.java deleted file mode 100644 index ec7e6def5..000000000 --- a/java/src/com/android/inputmethod/latin/about/AboutPreferences.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.about; - -import android.app.Fragment; - -/** - * Placeholer class of AboutPreferences. Never use this. - */ -public final class AboutPreferences extends Fragment { - private AboutPreferences() { - // Prevents this from being instantiated - } -} diff --git a/java/src/com/android/inputmethod/latin/accounts/AccountStateChangedListener.java b/java/src/com/android/inputmethod/latin/accounts/AccountStateChangedListener.java deleted file mode 100644 index 3c39c9a16..000000000 --- a/java/src/com/android/inputmethod/latin/accounts/AccountStateChangedListener.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.accounts; - -import androidx.annotation.NonNull; - -import javax.annotation.Nullable; - -/** - * Handles changes to account used to sign in to the keyboard. - * e.g. account switching/sign-in/sign-out from the keyboard - * user toggling the sync preference. - */ -public class AccountStateChangedListener { - - /** - * Called when the current account being used in keyboard is signed out. - * - * @param oldAccount the account that was signed out of. - */ - public static void onAccountSignedOut(@NonNull String oldAccount) { - } - - /** - * Called when the user signs-in to the keyboard. - * This may be called when the user switches accounts to sign in with a different account. - * - * @param oldAccount the previous account that was being used for sign-in. - * May be null for a fresh sign-in. - * @param newAccount the account being used for sign-in. - */ - public static void onAccountSignedIn(@Nullable String oldAccount, @NonNull String newAccount) { - } - - /** - * Called when the user toggles the sync preference. - * - * @param account the account being used for sync. - * @param syncEnabled indicates whether sync has been enabled or not. - */ - public static void onSyncPreferenceChanged(@Nullable String account, boolean syncEnabled) { - } - - /** - * Forces an immediate sync to happen. - * This should only be used for debugging purposes. - * - * @param account the account to use for sync. - */ - public static void forceSync(@Nullable String account) { - } - - /** - * Forces an immediate deletion of user's data. - * This should only be used for debugging purposes. - * - * @param account the account to use for sync. - */ - public static void forceDelete(@Nullable String account) { - } -} diff --git a/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java b/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java deleted file mode 100644 index 00bcecf52..000000000 --- a/java/src/com/android/inputmethod/latin/accounts/AccountsChangedReceiver.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.accounts; - -import android.accounts.AccountManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.settings.LocalSettingsConstants; - -/** - * {@link BroadcastReceiver} for {@link AccountManager#LOGIN_ACCOUNTS_CHANGED_ACTION}. - */ -public class AccountsChangedReceiver extends BroadcastReceiver { - static final String TAG = "AccountsChangedReceiver"; - - @Override - public void onReceive(Context context, Intent intent) { - if (!AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(intent.getAction())) { - Log.w(TAG, "Received unknown broadcast: " + intent); - return; - } - - // Ideally the account preference could live in a different preferences file - // that wasn't being backed up and restored, however the preference fragments - // currently only deal with the default shared preferences which is why - // separating this out into a different file is not trivial currently. - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - final String currentAccount = prefs.getString( - LocalSettingsConstants.PREF_ACCOUNT_NAME, null); - removeUnknownAccountFromPreference(prefs, getAccountsForLogin(context), currentAccount); - } - - /** - * Helper method to help test this receiver. - */ - @UsedForTesting - protected String[] getAccountsForLogin(Context context) { - return LoginAccountUtils.getAccountsForLogin(context); - } - - /** - * Removes the currentAccount from preferences if it's not found - * in the list of current accounts. - */ - private static void removeUnknownAccountFromPreference(final SharedPreferences prefs, - final String[] accounts, final String currentAccount) { - if (currentAccount == null) { - return; - } - for (final String account : accounts) { - if (TextUtils.equals(currentAccount, account)) { - return; - } - } - Log.i(TAG, "The current account was removed from the system: " + currentAccount); - prefs.edit() - .remove(LocalSettingsConstants.PREF_ACCOUNT_NAME) - .apply(); - } -} diff --git a/java/src/com/android/inputmethod/latin/accounts/AuthUtils.java b/java/src/com/android/inputmethod/latin/accounts/AuthUtils.java deleted file mode 100644 index 31aba3631..000000000 --- a/java/src/com/android/inputmethod/latin/accounts/AuthUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.accounts; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountManagerCallback; -import android.accounts.AccountManagerFuture; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; - -import java.io.IOException; - -/** - * Utility class that handles generation/invalidation of auth tokens in the app. - */ -public class AuthUtils { - private final AccountManager mAccountManager; - - public AuthUtils(Context context) { - mAccountManager = AccountManager.get(context); - } - - /** - * @see AccountManager#invalidateAuthToken(String, String) - */ - public void invalidateAuthToken(final String accountType, final String authToken) { - mAccountManager.invalidateAuthToken(accountType, authToken); - } - - /** - * @see AccountManager#getAuthToken( - * Account, String, Bundle, boolean, AccountManagerCallback, Handler) - */ - public AccountManagerFuture<Bundle> getAuthToken(final Account account, - final String authTokenType, final Bundle options, final boolean notifyAuthFailure, - final AccountManagerCallback<Bundle> callback, final Handler handler) { - return mAccountManager.getAuthToken(account, authTokenType, options, notifyAuthFailure, - callback, handler); - } - - /** - * @see AccountManager#blockingGetAuthToken(Account, String, boolean) - */ - public String blockingGetAuthToken(final Account account, final String authTokenType, - final boolean notifyAuthFailure) throws OperationCanceledException, - AuthenticatorException, IOException { - return mAccountManager.blockingGetAuthToken(account, authTokenType, notifyAuthFailure); - } -} diff --git a/java/src/com/android/inputmethod/latin/accounts/LoginAccountUtils.java b/java/src/com/android/inputmethod/latin/accounts/LoginAccountUtils.java deleted file mode 100644 index dcc64a223..000000000 --- a/java/src/com/android/inputmethod/latin/accounts/LoginAccountUtils.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.accounts; - -import android.content.Context; - -import javax.annotation.Nonnull; - -/** - * Utility class for retrieving accounts that may be used for login. - */ -public class LoginAccountUtils { - /** - * This defines the type of account this class deals with. - * This account type is used when listing the accounts available on the device for login. - */ - public static final String ACCOUNT_TYPE = ""; - - private LoginAccountUtils() { - // This utility class is not publicly instantiable. - } - - /** - * Get the accounts available for login. - * - * @return an array of accounts. Empty (never null) if no accounts are available for login. - */ - @Nonnull - @SuppressWarnings("unused") - public static String[] getAccountsForLogin(final Context context) { - return new String[0]; - } -} diff --git a/java/src/com/android/inputmethod/latin/define/DebugFlags.java b/java/src/com/android/inputmethod/latin/define/DebugFlags.java deleted file mode 100644 index c509e8322..000000000 --- a/java/src/com/android/inputmethod/latin/define/DebugFlags.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.define; - -import android.content.SharedPreferences; - -public final class DebugFlags { - public static final boolean DEBUG_ENABLED = false; - - private DebugFlags() { - // This class is not publicly instantiable. - } - - @SuppressWarnings("unused") - public static void init(final SharedPreferences prefs) { - } -} diff --git a/java/src/com/android/inputmethod/latin/define/DecoderSpecificConstants.java b/java/src/com/android/inputmethod/latin/define/DecoderSpecificConstants.java deleted file mode 100644 index 7f57ce858..000000000 --- a/java/src/com/android/inputmethod/latin/define/DecoderSpecificConstants.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.inputmethod.latin.define; - -/** - * Decoder specific constants for LatinIme. - */ -public class DecoderSpecificConstants { - - // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h - public static final int DICTIONARY_MAX_WORD_LENGTH = 48; - - // (MAX_PREV_WORD_COUNT_FOR_N_GRAM + 1)-gram is supported in Java side. Needs to modify - // MAX_PREV_WORD_COUNT_FOR_N_GRAM in native/jni/src/defines.h for suggestions. - public static final int MAX_PREV_WORD_COUNT_FOR_N_GRAM = 3; - - public static final String DECODER_DICT_SUFFIX = ""; - - public static final boolean SHOULD_VERIFY_MAGIC_NUMBER = true; - public static final boolean SHOULD_VERIFY_CHECKSUM = true; - public static final boolean SHOULD_USE_DICT_VERSION = true; - public static final boolean SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION = false; - public static final boolean SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION = true; -} diff --git a/java/src/com/android/inputmethod/latin/define/JniLibName.java b/java/src/com/android/inputmethod/latin/define/JniLibName.java deleted file mode 100644 index abfc36d39..000000000 --- a/java/src/com/android/inputmethod/latin/define/JniLibName.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.define; - -public final class JniLibName { - private JniLibName() { - // This class is not publicly instantiable. - } - - public static final String JNI_LIB_NAME = "jni_latinime"; -} diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlags.java b/java/src/com/android/inputmethod/latin/define/ProductionFlags.java deleted file mode 100644 index f31c20822..000000000 --- a/java/src/com/android/inputmethod/latin/define/ProductionFlags.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.define; - -public final class ProductionFlags { - private ProductionFlags() { - // This class is not publicly instantiable. - } - - public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false; - - /** - * Include all suggestions from all dictionaries in - * {@link com.android.inputmethod.latin.SuggestedWords#mRawSuggestions}. - */ - public static final boolean INCLUDE_RAW_SUGGESTIONS = false; - - /** - * When false, the metrics logging is not yet ready to be enabled. - */ - public static final boolean IS_METRICS_LOGGING_SUPPORTED = false; - - /** - * When {@code false}, the split keyboard is not yet ready to be enabled. - */ - public static final boolean IS_SPLIT_KEYBOARD_SUPPORTED = true; - - /** - * When {@code false}, account sign-in in keyboard is not yet ready to be enabled. - */ - public static final boolean ENABLE_ACCOUNT_SIGN_IN = false; - - /** - * When {@code true}, user history dictionary sync feature is ready to be enabled. - */ - public static final boolean ENABLE_USER_HISTORY_DICTIONARY_SYNC = - ENABLE_ACCOUNT_SIGN_IN && false; - - /** - * When {@code true}, the IME maintains per account {@link UserHistoryDictionary}. - */ - public static final boolean ENABLE_PER_ACCOUNT_USER_HISTORY_DICTIONARY = - ENABLE_ACCOUNT_SIGN_IN && false; -} diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java deleted file mode 100644 index a7804a13f..000000000 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ /dev/null @@ -1,2353 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.inputlogic; - -import android.graphics.Color; -import android.os.SystemClock; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.BackgroundColorSpan; -import android.text.style.SuggestionSpan; -import android.util.Log; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.inputmethod.CorrectionInfo; -import android.view.inputmethod.EditorInfo; - -import com.android.inputmethod.compat.SuggestionSpanUtils; -import com.android.inputmethod.event.Event; -import com.android.inputmethod.event.InputTransaction; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardSwitcher; -import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.DictionaryFacilitator; -import com.android.inputmethod.latin.LastComposedWord; -import com.android.inputmethod.latin.LatinIME; -import com.android.inputmethod.latin.NgramContext; -import com.android.inputmethod.latin.RichInputConnection; -import com.android.inputmethod.latin.Suggest; -import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.WordComposer; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.InputPointers; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.settings.SettingsValues; -import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; -import com.android.inputmethod.latin.settings.SpacingAndPunctuations; -import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor; -import com.android.inputmethod.latin.utils.AsyncResultHolder; -import com.android.inputmethod.latin.utils.InputTypeUtils; -import com.android.inputmethod.latin.utils.RecapitalizeStatus; -import com.android.inputmethod.latin.utils.StatsUtils; -import com.android.inputmethod.latin.utils.TextRange; - -import java.util.ArrayList; -import java.util.Locale; -import java.util.TreeSet; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; - -/** - * This class manages the input logic. - */ -public final class InputLogic { - private static final String TAG = InputLogic.class.getSimpleName(); - - // TODO : Remove this member when we can. - final LatinIME mLatinIME; - private final SuggestionStripViewAccessor mSuggestionStripViewAccessor; - - // Never null. - private InputLogicHandler mInputLogicHandler = InputLogicHandler.NULL_HANDLER; - - // TODO : make all these fields private as soon as possible. - // Current space state of the input method. This can be any of the above constants. - private int mSpaceState; - // Never null - public SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance(); - public final Suggest mSuggest; - private final DictionaryFacilitator mDictionaryFacilitator; - - public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; - // This has package visibility so it can be accessed from InputLogicHandler. - /* package */ final WordComposer mWordComposer; - public final RichInputConnection mConnection; - private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus(); - - private int mDeleteCount; - private long mLastKeyTime; - public final TreeSet<Long> mCurrentlyPressedHardwareKeys = new TreeSet<>(); - - // Keeps track of most recently inserted text (multi-character key) for reverting - private String mEnteredText; - - // TODO: This boolean is persistent state and causes large side effects at unexpected times. - // Find a way to remove it for readability. - private boolean mIsAutoCorrectionIndicatorOn; - private long mDoubleSpacePeriodCountdownStart; - - // The word being corrected while the cursor is in the middle of the word. - // Note: This does not have a composing span, so it must be handled separately. - private String mWordBeingCorrectedByCursor = null; - - /** - * Create a new instance of the input logic. - * @param latinIME the instance of the parent LatinIME. We should remove this when we can. - * @param suggestionStripViewAccessor an object to access the suggestion strip view. - * @param dictionaryFacilitator facilitator for getting suggestions and updating user history - * dictionary. - */ - public InputLogic(final LatinIME latinIME, - final SuggestionStripViewAccessor suggestionStripViewAccessor, - final DictionaryFacilitator dictionaryFacilitator) { - mLatinIME = latinIME; - mSuggestionStripViewAccessor = suggestionStripViewAccessor; - mWordComposer = new WordComposer(); - mConnection = new RichInputConnection(latinIME); - mInputLogicHandler = InputLogicHandler.NULL_HANDLER; - mSuggest = new Suggest(dictionaryFacilitator); - mDictionaryFacilitator = dictionaryFacilitator; - } - - /** - * Initializes the input logic for input in an editor. - * - * Call this when input starts or restarts in some editor (typically, in onStartInputView). - * - * @param combiningSpec the combining spec string for this subtype - * @param settingsValues the current settings values - */ - public void startInput(final String combiningSpec, final SettingsValues settingsValues) { - mEnteredText = null; - mWordBeingCorrectedByCursor = null; - mConnection.onStartInput(); - if (!mWordComposer.getTypedWord().isEmpty()) { - // For messaging apps that offer send button, the IME does not get the opportunity - // to capture the last word. This block should capture those uncommitted words. - // The timestamp at which it is captured is not accurate but close enough. - StatsUtils.onWordCommitUserTyped( - mWordComposer.getTypedWord(), mWordComposer.isBatchMode()); - } - mWordComposer.restartCombining(combiningSpec); - resetComposingState(true /* alsoResetLastComposedWord */); - mDeleteCount = 0; - mSpaceState = SpaceState.NONE; - mRecapitalizeStatus.disable(); // Do not perform recapitalize until the cursor is moved once - mCurrentlyPressedHardwareKeys.clear(); - mSuggestedWords = SuggestedWords.getEmptyInstance(); - // In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying - // so we try using some heuristics to find out about these and fix them. - mConnection.tryFixLyingCursorPosition(); - cancelDoubleSpacePeriodCountdown(); - if (InputLogicHandler.NULL_HANDLER == mInputLogicHandler) { - mInputLogicHandler = new InputLogicHandler(mLatinIME, this); - } else { - mInputLogicHandler.reset(); - } - - if (settingsValues.mShouldShowLxxSuggestionUi) { - mConnection.requestCursorUpdates(true /* enableMonitor */, - true /* requestImmediateCallback */); - } - } - - /** - * Call this when the subtype changes. - * @param combiningSpec the spec string for the combining rules - * @param settingsValues the current settings values - */ - public void onSubtypeChanged(final String combiningSpec, final SettingsValues settingsValues) { - finishInput(); - startInput(combiningSpec, settingsValues); - } - - /** - * Call this when the orientation changes. - * @param settingsValues the current values of the settings. - */ - public void onOrientationChange(final SettingsValues settingsValues) { - // If !isComposingWord, #commitTyped() is a no-op, but still, it's better to avoid - // the useless IPC of {begin,end}BatchEdit. - if (mWordComposer.isComposingWord()) { - mConnection.beginBatchEdit(); - // If we had a composition in progress, we need to commit the word so that the - // suggestionsSpan will be added. This will allow resuming on the same suggestions - // after rotation is finished. - commitTyped(settingsValues, LastComposedWord.NOT_A_SEPARATOR); - mConnection.endBatchEdit(); - } - } - - /** - * Clean up the input logic after input is finished. - */ - public void finishInput() { - if (mWordComposer.isComposingWord()) { - mConnection.finishComposingText(); - StatsUtils.onWordCommitUserTyped( - mWordComposer.getTypedWord(), mWordComposer.isBatchMode()); - } - resetComposingState(true /* alsoResetLastComposedWord */); - mInputLogicHandler.reset(); - } - - // Normally this class just gets out of scope after the process ends, but in unit tests, we - // create several instances of LatinIME in the same process, which results in several - // instances of InputLogic. This cleans up the associated handler so that tests don't leak - // handlers. - public void recycle() { - final InputLogicHandler inputLogicHandler = mInputLogicHandler; - mInputLogicHandler = InputLogicHandler.NULL_HANDLER; - inputLogicHandler.destroy(); - mDictionaryFacilitator.closeDictionaries(); - } - - /** - * React to a string input. - * - * This is triggered by keys that input many characters at once, like the ".com" key or - * some additional keys for example. - * - * @param settingsValues the current values of the settings. - * @param event the input event containing the data. - * @return the complete transaction object - */ - public InputTransaction onTextInput(final SettingsValues settingsValues, final Event event, - final int keyboardShiftMode, final LatinIME.UIHandler handler) { - final String rawText = event.getTextToCommit().toString(); - final InputTransaction inputTransaction = new InputTransaction(settingsValues, event, - SystemClock.uptimeMillis(), mSpaceState, - getActualCapsMode(settingsValues, keyboardShiftMode)); - mConnection.beginBatchEdit(); - if (mWordComposer.isComposingWord()) { - commitCurrentAutoCorrection(settingsValues, rawText, handler); - } else { - resetComposingState(true /* alsoResetLastComposedWord */); - } - handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_TYPING); - final String text = performSpecificTldProcessingOnTextInput(rawText); - if (SpaceState.PHANTOM == mSpaceState) { - insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues); - } - mConnection.commitText(text, 1); - StatsUtils.onWordCommitUserTyped(mEnteredText, mWordComposer.isBatchMode()); - mConnection.endBatchEdit(); - // Space state must be updated before calling updateShiftState - mSpaceState = SpaceState.NONE; - mEnteredText = text; - mWordBeingCorrectedByCursor = null; - inputTransaction.setDidAffectContents(); - inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); - return inputTransaction; - } - - /** - * A suggestion was picked from the suggestion strip. - * @param settingsValues the current values of the settings. - * @param suggestionInfo the suggestion info. - * @param keyboardShiftState the shift state of the keyboard, as returned by - * {@link com.android.inputmethod.keyboard.KeyboardSwitcher#getKeyboardShiftMode()} - * @return the complete transaction object - */ - // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener} - // interface - public InputTransaction onPickSuggestionManually(final SettingsValues settingsValues, - final SuggestedWordInfo suggestionInfo, final int keyboardShiftState, - final int currentKeyboardScriptId, final LatinIME.UIHandler handler) { - final SuggestedWords suggestedWords = mSuggestedWords; - final String suggestion = suggestionInfo.mWord; - // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput - if (suggestion.length() == 1 && suggestedWords.isPunctuationSuggestions()) { - // We still want to log a suggestion click. - StatsUtils.onPickSuggestionManually( - mSuggestedWords, suggestionInfo, mDictionaryFacilitator); - // Word separators are suggested before the user inputs something. - // Rely on onCodeInput to do the complicated swapping/stripping logic consistently. - final Event event = Event.createPunctuationSuggestionPickedEvent(suggestionInfo); - return onCodeInput(settingsValues, event, keyboardShiftState, - currentKeyboardScriptId, handler); - } - - final Event event = Event.createSuggestionPickedEvent(suggestionInfo); - final InputTransaction inputTransaction = new InputTransaction(settingsValues, - event, SystemClock.uptimeMillis(), mSpaceState, keyboardShiftState); - // Manual pick affects the contents of the editor, so we take note of this. It's important - // for the sequence of language switching. - inputTransaction.setDidAffectContents(); - mConnection.beginBatchEdit(); - if (SpaceState.PHANTOM == mSpaceState && suggestion.length() > 0 - // In the batch input mode, a manually picked suggested word should just replace - // the current batch input text and there is no need for a phantom space. - && !mWordComposer.isBatchMode()) { - final int firstChar = Character.codePointAt(suggestion, 0); - if (!settingsValues.isWordSeparator(firstChar) - || settingsValues.isUsuallyPrecededBySpace(firstChar)) { - insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues); - } - } - - // TODO: We should not need the following branch. We should be able to take the same - // code path as for other kinds, use commitChosenWord, and do everything normally. We will - // however need to reset the suggestion strip right away, because we know we can't take - // the risk of calling commitCompletion twice because we don't know how the app will react. - if (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_APP_DEFINED)) { - mSuggestedWords = SuggestedWords.getEmptyInstance(); - mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); - inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); - resetComposingState(true /* alsoResetLastComposedWord */); - mConnection.commitCompletion(suggestionInfo.mApplicationSpecifiedCompletionInfo); - mConnection.endBatchEdit(); - return inputTransaction; - } - - commitChosenWord(settingsValues, suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK, - LastComposedWord.NOT_A_SEPARATOR); - mConnection.endBatchEdit(); - // Don't allow cancellation of manual pick - mLastComposedWord.deactivate(); - // Space state must be updated before calling updateShiftState - mSpaceState = SpaceState.PHANTOM; - inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); - - // If we're not showing the "Touch again to save", then update the suggestion strip. - // That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE. - handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE); - - StatsUtils.onPickSuggestionManually( - mSuggestedWords, suggestionInfo, mDictionaryFacilitator); - StatsUtils.onWordCommitSuggestionPickedManually( - suggestionInfo.mWord, mWordComposer.isBatchMode()); - return inputTransaction; - } - - /** - * Consider an update to the cursor position. Evaluate whether this update has happened as - * part of normal typing or whether it was an explicit cursor move by the user. In any case, - * do the necessary adjustments. - * @param oldSelStart old selection start - * @param oldSelEnd old selection end - * @param newSelStart new selection start - * @param newSelEnd new selection end - * @param settingsValues the current values of the settings. - * @return whether the cursor has moved as a result of user interaction. - */ - public boolean onUpdateSelection(final int oldSelStart, final int oldSelEnd, - final int newSelStart, final int newSelEnd, final SettingsValues settingsValues) { - if (mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart, oldSelEnd, newSelEnd)) { - return false; - } - // TODO: the following is probably better done in resetEntireInputState(). - // it should only happen when the cursor moved, and the very purpose of the - // test below is to narrow down whether this happened or not. Likewise with - // the call to updateShiftState. - // We set this to NONE because after a cursor move, we don't want the space - // state-related special processing to kick in. - mSpaceState = SpaceState.NONE; - - final boolean selectionChangedOrSafeToReset = - oldSelStart != newSelStart || oldSelEnd != newSelEnd // selection changed - || !mWordComposer.isComposingWord(); // safe to reset - final boolean hasOrHadSelection = (oldSelStart != oldSelEnd || newSelStart != newSelEnd); - final int moveAmount = newSelStart - oldSelStart; - // As an added small gift from the framework, it happens upon rotation when there - // is a selection that we get a wrong cursor position delivered to startInput() that - // does not get reflected in the oldSel{Start,End} parameters to the next call to - // onUpdateSelection. In this case, we may have set a composition, and when we're here - // we realize we shouldn't have. In theory, in this case, selectionChangedOrSafeToReset - // should be true, but that is if the framework had taken that wrong cursor position - // into account, which means we have to reset the entire composing state whenever there - // is or was a selection regardless of whether it changed or not. - if (hasOrHadSelection || !settingsValues.needsToLookupSuggestions() - || (selectionChangedOrSafeToReset - && !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) { - // If we are composing a word and moving the cursor, we would want to set a - // suggestion span for recorrection to work correctly. Unfortunately, that - // would involve the keyboard committing some new text, which would move the - // cursor back to where it was. Latin IME could then fix the position of the cursor - // again, but the asynchronous nature of the calls results in this wreaking havoc - // with selection on double tap and the like. - // Another option would be to send suggestions each time we set the composing - // text, but that is probably too expensive to do, so we decided to leave things - // as is. - // Also, we're posting a resume suggestions message, and this will update the - // suggestions strip in a few milliseconds, so if we cleared the suggestion strip here - // we'd have the suggestion strip noticeably janky. To avoid that, we don't clear - // it here, which means we'll keep outdated suggestions for a split second but the - // visual result is better. - resetEntireInputState(newSelStart, newSelEnd, false /* clearSuggestionStrip */); - // If the user is in the middle of correcting a word, we should learn it before moving - // the cursor away. - if (!TextUtils.isEmpty(mWordBeingCorrectedByCursor)) { - final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds( - System.currentTimeMillis()); - performAdditionToUserHistoryDictionary(settingsValues, mWordBeingCorrectedByCursor, - NgramContext.EMPTY_PREV_WORDS_INFO); - } - } else { - // resetEntireInputState calls resetCachesUponCursorMove, but forcing the - // composition to end. But in all cases where we don't reset the entire input - // state, we still want to tell the rich input connection about the new cursor - // position so that it can update its caches. - mConnection.resetCachesUponCursorMoveAndReturnSuccess( - newSelStart, newSelEnd, false /* shouldFinishComposition */); - } - - // The cursor has been moved : we now accept to perform recapitalization - mRecapitalizeStatus.enable(); - // We moved the cursor. If we are touching a word, we need to resume suggestion. - mLatinIME.mHandler.postResumeSuggestions(true /* shouldDelay */); - // Stop the last recapitalization, if started. - mRecapitalizeStatus.stop(); - mWordBeingCorrectedByCursor = null; - return true; - } - - /** - * React to a code input. It may be a code point to insert, or a symbolic value that influences - * the keyboard behavior. - * - * Typically, this is called whenever a key is pressed on the software keyboard. This is not - * the entry point for gesture input; see the onBatchInput* family of functions for this. - * - * @param settingsValues the current settings values. - * @param event the event to handle. - * @param keyboardShiftMode the current shift mode of the keyboard, as returned by - * {@link com.android.inputmethod.keyboard.KeyboardSwitcher#getKeyboardShiftMode()} - * @return the complete transaction object - */ - public InputTransaction onCodeInput(final SettingsValues settingsValues, - @Nonnull final Event event, final int keyboardShiftMode, - final int currentKeyboardScriptId, final LatinIME.UIHandler handler) { - mWordBeingCorrectedByCursor = null; - final Event processedEvent = mWordComposer.processEvent(event); - final InputTransaction inputTransaction = new InputTransaction(settingsValues, - processedEvent, SystemClock.uptimeMillis(), mSpaceState, - getActualCapsMode(settingsValues, keyboardShiftMode)); - if (processedEvent.mKeyCode != Constants.CODE_DELETE - || inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) { - mDeleteCount = 0; - } - mLastKeyTime = inputTransaction.mTimestamp; - mConnection.beginBatchEdit(); - if (!mWordComposer.isComposingWord()) { - // TODO: is this useful? It doesn't look like it should be done here, but rather after - // a word is committed. - mIsAutoCorrectionIndicatorOn = false; - } - - // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state. - if (processedEvent.mCodePoint != Constants.CODE_SPACE) { - cancelDoubleSpacePeriodCountdown(); - } - - Event currentEvent = processedEvent; - while (null != currentEvent) { - if (currentEvent.isConsumed()) { - handleConsumedEvent(currentEvent, inputTransaction); - } else if (currentEvent.isFunctionalKeyEvent()) { - handleFunctionalEvent(currentEvent, inputTransaction, currentKeyboardScriptId, - handler); - } else { - handleNonFunctionalEvent(currentEvent, inputTransaction, handler); - } - currentEvent = currentEvent.mNextEvent; - } - // Try to record the word being corrected when the user enters a word character or - // the backspace key. - if (!mConnection.hasSlowInputConnection() && !mWordComposer.isComposingWord() - && (settingsValues.isWordCodePoint(processedEvent.mCodePoint) || - processedEvent.mKeyCode == Constants.CODE_DELETE)) { - mWordBeingCorrectedByCursor = getWordAtCursor( - settingsValues, currentKeyboardScriptId); - } - if (!inputTransaction.didAutoCorrect() && processedEvent.mKeyCode != Constants.CODE_SHIFT - && processedEvent.mKeyCode != Constants.CODE_CAPSLOCK - && processedEvent.mKeyCode != Constants.CODE_SWITCH_ALPHA_SYMBOL) - mLastComposedWord.deactivate(); - if (Constants.CODE_DELETE != processedEvent.mKeyCode) { - mEnteredText = null; - } - mConnection.endBatchEdit(); - return inputTransaction; - } - - public void onStartBatchInput(final SettingsValues settingsValues, - final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) { - mWordBeingCorrectedByCursor = null; - mInputLogicHandler.onStartBatchInput(); - handler.showGesturePreviewAndSuggestionStrip( - SuggestedWords.getEmptyInstance(), false /* dismissGestureFloatingPreviewText */); - handler.cancelUpdateSuggestionStrip(); - ++mAutoCommitSequenceNumber; - mConnection.beginBatchEdit(); - if (mWordComposer.isComposingWord()) { - if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { - // If we are in the middle of a recorrection, we need to commit the recorrection - // first so that we can insert the batch input at the current cursor position. - // We also need to unlearn the original word that is now being corrected. - unlearnWord(mWordComposer.getTypedWord(), settingsValues, - Constants.EVENT_BACKSPACE); - resetEntireInputState(mConnection.getExpectedSelectionStart(), - mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */); - } else if (mWordComposer.isSingleLetter()) { - // We auto-correct the previous (typed, not gestured) string iff it's one character - // long. The reason for this is, even in the middle of gesture typing, you'll still - // tap one-letter words and you want them auto-corrected (typically, "i" in English - // should become "I"). However for any longer word, we assume that the reason for - // tapping probably is that the word you intend to type is not in the dictionary, - // so we do not attempt to correct, on the assumption that if that was a dictionary - // word, the user would probably have gestured instead. - commitCurrentAutoCorrection(settingsValues, LastComposedWord.NOT_A_SEPARATOR, - handler); - } else { - commitTyped(settingsValues, LastComposedWord.NOT_A_SEPARATOR); - } - } - final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); - if (Character.isLetterOrDigit(codePointBeforeCursor) - || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) { - final boolean autoShiftHasBeenOverriden = keyboardSwitcher.getKeyboardShiftMode() != - getCurrentAutoCapsState(settingsValues); - mSpaceState = SpaceState.PHANTOM; - if (!autoShiftHasBeenOverriden) { - // When we change the space state, we need to update the shift state of the - // keyboard unless it has been overridden manually. This is happening for example - // after typing some letters and a period, then gesturing; the keyboard is not in - // caps mode yet, but since a gesture is starting, it should go in caps mode, - // unless the user explictly said it should not. - keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(settingsValues), - getCurrentRecapitalizeState()); - } - } - mConnection.endBatchEdit(); - mWordComposer.setCapitalizedModeAtStartComposingTime( - getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode())); - } - - /* The sequence number member is only used in onUpdateBatchInput. It is increased each time - * auto-commit happens. The reason we need this is, when auto-commit happens we trim the - * input pointers that are held in a singleton, and to know how much to trim we rely on the - * results of the suggestion process that is held in mSuggestedWords. - * However, the suggestion process is asynchronous, and sometimes we may enter the - * onUpdateBatchInput method twice without having recomputed suggestions yet, or having - * received new suggestions generated from not-yet-trimmed input pointers. In this case, the - * mIndexOfTouchPointOfSecondWords member will be out of date, and we must not use it lest we - * remove an unrelated number of pointers (possibly even more than are left in the input - * pointers, leading to a crash). - * To avoid that, we increase the sequence number each time we auto-commit and trim the - * input pointers, and we do not use any suggested words that have been generated with an - * earlier sequence number. - */ - private int mAutoCommitSequenceNumber = 1; - public void onUpdateBatchInput(final InputPointers batchPointers) { - mInputLogicHandler.onUpdateBatchInput(batchPointers, mAutoCommitSequenceNumber); - } - - public void onEndBatchInput(final InputPointers batchPointers) { - mInputLogicHandler.updateTailBatchInput(batchPointers, mAutoCommitSequenceNumber); - ++mAutoCommitSequenceNumber; - } - - public void onCancelBatchInput(final LatinIME.UIHandler handler) { - mInputLogicHandler.onCancelBatchInput(); - handler.showGesturePreviewAndSuggestionStrip( - SuggestedWords.getEmptyInstance(), true /* dismissGestureFloatingPreviewText */); - } - - // TODO: on the long term, this method should become private, but it will be difficult. - // Especially, how do we deal with InputMethodService.onDisplayCompletions? - public void setSuggestedWords(final SuggestedWords suggestedWords) { - if (!suggestedWords.isEmpty()) { - final SuggestedWordInfo suggestedWordInfo; - if (suggestedWords.mWillAutoCorrect) { - suggestedWordInfo = suggestedWords.getInfo(SuggestedWords.INDEX_OF_AUTO_CORRECTION); - } else { - // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD) - // because it may differ from mWordComposer.mTypedWord. - suggestedWordInfo = suggestedWords.mTypedWordInfo; - } - mWordComposer.setAutoCorrection(suggestedWordInfo); - } - mSuggestedWords = suggestedWords; - final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect; - - // Put a blue underline to a word in TextView which will be auto-corrected. - if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator - && mWordComposer.isComposingWord()) { - mIsAutoCorrectionIndicatorOn = newAutoCorrectionIndicator; - final CharSequence textWithUnderline = - getTextWithUnderline(mWordComposer.getTypedWord()); - // TODO: when called from an updateSuggestionStrip() call that results from a posted - // message, this is called outside any batch edit. Potentially, this may result in some - // janky flickering of the screen, although the display speed makes it unlikely in - // the practice. - setComposingTextInternal(textWithUnderline, 1); - } - } - - /** - * Handle a consumed event. - * - * Consumed events represent events that have already been consumed, typically by the - * combining chain. - * - * @param event The event to handle. - * @param inputTransaction The transaction in progress. - */ - private void handleConsumedEvent(final Event event, final InputTransaction inputTransaction) { - // A consumed event may have text to commit and an update to the composing state, so - // we evaluate both. With some combiners, it's possible than an event contains both - // and we enter both of the following if clauses. - final CharSequence textToCommit = event.getTextToCommit(); - if (!TextUtils.isEmpty(textToCommit)) { - mConnection.commitText(textToCommit, 1); - inputTransaction.setDidAffectContents(); - } - if (mWordComposer.isComposingWord()) { - setComposingTextInternal(mWordComposer.getTypedWord(), 1); - inputTransaction.setDidAffectContents(); - inputTransaction.setRequiresUpdateSuggestions(); - } - } - - /** - * Handle a functional key event. - * - * A functional event is a special key, like delete, shift, emoji, or the settings key. - * Non-special keys are those that generate a single code point. - * This includes all letters, digits, punctuation, separators, emoji. It excludes keys that - * manage keyboard-related stuff like shift, language switch, settings, layout switch, or - * any key that results in multiple code points like the ".com" key. - * - * @param event The event to handle. - * @param inputTransaction The transaction in progress. - */ - private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction, - final int currentKeyboardScriptId, final LatinIME.UIHandler handler) { - switch (event.mKeyCode) { - case Constants.CODE_DELETE: - handleBackspaceEvent(event, inputTransaction, currentKeyboardScriptId); - // Backspace is a functional key, but it affects the contents of the editor. - inputTransaction.setDidAffectContents(); - break; - case Constants.CODE_SHIFT: - performRecapitalization(inputTransaction.mSettingsValues); - inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); - if (mSuggestedWords.isPrediction()) { - inputTransaction.setRequiresUpdateSuggestions(); - } - break; - case Constants.CODE_CAPSLOCK: - // Note: Changing keyboard to shift lock state is handled in - // {@link KeyboardSwitcher#onEvent(Event)}. - break; - case Constants.CODE_SYMBOL_SHIFT: - // Note: Calling back to the keyboard on the symbol Shift key is handled in - // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}. - break; - case Constants.CODE_SWITCH_ALPHA_SYMBOL: - // Note: Calling back to the keyboard on symbol key is handled in - // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}. - break; - case Constants.CODE_SETTINGS: - onSettingsKeyPressed(); - break; - case Constants.CODE_SHORTCUT: - // We need to switch to the shortcut IME. This is handled by LatinIME since the - // input logic has no business with IME switching. - break; - case Constants.CODE_ACTION_NEXT: - performEditorAction(EditorInfo.IME_ACTION_NEXT); - break; - case Constants.CODE_ACTION_PREVIOUS: - performEditorAction(EditorInfo.IME_ACTION_PREVIOUS); - break; - case Constants.CODE_LANGUAGE_SWITCH: - handleLanguageSwitchKey(); - break; - case Constants.CODE_EMOJI: - // Note: Switching emoji keyboard is being handled in - // {@link KeyboardState#onEvent(Event,int)}. - break; - case Constants.CODE_ALPHA_FROM_EMOJI: - // Note: Switching back from Emoji keyboard to the main keyboard is being - // handled in {@link KeyboardState#onEvent(Event,int)}. - break; - case Constants.CODE_SHIFT_ENTER: - final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_ENTER, - event.mKeyCode, event.mX, event.mY, event.isKeyRepeat()); - handleNonSpecialCharacterEvent(tmpEvent, inputTransaction, handler); - // Shift + Enter is treated as a functional key but it results in adding a new - // line, so that does affect the contents of the editor. - inputTransaction.setDidAffectContents(); - break; - default: - throw new RuntimeException("Unknown key code : " + event.mKeyCode); - } - } - - /** - * Handle an event that is not a functional event. - * - * These events are generally events that cause input, but in some cases they may do other - * things like trigger an editor action. - * - * @param event The event to handle. - * @param inputTransaction The transaction in progress. - */ - private void handleNonFunctionalEvent(final Event event, - final InputTransaction inputTransaction, - final LatinIME.UIHandler handler) { - inputTransaction.setDidAffectContents(); - switch (event.mCodePoint) { - case Constants.CODE_ENTER: - final EditorInfo editorInfo = getCurrentInputEditorInfo(); - final int imeOptionsActionId = - InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo); - if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) { - // Either we have an actionLabel and we should performEditorAction with - // actionId regardless of its value. - performEditorAction(editorInfo.actionId); - } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) { - // We didn't have an actionLabel, but we had another action to execute. - // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast, - // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it - // means there should be an action and the app didn't bother to set a specific - // code for it - presumably it only handles one. It does not have to be treated - // in any specific way: anything that is not IME_ACTION_NONE should be sent to - // performEditorAction. - performEditorAction(imeOptionsActionId); - } else { - // No action label, and the action from imeOptions is NONE: this is a regular - // enter key that should input a carriage return. - handleNonSpecialCharacterEvent(event, inputTransaction, handler); - } - break; - default: - handleNonSpecialCharacterEvent(event, inputTransaction, handler); - break; - } - } - - /** - * Handle inputting a code point to the editor. - * - * Non-special keys are those that generate a single code point. - * This includes all letters, digits, punctuation, separators, emoji. It excludes keys that - * manage keyboard-related stuff like shift, language switch, settings, layout switch, or - * any key that results in multiple code points like the ".com" key. - * - * @param event The event to handle. - * @param inputTransaction The transaction in progress. - */ - private void handleNonSpecialCharacterEvent(final Event event, - final InputTransaction inputTransaction, - final LatinIME.UIHandler handler) { - final int codePoint = event.mCodePoint; - mSpaceState = SpaceState.NONE; - if (inputTransaction.mSettingsValues.isWordSeparator(codePoint) - || Character.getType(codePoint) == Character.OTHER_SYMBOL) { - handleSeparatorEvent(event, inputTransaction, handler); - } else { - if (SpaceState.PHANTOM == inputTransaction.mSpaceState) { - if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { - // If we are in the middle of a recorrection, we need to commit the recorrection - // first so that we can insert the character at the current cursor position. - // We also need to unlearn the original word that is now being corrected. - unlearnWord(mWordComposer.getTypedWord(), inputTransaction.mSettingsValues, - Constants.EVENT_BACKSPACE); - resetEntireInputState(mConnection.getExpectedSelectionStart(), - mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */); - } else { - commitTyped(inputTransaction.mSettingsValues, LastComposedWord.NOT_A_SEPARATOR); - } - } - handleNonSeparatorEvent(event, inputTransaction.mSettingsValues, inputTransaction); - } - } - - /** - * Handle a non-separator. - * @param event The event to handle. - * @param settingsValues The current settings values. - * @param inputTransaction The transaction in progress. - */ - private void handleNonSeparatorEvent(final Event event, final SettingsValues settingsValues, - final InputTransaction inputTransaction) { - final int codePoint = event.mCodePoint; - // TODO: refactor this method to stop flipping isComposingWord around all the time, and - // make it shorter (possibly cut into several pieces). Also factor - // handleNonSpecialCharacterEvent which has the same name as other handle* methods but is - // not the same. - boolean isComposingWord = mWordComposer.isComposingWord(); - - // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead. - // See onStartBatchInput() to see how to do it. - if (SpaceState.PHANTOM == inputTransaction.mSpaceState - && !settingsValues.isWordConnector(codePoint)) { - if (isComposingWord) { - // Validity check - throw new RuntimeException("Should not be composing here"); - } - insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues); - } - - if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { - // If we are in the middle of a recorrection, we need to commit the recorrection - // first so that we can insert the character at the current cursor position. - // We also need to unlearn the original word that is now being corrected. - unlearnWord(mWordComposer.getTypedWord(), inputTransaction.mSettingsValues, - Constants.EVENT_BACKSPACE); - resetEntireInputState(mConnection.getExpectedSelectionStart(), - mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */); - isComposingWord = false; - } - // We want to find out whether to start composing a new word with this character. If so, - // we need to reset the composing state and switch isComposingWord. The order of the - // tests is important for good performance. - // We only start composing if we're not already composing. - if (!isComposingWord - // We only start composing if this is a word code point. Essentially that means it's a - // a letter or a word connector. - && settingsValues.isWordCodePoint(codePoint) - // We never go into composing state if suggestions are not requested. - && settingsValues.needsToLookupSuggestions() && - // In languages with spaces, we only start composing a word when we are not already - // touching a word. In languages without spaces, the above conditions are sufficient. - // NOTE: If the InputConnection is slow, we skip the text-after-cursor check since it - // can incur a very expensive getTextAfterCursor() lookup, potentially making the - // keyboard UI slow and non-responsive. - // TODO: Cache the text after the cursor so we don't need to go to the InputConnection - // each time. We are already doing this for getTextBeforeCursor(). - (!settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces - || !mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations, - !mConnection.hasSlowInputConnection() /* checkTextAfter */))) { - // Reset entirely the composing state anyway, then start composing a new word unless - // the character is a word connector. The idea here is, word connectors are not - // separators and they should be treated as normal characters, except in the first - // position where they should not start composing a word. - isComposingWord = !settingsValues.mSpacingAndPunctuations.isWordConnector(codePoint); - // Here we don't need to reset the last composed word. It will be reset - // when we commit this one, if we ever do; if on the other hand we backspace - // it entirely and resume suggestions on the previous word, we'd like to still - // have touch coordinates for it. - resetComposingState(false /* alsoResetLastComposedWord */); - } - if (isComposingWord) { - mWordComposer.applyProcessedEvent(event); - // If it's the first letter, make note of auto-caps state - if (mWordComposer.isSingleLetter()) { - mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.mShiftState); - } - setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1); - } else { - final boolean swapWeakSpace = tryStripSpaceAndReturnWhetherShouldSwapInstead(event, - inputTransaction); - - if (swapWeakSpace && trySwapSwapperAndSpace(event, inputTransaction)) { - mSpaceState = SpaceState.WEAK; - } else { - sendKeyCodePoint(settingsValues, codePoint); - } - } - inputTransaction.setRequiresUpdateSuggestions(); - } - - /** - * Handle input of a separator code point. - * @param event The event to handle. - * @param inputTransaction The transaction in progress. - */ - private void handleSeparatorEvent(final Event event, final InputTransaction inputTransaction, - final LatinIME.UIHandler handler) { - final int codePoint = event.mCodePoint; - final SettingsValues settingsValues = inputTransaction.mSettingsValues; - final boolean wasComposingWord = mWordComposer.isComposingWord(); - // We avoid sending spaces in languages without spaces if we were composing. - final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint - && !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces - && wasComposingWord; - if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { - // If we are in the middle of a recorrection, we need to commit the recorrection - // first so that we can insert the separator at the current cursor position. - // We also need to unlearn the original word that is now being corrected. - unlearnWord(mWordComposer.getTypedWord(), inputTransaction.mSettingsValues, - Constants.EVENT_BACKSPACE); - resetEntireInputState(mConnection.getExpectedSelectionStart(), - mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */); - } - // isComposingWord() may have changed since we stored wasComposing - if (mWordComposer.isComposingWord()) { - if (settingsValues.mAutoCorrectionEnabledPerUserSettings) { - final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR - : StringUtils.newSingleCodePointString(codePoint); - commitCurrentAutoCorrection(settingsValues, separator, handler); - inputTransaction.setDidAutoCorrect(); - } else { - commitTyped(settingsValues, - StringUtils.newSingleCodePointString(codePoint)); - } - } - - final boolean swapWeakSpace = tryStripSpaceAndReturnWhetherShouldSwapInstead(event, - inputTransaction); - - final boolean isInsideDoubleQuoteOrAfterDigit = Constants.CODE_DOUBLE_QUOTE == codePoint - && mConnection.isInsideDoubleQuoteOrAfterDigit(); - - final boolean needsPrecedingSpace; - if (SpaceState.PHANTOM != inputTransaction.mSpaceState) { - needsPrecedingSpace = false; - } else if (Constants.CODE_DOUBLE_QUOTE == codePoint) { - // Double quotes behave like they are usually preceded by space iff we are - // not inside a double quote or after a digit. - needsPrecedingSpace = !isInsideDoubleQuoteOrAfterDigit; - } else if (settingsValues.mSpacingAndPunctuations.isClusteringSymbol(codePoint) - && settingsValues.mSpacingAndPunctuations.isClusteringSymbol( - mConnection.getCodePointBeforeCursor())) { - needsPrecedingSpace = false; - } else { - needsPrecedingSpace = settingsValues.isUsuallyPrecededBySpace(codePoint); - } - - if (needsPrecedingSpace) { - insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues); - } - - if (tryPerformDoubleSpacePeriod(event, inputTransaction)) { - mSpaceState = SpaceState.DOUBLE; - inputTransaction.setRequiresUpdateSuggestions(); - StatsUtils.onDoubleSpacePeriod(); - } else if (swapWeakSpace && trySwapSwapperAndSpace(event, inputTransaction)) { - mSpaceState = SpaceState.SWAP_PUNCTUATION; - mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); - } else if (Constants.CODE_SPACE == codePoint) { - if (!mSuggestedWords.isPunctuationSuggestions()) { - mSpaceState = SpaceState.WEAK; - } - - startDoubleSpacePeriodCountdown(inputTransaction); - if (wasComposingWord || mSuggestedWords.isEmpty()) { - inputTransaction.setRequiresUpdateSuggestions(); - } - - if (!shouldAvoidSendingCode) { - sendKeyCodePoint(settingsValues, codePoint); - } - } else { - if ((SpaceState.PHANTOM == inputTransaction.mSpaceState - && settingsValues.isUsuallyFollowedBySpace(codePoint)) - || (Constants.CODE_DOUBLE_QUOTE == codePoint - && isInsideDoubleQuoteOrAfterDigit)) { - // If we are in phantom space state, and the user presses a separator, we want to - // stay in phantom space state so that the next keypress has a chance to add the - // space. For example, if I type "Good dat", pick "day" from the suggestion strip - // then insert a comma and go on to typing the next word, I want the space to be - // inserted automatically before the next word, the same way it is when I don't - // input the comma. A double quote behaves like it's usually followed by space if - // we're inside a double quote. - // The case is a little different if the separator is a space stripper. Such a - // separator does not normally need a space on the right (that's the difference - // between swappers and strippers), so we should not stay in phantom space state if - // the separator is a stripper. Hence the additional test above. - mSpaceState = SpaceState.PHANTOM; - } - - sendKeyCodePoint(settingsValues, codePoint); - - // Set punctuation right away. onUpdateSelection will fire but tests whether it is - // already displayed or not, so it's okay. - mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); - } - - inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); - } - - /** - * Handle a press on the backspace key. - * @param event The event to handle. - * @param inputTransaction The transaction in progress. - */ - private void handleBackspaceEvent(final Event event, final InputTransaction inputTransaction, - final int currentKeyboardScriptId) { - mSpaceState = SpaceState.NONE; - mDeleteCount++; - - // In many cases after backspace, we need to update the shift state. Normally we need - // to do this right away to avoid the shift state being out of date in case the user types - // backspace then some other character very fast. However, in the case of backspace key - // repeat, this can lead to flashiness when the cursor flies over positions where the - // shift state should be updated, so if this is a key repeat, we update after a small delay. - // Then again, even in the case of a key repeat, if the cursor is at start of text, it - // can't go any further back, so we can update right away even if it's a key repeat. - final int shiftUpdateKind = - event.isKeyRepeat() && mConnection.getExpectedSelectionStart() > 0 - ? InputTransaction.SHIFT_UPDATE_LATER : InputTransaction.SHIFT_UPDATE_NOW; - inputTransaction.requireShiftUpdate(shiftUpdateKind); - - if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { - // If we are in the middle of a recorrection, we need to commit the recorrection - // first so that we can remove the character at the current cursor position. - // We also need to unlearn the original word that is now being corrected. - unlearnWord(mWordComposer.getTypedWord(), inputTransaction.mSettingsValues, - Constants.EVENT_BACKSPACE); - resetEntireInputState(mConnection.getExpectedSelectionStart(), - mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */); - // When we exit this if-clause, mWordComposer.isComposingWord() will return false. - } - if (mWordComposer.isComposingWord()) { - if (mWordComposer.isBatchMode()) { - final String rejectedSuggestion = mWordComposer.getTypedWord(); - mWordComposer.reset(); - mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion); - if (!TextUtils.isEmpty(rejectedSuggestion)) { - unlearnWord(rejectedSuggestion, inputTransaction.mSettingsValues, - Constants.EVENT_REJECTION); - } - StatsUtils.onBackspaceWordDelete(rejectedSuggestion.length()); - } else { - mWordComposer.applyProcessedEvent(event); - StatsUtils.onBackspacePressed(1); - } - if (mWordComposer.isComposingWord()) { - setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1); - } else { - mConnection.commitText("", 1); - } - inputTransaction.setRequiresUpdateSuggestions(); - } else { - if (mLastComposedWord.canRevertCommit()) { - final String lastComposedWord = mLastComposedWord.mTypedWord; - revertCommit(inputTransaction, inputTransaction.mSettingsValues); - StatsUtils.onRevertAutoCorrect(); - StatsUtils.onWordCommitUserTyped(lastComposedWord, mWordComposer.isBatchMode()); - // Restart suggestions when backspacing into a reverted word. This is required for - // the final corrected word to be learned, as learning only occurs when suggestions - // are active. - // - // Note: restartSuggestionsOnWordTouchedByCursor is already called for normal - // (non-revert) backspace handling. - if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings() - && inputTransaction.mSettingsValues.mSpacingAndPunctuations - .mCurrentLanguageHasSpaces - && !mConnection.isCursorFollowedByWordCharacter( - inputTransaction.mSettingsValues.mSpacingAndPunctuations)) { - restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues, - false /* forStartInput */, currentKeyboardScriptId); - } - return; - } - if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) { - // Cancel multi-character input: remove the text we just entered. - // This is triggered on backspace after a key that inputs multiple characters, - // like the smiley key or the .com key. - mConnection.deleteTextBeforeCursor(mEnteredText.length()); - StatsUtils.onDeleteMultiCharInput(mEnteredText.length()); - mEnteredText = null; - // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false. - // In addition we know that spaceState is false, and that we should not be - // reverting any autocorrect at this point. So we can safely return. - return; - } - if (SpaceState.DOUBLE == inputTransaction.mSpaceState) { - cancelDoubleSpacePeriodCountdown(); - if (mConnection.revertDoubleSpacePeriod( - inputTransaction.mSettingsValues.mSpacingAndPunctuations)) { - // No need to reset mSpaceState, it has already be done (that's why we - // receive it as a parameter) - inputTransaction.setRequiresUpdateSuggestions(); - mWordComposer.setCapitalizedModeAtStartComposingTime( - WordComposer.CAPS_MODE_OFF); - StatsUtils.onRevertDoubleSpacePeriod(); - return; - } - } else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) { - if (mConnection.revertSwapPunctuation()) { - StatsUtils.onRevertSwapPunctuation(); - // Likewise - return; - } - } - - boolean hasUnlearnedWordBeingDeleted = false; - - // No cancelling of commit/double space/swap: we have a regular backspace. - // We should backspace one char and restart suggestion if at the end of a word. - if (mConnection.hasSelection()) { - // If there is a selection, remove it. - // We also need to unlearn the selected text. - final CharSequence selection = mConnection.getSelectedText(0 /* 0 for no styles */); - if (!TextUtils.isEmpty(selection)) { - unlearnWord(selection.toString(), inputTransaction.mSettingsValues, - Constants.EVENT_BACKSPACE); - hasUnlearnedWordBeingDeleted = true; - } - final int numCharsDeleted = mConnection.getExpectedSelectionEnd() - - mConnection.getExpectedSelectionStart(); - mConnection.setSelection(mConnection.getExpectedSelectionEnd(), - mConnection.getExpectedSelectionEnd()); - mConnection.deleteTextBeforeCursor(numCharsDeleted); - StatsUtils.onBackspaceSelectedText(numCharsDeleted); - } else { - // There is no selection, just delete one character. - if (inputTransaction.mSettingsValues.isBeforeJellyBean() - || inputTransaction.mSettingsValues.mInputAttributes.isTypeNull() - || Constants.NOT_A_CURSOR_POSITION - == mConnection.getExpectedSelectionEnd()) { - // There are three possible reasons to send a key event: either the field has - // type TYPE_NULL, in which case the keyboard should send events, or we are - // running in backward compatibility mode, or we don't know the cursor position. - // Before Jelly bean, the keyboard would simulate a hardware keyboard event on - // pressing enter or delete. This is bad for many reasons (there are race - // conditions with commits) but some applications are relying on this behavior - // so we continue to support it for older apps, so we retain this behavior if - // the app has target SDK < JellyBean. - // As for the case where we don't know the cursor position, it can happen - // because of bugs in the framework. But the framework should know, so the next - // best thing is to leave it to whatever it thinks is best. - sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL); - int totalDeletedLength = 1; - if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) { - // If this is an accelerated (i.e., double) deletion, then we need to - // consider unlearning here because we may have already reached - // the previous word, and will lose it after next deletion. - hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted( - inputTransaction.mSettingsValues, currentKeyboardScriptId); - sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL); - totalDeletedLength++; - } - StatsUtils.onBackspacePressed(totalDeletedLength); - } else { - final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); - if (codePointBeforeCursor == Constants.NOT_A_CODE) { - // HACK for backward compatibility with broken apps that haven't realized - // yet that hardware keyboards are not the only way of inputting text. - // Nothing to delete before the cursor. We should not do anything, but many - // broken apps expect something to happen in this case so that they can - // catch it and have their broken interface react. If you need the keyboard - // to do this, you're doing it wrong -- please fix your app. - mConnection.deleteTextBeforeCursor(1); - // TODO: Add a new StatsUtils method onBackspaceWhenNoText() - return; - } - final int lengthToDelete = - Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1; - mConnection.deleteTextBeforeCursor(lengthToDelete); - int totalDeletedLength = lengthToDelete; - if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) { - // If this is an accelerated (i.e., double) deletion, then we need to - // consider unlearning here because we may have already reached - // the previous word, and will lose it after next deletion. - hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted( - inputTransaction.mSettingsValues, currentKeyboardScriptId); - final int codePointBeforeCursorToDeleteAgain = - mConnection.getCodePointBeforeCursor(); - if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) { - final int lengthToDeleteAgain = Character.isSupplementaryCodePoint( - codePointBeforeCursorToDeleteAgain) ? 2 : 1; - mConnection.deleteTextBeforeCursor(lengthToDeleteAgain); - totalDeletedLength += lengthToDeleteAgain; - } - } - StatsUtils.onBackspacePressed(totalDeletedLength); - } - } - if (!hasUnlearnedWordBeingDeleted) { - // Consider unlearning the word being deleted (if we have not done so already). - unlearnWordBeingDeleted( - inputTransaction.mSettingsValues, currentKeyboardScriptId); - } - if (mConnection.hasSlowInputConnection()) { - mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); - } else if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings() - && inputTransaction.mSettingsValues.mSpacingAndPunctuations - .mCurrentLanguageHasSpaces - && !mConnection.isCursorFollowedByWordCharacter( - inputTransaction.mSettingsValues.mSpacingAndPunctuations)) { - restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues, - false /* forStartInput */, currentKeyboardScriptId); - } - } - } - - String getWordAtCursor(final SettingsValues settingsValues, final int currentKeyboardScriptId) { - if (!mConnection.hasSelection() - && settingsValues.isSuggestionsEnabledPerUserSettings() - && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) { - final TextRange range = mConnection.getWordRangeAtCursor( - settingsValues.mSpacingAndPunctuations, - currentKeyboardScriptId); - if (range != null) { - return range.mWord.toString(); - } - } - return ""; - } - - boolean unlearnWordBeingDeleted( - final SettingsValues settingsValues, final int currentKeyboardScriptId) { - if (mConnection.hasSlowInputConnection()) { - // TODO: Refactor unlearning so that it does not incur any extra calls - // to the InputConnection. That way it can still be performed on a slow - // InputConnection. - Log.w(TAG, "Skipping unlearning due to slow InputConnection."); - return false; - } - // If we just started backspacing to delete a previous word (but have not - // entered the composing state yet), unlearn the word. - // TODO: Consider tracking whether or not this word was typed by the user. - if (!mConnection.isCursorFollowedByWordCharacter(settingsValues.mSpacingAndPunctuations)) { - final String wordBeingDeleted = getWordAtCursor( - settingsValues, currentKeyboardScriptId); - if (!TextUtils.isEmpty(wordBeingDeleted)) { - unlearnWord(wordBeingDeleted, settingsValues, Constants.EVENT_BACKSPACE); - return true; - } - } - return false; - } - - void unlearnWord(final String word, final SettingsValues settingsValues, final int eventType) { - final NgramContext ngramContext = mConnection.getNgramContextFromNthPreviousWord( - settingsValues.mSpacingAndPunctuations, 2); - final long timeStampInSeconds = TimeUnit.MILLISECONDS.toSeconds( - System.currentTimeMillis()); - mDictionaryFacilitator.unlearnFromUserHistory( - word, ngramContext, timeStampInSeconds, eventType); - } - - /** - * Handle a press on the language switch key (the "globe key") - */ - private void handleLanguageSwitchKey() { - mLatinIME.switchToNextSubtype(); - } - - /** - * Swap a space with a space-swapping punctuation sign. - * - * This method will check that there are two characters before the cursor and that the first - * one is a space before it does the actual swapping. - * @param event The event to handle. - * @param inputTransaction The transaction in progress. - * @return true if the swap has been performed, false if it was prevented by preliminary checks. - */ - private boolean trySwapSwapperAndSpace(final Event event, - final InputTransaction inputTransaction) { - final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); - if (Constants.CODE_SPACE != codePointBeforeCursor) { - return false; - } - mConnection.deleteTextBeforeCursor(1); - final String text = event.getTextToCommit() + " "; - mConnection.commitText(text, 1); - inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); - return true; - } - - /* - * Strip a trailing space if necessary and returns whether it's a swap weak space situation. - * @param event The event to handle. - * @param inputTransaction The transaction in progress. - * @return whether we should swap the space instead of removing it. - */ - private boolean tryStripSpaceAndReturnWhetherShouldSwapInstead(final Event event, - final InputTransaction inputTransaction) { - final int codePoint = event.mCodePoint; - final boolean isFromSuggestionStrip = event.isSuggestionStripPress(); - if (Constants.CODE_ENTER == codePoint && - SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) { - mConnection.removeTrailingSpace(); - return false; - } - if ((SpaceState.WEAK == inputTransaction.mSpaceState - || SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) - && isFromSuggestionStrip) { - if (inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(codePoint)) { - return false; - } - if (inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(codePoint)) { - return true; - } - mConnection.removeTrailingSpace(); - } - return false; - } - - public void startDoubleSpacePeriodCountdown(final InputTransaction inputTransaction) { - mDoubleSpacePeriodCountdownStart = inputTransaction.mTimestamp; - } - - public void cancelDoubleSpacePeriodCountdown() { - mDoubleSpacePeriodCountdownStart = 0; - } - - public boolean isDoubleSpacePeriodCountdownActive(final InputTransaction inputTransaction) { - return inputTransaction.mTimestamp - mDoubleSpacePeriodCountdownStart - < inputTransaction.mSettingsValues.mDoubleSpacePeriodTimeout; - } - - /** - * Apply the double-space-to-period transformation if applicable. - * - * The double-space-to-period transformation means that we replace two spaces with a - * period-space sequence of characters. This typically happens when the user presses space - * twice in a row quickly. - * This method will check that the double-space-to-period is active in settings, that the - * two spaces have been input close enough together, that the typed character is a space - * and that the previous character allows for the transformation to take place. If all of - * these conditions are fulfilled, this method applies the transformation and returns true. - * Otherwise, it does nothing and returns false. - * - * @param event The event to handle. - * @param inputTransaction The transaction in progress. - * @return true if we applied the double-space-to-period transformation, false otherwise. - */ - private boolean tryPerformDoubleSpacePeriod(final Event event, - final InputTransaction inputTransaction) { - // Check the setting, the typed character and the countdown. If any of the conditions is - // not fulfilled, return false. - if (!inputTransaction.mSettingsValues.mUseDoubleSpacePeriod - || Constants.CODE_SPACE != event.mCodePoint - || !isDoubleSpacePeriodCountdownActive(inputTransaction)) { - return false; - } - // We only do this when we see one space and an accepted code point before the cursor. - // The code point may be a surrogate pair but the space may not, so we need 3 chars. - final CharSequence lastTwo = mConnection.getTextBeforeCursor(3, 0); - if (null == lastTwo) return false; - final int length = lastTwo.length(); - if (length < 2) return false; - if (lastTwo.charAt(length - 1) != Constants.CODE_SPACE) { - return false; - } - // We know there is a space in pos -1, and we have at least two chars. If we have only two - // chars, isSurrogatePairs can't return true as charAt(1) is a space, so this is fine. - final int firstCodePoint = - Character.isSurrogatePair(lastTwo.charAt(0), lastTwo.charAt(1)) ? - Character.codePointAt(lastTwo, length - 3) : lastTwo.charAt(length - 2); - if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) { - cancelDoubleSpacePeriodCountdown(); - mConnection.deleteTextBeforeCursor(1); - final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations - .mSentenceSeparatorAndSpace; - mConnection.commitText(textToInsert, 1); - inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); - inputTransaction.setRequiresUpdateSuggestions(); - return true; - } - return false; - } - - /** - * Returns whether this code point can be followed by the double-space-to-period transformation. - * - * See #maybeDoubleSpaceToPeriod for details. - * Generally, most word characters can be followed by the double-space-to-period transformation, - * while most punctuation can't. Some punctuation however does allow for this to take place - * after them, like the closing parenthesis for example. - * - * @param codePoint the code point after which we may want to apply the transformation - * @return whether it's fine to apply the transformation after this code point. - */ - private static boolean canBeFollowedByDoubleSpacePeriod(final int codePoint) { - // TODO: This should probably be a denylist rather than a allowlist. - // TODO: This should probably be language-dependant... - return Character.isLetterOrDigit(codePoint) - || codePoint == Constants.CODE_SINGLE_QUOTE - || codePoint == Constants.CODE_DOUBLE_QUOTE - || codePoint == Constants.CODE_CLOSING_PARENTHESIS - || codePoint == Constants.CODE_CLOSING_SQUARE_BRACKET - || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET - || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET - || codePoint == Constants.CODE_PLUS - || codePoint == Constants.CODE_PERCENT - || Character.getType(codePoint) == Character.OTHER_SYMBOL; - } - - /** - * Performs a recapitalization event. - * @param settingsValues The current settings values. - */ - private void performRecapitalization(final SettingsValues settingsValues) { - if (!mConnection.hasSelection() || !mRecapitalizeStatus.mIsEnabled()) { - return; // No selection or recapitalize is disabled for now - } - final int selectionStart = mConnection.getExpectedSelectionStart(); - final int selectionEnd = mConnection.getExpectedSelectionEnd(); - final int numCharsSelected = selectionEnd - selectionStart; - if (numCharsSelected > Constants.MAX_CHARACTERS_FOR_RECAPITALIZATION) { - // We bail out if we have too many characters for performance reasons. We don't want - // to suck possibly multiple-megabyte data. - return; - } - // If we have a recapitalize in progress, use it; otherwise, start a new one. - if (!mRecapitalizeStatus.isStarted() - || !mRecapitalizeStatus.isSetAt(selectionStart, selectionEnd)) { - final CharSequence selectedText = - mConnection.getSelectedText(0 /* flags, 0 for no styles */); - if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection - mRecapitalizeStatus.start(selectionStart, selectionEnd, selectedText.toString(), - settingsValues.mLocale, - settingsValues.mSpacingAndPunctuations.mSortedWordSeparators); - // We trim leading and trailing whitespace. - mRecapitalizeStatus.trim(); - } - mConnection.finishComposingText(); - mRecapitalizeStatus.rotate(); - mConnection.setSelection(selectionEnd, selectionEnd); - mConnection.deleteTextBeforeCursor(numCharsSelected); - mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0); - mConnection.setSelection(mRecapitalizeStatus.getNewCursorStart(), - mRecapitalizeStatus.getNewCursorEnd()); - } - - private void performAdditionToUserHistoryDictionary(final SettingsValues settingsValues, - final String suggestion, @Nonnull final NgramContext ngramContext) { - // If correction is not enabled, we don't add words to the user history dictionary. - // That's to avoid unintended additions in some sensitive fields, or fields that - // expect to receive non-words. - if (!settingsValues.mAutoCorrectionEnabledPerUserSettings) return; - if (mConnection.hasSlowInputConnection()) { - // Since we don't unlearn when the user backspaces on a slow InputConnection, - // turn off learning to guard against adding typos that the user later deletes. - Log.w(TAG, "Skipping learning due to slow InputConnection."); - return; - } - - if (TextUtils.isEmpty(suggestion)) return; - final boolean wasAutoCapitalized = - mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps(); - final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds( - System.currentTimeMillis()); - mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, - ngramContext, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive); - } - - public void performUpdateSuggestionStripSync(final SettingsValues settingsValues, - final int inputStyle) { - long startTimeMillis = 0; - if (DebugFlags.DEBUG_ENABLED) { - startTimeMillis = System.currentTimeMillis(); - Log.d(TAG, "performUpdateSuggestionStripSync()"); - } - // Check if we have a suggestion engine attached. - if (!settingsValues.needsToLookupSuggestions()) { - if (mWordComposer.isComposingWord()) { - Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not " - + "requested!"); - } - // Clear the suggestions strip. - mSuggestionStripViewAccessor.showSuggestionStrip(SuggestedWords.getEmptyInstance()); - return; - } - - if (!mWordComposer.isComposingWord() && !settingsValues.mBigramPredictionEnabled) { - mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); - return; - } - - final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<>("Suggest"); - mInputLogicHandler.getSuggestedWords(inputStyle, SuggestedWords.NOT_A_SEQUENCE_NUMBER, - new OnGetSuggestedWordsCallback() { - @Override - public void onGetSuggestedWords(final SuggestedWords suggestedWords) { - final String typedWordString = mWordComposer.getTypedWord(); - final SuggestedWordInfo typedWordInfo = new SuggestedWordInfo( - typedWordString, "" /* prevWordsContext */, - SuggestedWordInfo.MAX_SCORE, - SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED, - SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, - SuggestedWordInfo.NOT_A_CONFIDENCE); - // Show new suggestions if we have at least one. Otherwise keep the old - // suggestions with the new typed word. Exception: if the length of the - // typed word is <= 1 (after a deletion typically) we clear old suggestions. - if (suggestedWords.size() > 1 || typedWordString.length() <= 1) { - holder.set(suggestedWords); - } else { - holder.set(retrieveOlderSuggestions(typedWordInfo, mSuggestedWords)); - } - } - } - ); - - // This line may cause the current thread to wait. - final SuggestedWords suggestedWords = holder.get(null, - Constants.GET_SUGGESTED_WORDS_TIMEOUT); - if (suggestedWords != null) { - mSuggestionStripViewAccessor.showSuggestionStrip(suggestedWords); - } - if (DebugFlags.DEBUG_ENABLED) { - long runTimeMillis = System.currentTimeMillis() - startTimeMillis; - Log.d(TAG, "performUpdateSuggestionStripSync() : " + runTimeMillis + " ms to finish"); - } - } - - /** - * Check if the cursor is touching a word. If so, restart suggestions on this word, else - * do nothing. - * - * @param settingsValues the current values of the settings. - * @param forStartInput whether we're doing this in answer to starting the input (as opposed - * to a cursor move, for example). In ICS, there is a platform bug that we need to work - * around only when we come here at input start time. - */ - public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues, - final boolean forStartInput, - // TODO: remove this argument, put it into settingsValues - final int currentKeyboardScriptId) { - // HACK: We may want to special-case some apps that exhibit bad behavior in case of - // recorrection. This is a temporary, stopgap measure that will be removed later. - // TODO: remove this. - if (settingsValues.isBrokenByRecorrection() - // Recorrection is not supported in languages without spaces because we don't know - // how to segment them yet. - || !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces - // If no suggestions are requested, don't try restarting suggestions. - || !settingsValues.needsToLookupSuggestions() - // If we are currently in a batch input, we must not resume suggestions, or the result - // of the batch input will replace the new composition. This may happen in the corner case - // that the app moves the cursor on its own accord during a batch input. - || mInputLogicHandler.isInBatchInput() - // If the cursor is not touching a word, or if there is a selection, return right away. - || mConnection.hasSelection() - // If we don't know the cursor location, return. - || mConnection.getExpectedSelectionStart() < 0) { - mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); - return; - } - final int expectedCursorPosition = mConnection.getExpectedSelectionStart(); - if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations, - true /* checkTextAfter */)) { - // Show predictions. - mWordComposer.setCapitalizedModeAtStartComposingTime(WordComposer.CAPS_MODE_OFF); - mLatinIME.mHandler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_RECORRECTION); - return; - } - final TextRange range = mConnection.getWordRangeAtCursor( - settingsValues.mSpacingAndPunctuations, currentKeyboardScriptId); - if (null == range) return; // Happens if we don't have an input connection at all - if (range.length() <= 0) { - // Race condition, or touching a word in a non-supported script. - mLatinIME.setNeutralSuggestionStrip(); - return; - } - // If for some strange reason (editor bug or so) we measure the text before the cursor as - // longer than what the entire text is supposed to be, the safe thing to do is bail out. - if (range.mHasUrlSpans) return; // If there are links, we don't resume suggestions. Making - // edits to a linkified text through batch commands would ruin the URL spans, and unless - // we take very complicated steps to preserve the whole link, we can't do things right so - // we just do not resume because it's safer. - final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor(); - if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return; - final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>(); - final String typedWordString = range.mWord.toString(); - final SuggestedWordInfo typedWordInfo = new SuggestedWordInfo(typedWordString, - "" /* prevWordsContext */, SuggestedWords.MAX_SUGGESTIONS + 1, - SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED, - SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, - SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */); - suggestions.add(typedWordInfo); - if (!isResumableWord(settingsValues, typedWordString)) { - mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); - return; - } - int i = 0; - for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) { - for (final String s : span.getSuggestions()) { - ++i; - if (!TextUtils.equals(s, typedWordString)) { - suggestions.add(new SuggestedWordInfo(s, - "" /* prevWordsContext */, SuggestedWords.MAX_SUGGESTIONS - i, - SuggestedWordInfo.KIND_RESUMED, Dictionary.DICTIONARY_RESUMED, - SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, - SuggestedWordInfo.NOT_A_CONFIDENCE - /* autoCommitFirstWordConfidence */)); - } - } - } - final int[] codePoints = StringUtils.toCodePointArray(typedWordString); - mWordComposer.setComposingWord(codePoints, - mLatinIME.getCoordinatesForCurrentKeyboard(codePoints)); - mWordComposer.setCursorPositionWithinWord( - typedWordString.codePointCount(0, numberOfCharsInWordBeforeCursor)); - if (forStartInput) { - mConnection.maybeMoveTheCursorAroundAndRestoreToWorkaroundABug(); - } - mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor, - expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor()); - if (suggestions.size() <= 1) { - // If there weren't any suggestion spans on this word, suggestions#size() will be 1 - // if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we - // have no useful suggestions, so we will try to compute some for it instead. - mInputLogicHandler.getSuggestedWords(Suggest.SESSION_ID_TYPING, - SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() { - @Override - public void onGetSuggestedWords(final SuggestedWords suggestedWords) { - doShowSuggestionsAndClearAutoCorrectionIndicator(suggestedWords); - }}); - } else { - // We found suggestion spans in the word. We'll create the SuggestedWords out of - // them, and make willAutoCorrect false. We make typedWordValid false, because the - // color of the word in the suggestion strip changes according to this parameter, - // and false gives the correct color. - final SuggestedWords suggestedWords = new SuggestedWords(suggestions, - null /* rawSuggestions */, typedWordInfo, false /* typedWordValid */, - false /* willAutoCorrect */, false /* isObsoleteSuggestions */, - SuggestedWords.INPUT_STYLE_RECORRECTION, SuggestedWords.NOT_A_SEQUENCE_NUMBER); - doShowSuggestionsAndClearAutoCorrectionIndicator(suggestedWords); - } - } - - void doShowSuggestionsAndClearAutoCorrectionIndicator(final SuggestedWords suggestedWords) { - mIsAutoCorrectionIndicatorOn = false; - mLatinIME.mHandler.showSuggestionStrip(suggestedWords); - } - - /** - * Reverts a previous commit with auto-correction. - * - * This is triggered upon pressing backspace just after a commit with auto-correction. - * - * @param inputTransaction The transaction in progress. - * @param settingsValues the current values of the settings. - */ - private void revertCommit(final InputTransaction inputTransaction, - final SettingsValues settingsValues) { - final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord; - final String originallyTypedWordString = - originallyTypedWord != null ? originallyTypedWord.toString() : ""; - final CharSequence committedWord = mLastComposedWord.mCommittedWord; - final String committedWordString = committedWord.toString(); - final int cancelLength = committedWord.length(); - final String separatorString = mLastComposedWord.mSeparatorString; - // If our separator is a space, we won't actually commit it, - // but set the space state to PHANTOM so that a space will be inserted - // on the next keypress - final boolean usePhantomSpace = separatorString.equals(Constants.STRING_SPACE); - // We want java chars, not codepoints for the following. - final int separatorLength = separatorString.length(); - // TODO: should we check our saved separator against the actual contents of the text view? - final int deleteLength = cancelLength + separatorLength; - if (DebugFlags.DEBUG_ENABLED) { - if (mWordComposer.isComposingWord()) { - throw new RuntimeException("revertCommit, but we are composing a word"); - } - final CharSequence wordBeforeCursor = - mConnection.getTextBeforeCursor(deleteLength, 0).subSequence(0, cancelLength); - if (!TextUtils.equals(committedWord, wordBeforeCursor)) { - throw new RuntimeException("revertCommit check failed: we thought we were " - + "reverting \"" + committedWord - + "\", but before the cursor we found \"" + wordBeforeCursor + "\""); - } - } - mConnection.deleteTextBeforeCursor(deleteLength); - if (!TextUtils.isEmpty(committedWord)) { - unlearnWord(committedWordString, inputTransaction.mSettingsValues, - Constants.EVENT_REVERT); - } - final String stringToCommit = originallyTypedWord + - (usePhantomSpace ? "" : separatorString); - final SpannableString textToCommit = new SpannableString(stringToCommit); - if (committedWord instanceof SpannableString) { - final SpannableString committedWordWithSuggestionSpans = (SpannableString)committedWord; - final Object[] spans = committedWordWithSuggestionSpans.getSpans(0, - committedWord.length(), Object.class); - final int lastCharIndex = textToCommit.length() - 1; - // We will collect all suggestions in the following array. - final ArrayList<String> suggestions = new ArrayList<>(); - // First, add the committed word to the list of suggestions. - suggestions.add(committedWordString); - for (final Object span : spans) { - // If this is a suggestion span, we check that the word is not the committed word. - // That should mostly be the case. - // Given this, we add it to the list of suggestions, otherwise we discard it. - if (span instanceof SuggestionSpan) { - final SuggestionSpan suggestionSpan = (SuggestionSpan)span; - for (final String suggestion : suggestionSpan.getSuggestions()) { - if (!suggestion.equals(committedWordString)) { - suggestions.add(suggestion); - } - } - } else { - // If this is not a suggestion span, we just add it as is. - textToCommit.setSpan(span, 0 /* start */, lastCharIndex /* end */, - committedWordWithSuggestionSpans.getSpanFlags(span)); - } - } - // Add the suggestion list to the list of suggestions. - textToCommit.setSpan(new SuggestionSpan(mLatinIME /* context */, - inputTransaction.mSettingsValues.mLocale, - suggestions.toArray(new String[suggestions.size()]), 0 /* flags */, - null /* notificationTargetClass */), - 0 /* start */, lastCharIndex /* end */, 0 /* flags */); - } - - if (inputTransaction.mSettingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) { - mConnection.commitText(textToCommit, 1); - if (usePhantomSpace) { - mSpaceState = SpaceState.PHANTOM; - } - } else { - // For languages without spaces, we revert the typed string but the cursor is flush - // with the typed word, so we need to resume suggestions right away. - final int[] codePoints = StringUtils.toCodePointArray(stringToCommit); - mWordComposer.setComposingWord(codePoints, - mLatinIME.getCoordinatesForCurrentKeyboard(codePoints)); - setComposingTextInternal(textToCommit, 1); - } - // Don't restart suggestion yet. We'll restart if the user deletes the separator. - mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; - - // We have a separator between the word and the cursor: we should show predictions. - inputTransaction.setRequiresUpdateSuggestions(); - } - - /** - * Factor in auto-caps and manual caps and compute the current caps mode. - * @param settingsValues the current settings values. - * @param keyboardShiftMode the current shift mode of the keyboard. See - * KeyboardSwitcher#getKeyboardShiftMode() for possible values. - * @return the actual caps mode the keyboard is in right now. - */ - private int getActualCapsMode(final SettingsValues settingsValues, - final int keyboardShiftMode) { - if (keyboardShiftMode != WordComposer.CAPS_MODE_AUTO_SHIFTED) { - return keyboardShiftMode; - } - final int auto = getCurrentAutoCapsState(settingsValues); - if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) { - return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED; - } - if (0 != auto) { - return WordComposer.CAPS_MODE_AUTO_SHIFTED; - } - return WordComposer.CAPS_MODE_OFF; - } - - /** - * Gets the current auto-caps state, factoring in the space state. - * - * This method tries its best to do this in the most efficient possible manner. It avoids - * getting text from the editor if possible at all. - * This is called from the KeyboardSwitcher (through a trampoline in LatinIME) because it - * needs to know auto caps state to display the right layout. - * - * @param settingsValues the relevant settings values - * @return a caps mode from TextUtils.CAP_MODE_* or Constants.TextUtils.CAP_MODE_OFF. - */ - public int getCurrentAutoCapsState(final SettingsValues settingsValues) { - if (!settingsValues.mAutoCap) return Constants.TextUtils.CAP_MODE_OFF; - - final EditorInfo ei = getCurrentInputEditorInfo(); - if (ei == null) return Constants.TextUtils.CAP_MODE_OFF; - final int inputType = ei.inputType; - // Warning: this depends on mSpaceState, which may not be the most current value. If - // mSpaceState gets updated later, whoever called this may need to be told about it. - return mConnection.getCursorCapsMode(inputType, settingsValues.mSpacingAndPunctuations, - SpaceState.PHANTOM == mSpaceState); - } - - public int getCurrentRecapitalizeState() { - if (!mRecapitalizeStatus.isStarted() - || !mRecapitalizeStatus.isSetAt(mConnection.getExpectedSelectionStart(), - mConnection.getExpectedSelectionEnd())) { - // Not recapitalizing at the moment - return RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; - } - return mRecapitalizeStatus.getCurrentMode(); - } - - /** - * @return the editor info for the current editor - */ - private EditorInfo getCurrentInputEditorInfo() { - return mLatinIME.getCurrentInputEditorInfo(); - } - - /** - * Get n-gram context from the nth previous word before the cursor as context - * for the suggestion process. - * @param spacingAndPunctuations the current spacing and punctuations settings. - * @param nthPreviousWord reverse index of the word to get (1-indexed) - * @return the information of previous words - */ - public NgramContext getNgramContextFromNthPreviousWordForSuggestion( - final SpacingAndPunctuations spacingAndPunctuations, final int nthPreviousWord) { - if (spacingAndPunctuations.mCurrentLanguageHasSpaces) { - // If we are typing in a language with spaces we can just look up the previous - // word information from textview. - return mConnection.getNgramContextFromNthPreviousWord( - spacingAndPunctuations, nthPreviousWord); - } - if (LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord) { - return NgramContext.BEGINNING_OF_SENTENCE; - } - return new NgramContext(new NgramContext.WordInfo( - mLastComposedWord.mCommittedWord.toString())); - } - - /** - * Tests the passed word for resumability. - * - * We can resume suggestions on words whose first code point is a word code point (with some - * nuances: check the code for details). - * - * @param settings the current values of the settings. - * @param word the word to evaluate. - * @return whether it's fine to resume suggestions on this word. - */ - private static boolean isResumableWord(final SettingsValues settings, final String word) { - final int firstCodePoint = word.codePointAt(0); - return settings.isWordCodePoint(firstCodePoint) - && Constants.CODE_SINGLE_QUOTE != firstCodePoint - && Constants.CODE_DASH != firstCodePoint; - } - - /** - * @param actionId the action to perform - */ - private void performEditorAction(final int actionId) { - mConnection.performEditorAction(actionId); - } - - /** - * Perform the processing specific to inputting TLDs. - * - * Some keys input a TLD (specifically, the ".com" key) and this warrants some specific - * processing. First, if this is a TLD, we ignore PHANTOM spaces -- this is done by type - * of character in onCodeInput, but since this gets inputted as a whole string we need to - * do it here specifically. Then, if the last character before the cursor is a period, then - * we cut the dot at the start of ".com". This is because humans tend to type "www.google." - * and then press the ".com" key and instinctively don't expect to get "www.google..com". - * - * @param text the raw text supplied to onTextInput - * @return the text to actually send to the editor - */ - private String performSpecificTldProcessingOnTextInput(final String text) { - if (text.length() <= 1 || text.charAt(0) != Constants.CODE_PERIOD - || !Character.isLetter(text.charAt(1))) { - // Not a tld: do nothing. - return text; - } - // We have a TLD (or something that looks like this): make sure we don't add - // a space even if currently in phantom mode. - mSpaceState = SpaceState.NONE; - final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); - // If no code point, #getCodePointBeforeCursor returns NOT_A_CODE_POINT. - if (Constants.CODE_PERIOD == codePointBeforeCursor) { - return text.substring(1); - } - return text; - } - - /** - * Handle a press on the settings key. - */ - private void onSettingsKeyPressed() { - mLatinIME.displaySettingsDialog(); - } - - /** - * Resets the whole input state to the starting state. - * - * This will clear the composing word, reset the last composed word, clear the suggestion - * strip and tell the input connection about it so that it can refresh its caches. - * - * @param newSelStart the new selection start, in java characters. - * @param newSelEnd the new selection end, in java characters. - * @param clearSuggestionStrip whether this method should clear the suggestion strip. - */ - // TODO: how is this different from startInput ?! - private void resetEntireInputState(final int newSelStart, final int newSelEnd, - final boolean clearSuggestionStrip) { - final boolean shouldFinishComposition = mWordComposer.isComposingWord(); - resetComposingState(true /* alsoResetLastComposedWord */); - if (clearSuggestionStrip) { - mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); - } - mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd, - shouldFinishComposition); - } - - /** - * Resets only the composing state. - * - * Compare #resetEntireInputState, which also clears the suggestion strip and resets the - * input connection caches. This only deals with the composing state. - * - * @param alsoResetLastComposedWord whether to also reset the last composed word. - */ - private void resetComposingState(final boolean alsoResetLastComposedWord) { - mWordComposer.reset(); - if (alsoResetLastComposedWord) { - mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; - } - } - - /** - * Make a {@link com.android.inputmethod.latin.SuggestedWords} object containing a typed word - * and obsolete suggestions. - * See {@link com.android.inputmethod.latin.SuggestedWords#getTypedWordAndPreviousSuggestions( - * SuggestedWordInfo, com.android.inputmethod.latin.SuggestedWords)}. - * @param typedWordInfo The typed word as a SuggestedWordInfo. - * @param previousSuggestedWords The previously suggested words. - * @return Obsolete suggestions with the newly typed word. - */ - static SuggestedWords retrieveOlderSuggestions(final SuggestedWordInfo typedWordInfo, - final SuggestedWords previousSuggestedWords) { - final SuggestedWords oldSuggestedWords = previousSuggestedWords.isPunctuationSuggestions() - ? SuggestedWords.getEmptyInstance() : previousSuggestedWords; - final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions = - SuggestedWords.getTypedWordAndPreviousSuggestions(typedWordInfo, oldSuggestedWords); - return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */, - typedWordInfo, false /* typedWordValid */, false /* hasAutoCorrectionCandidate */, - true /* isObsoleteSuggestions */, oldSuggestedWords.mInputStyle, - SuggestedWords.NOT_A_SEQUENCE_NUMBER); - } - - /** - * @return the {@link Locale} of the {@link #mDictionaryFacilitator} if available. Otherwise - * {@link Locale#ROOT}. - */ - @Nonnull - private Locale getDictionaryFacilitatorLocale() { - return mDictionaryFacilitator != null ? mDictionaryFacilitator.getLocale() : Locale.ROOT; - } - - /** - * Gets a chunk of text with or the auto-correction indicator underline span as appropriate. - * - * This method looks at the old state of the auto-correction indicator to put or not put - * the underline span as appropriate. It is important to note that this does not correspond - * exactly to whether this word will be auto-corrected to or not: what's important here is - * to keep the same indication as before. - * When we add a new code point to a composing word, we don't know yet if we are going to - * auto-correct it until the suggestions are computed. But in the mean time, we still need - * to display the character and to extend the previous underline. To avoid any flickering, - * the underline should keep the same color it used to have, even if that's not ultimately - * the correct color for this new word. When the suggestions are finished evaluating, we - * will call this method again to fix the color of the underline. - * - * @param text the text on which to maybe apply the span. - * @return the same text, with the auto-correction underline span if that's appropriate. - */ - // TODO: Shouldn't this go in some *Utils class instead? - private CharSequence getTextWithUnderline(final String text) { - // TODO: Locale should be determined based on context and the text given. - return mIsAutoCorrectionIndicatorOn - ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline( - mLatinIME, text, getDictionaryFacilitatorLocale()) - : text; - } - - /** - * Sends a DOWN key event followed by an UP key event to the editor. - * - * If possible at all, avoid using this method. It causes all sorts of race conditions with - * the text view because it goes through a different, asynchronous binder. Also, batch edits - * are ignored for key events. Use the normal software input methods instead. - * - * @param keyCode the key code to send inside the key event. - */ - private void sendDownUpKeyEvent(final int keyCode) { - final long eventTime = SystemClock.uptimeMillis(); - mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime, - KeyEvent.ACTION_DOWN, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, - KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE)); - mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, - KeyEvent.ACTION_UP, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, - KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE)); - } - - /** - * Sends a code point to the editor, using the most appropriate method. - * - * Normally we send code points with commitText, but there are some cases (where backward - * compatibility is a concern for example) where we want to use deprecated methods. - * - * @param settingsValues the current values of the settings. - * @param codePoint the code point to send. - */ - // TODO: replace these two parameters with an InputTransaction - private void sendKeyCodePoint(final SettingsValues settingsValues, final int codePoint) { - // TODO: Remove this special handling of digit letters. - // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}. - if (codePoint >= '0' && codePoint <= '9') { - sendDownUpKeyEvent(codePoint - '0' + KeyEvent.KEYCODE_0); - return; - } - - // TODO: we should do this also when the editor has TYPE_NULL - if (Constants.CODE_ENTER == codePoint && settingsValues.isBeforeJellyBean()) { - // Backward compatibility mode. Before Jelly bean, the keyboard would simulate - // a hardware keyboard event on pressing enter or delete. This is bad for many - // reasons (there are race conditions with commits) but some applications are - // relying on this behavior so we continue to support it for older apps. - sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER); - } else { - mConnection.commitText(StringUtils.newSingleCodePointString(codePoint), 1); - } - } - - /** - * Insert an automatic space, if the options allow it. - * - * This checks the options and the text before the cursor are appropriate before inserting - * an automatic space. - * - * @param settingsValues the current values of the settings. - */ - private void insertAutomaticSpaceIfOptionsAndTextAllow(final SettingsValues settingsValues) { - if (settingsValues.shouldInsertSpacesAutomatically() - && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces - && !mConnection.textBeforeCursorLooksLikeURL()) { - sendKeyCodePoint(settingsValues, Constants.CODE_SPACE); - } - } - - /** - * Do the final processing after a batch input has ended. This commits the word to the editor. - * @param settingsValues the current values of the settings. - * @param suggestedWords suggestedWords to use. - */ - public void onUpdateTailBatchInputCompleted(final SettingsValues settingsValues, - final SuggestedWords suggestedWords, final KeyboardSwitcher keyboardSwitcher) { - final String batchInputText = suggestedWords.isEmpty() ? null : suggestedWords.getWord(0); - if (TextUtils.isEmpty(batchInputText)) { - return; - } - mConnection.beginBatchEdit(); - if (SpaceState.PHANTOM == mSpaceState) { - insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues); - } - mWordComposer.setBatchInputWord(batchInputText); - setComposingTextInternal(batchInputText, 1); - mConnection.endBatchEdit(); - // Space state must be updated before calling updateShiftState - mSpaceState = SpaceState.PHANTOM; - keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(settingsValues), - getCurrentRecapitalizeState()); - } - - /** - * Commit the typed string to the editor. - * - * This is typically called when we should commit the currently composing word without applying - * auto-correction to it. Typically, we come here upon pressing a separator when the keyboard - * is configured to not do auto-correction at all (because of the settings or the properties of - * the editor). In this case, `separatorString' is set to the separator that was pressed. - * We also come here in a variety of cases with external user action. For example, when the - * cursor is moved while there is a composition, or when the keyboard is closed, or when the - * user presses the Send button for an SMS, we don't auto-correct as that would be unexpected. - * In this case, `separatorString' is set to NOT_A_SEPARATOR. - * - * @param settingsValues the current values of the settings. - * @param separatorString the separator that's causing the commit, or NOT_A_SEPARATOR if none. - */ - public void commitTyped(final SettingsValues settingsValues, final String separatorString) { - if (!mWordComposer.isComposingWord()) return; - final String typedWord = mWordComposer.getTypedWord(); - if (typedWord.length() > 0) { - final boolean isBatchMode = mWordComposer.isBatchMode(); - commitChosenWord(settingsValues, typedWord, - LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString); - StatsUtils.onWordCommitUserTyped(typedWord, isBatchMode); - } - } - - /** - * Commit the current auto-correction. - * - * This will commit the best guess of the keyboard regarding what the user meant by typing - * the currently composing word. The IME computes suggestions and assigns a confidence score - * to each of them; when it's confident enough in one suggestion, it replaces the typed string - * by this suggestion at commit time. When it's not confident enough, or when it has no - * suggestions, or when the settings or environment does not allow for auto-correction, then - * this method just commits the typed string. - * Note that if suggestions are currently being computed in the background, this method will - * block until the computation returns. This is necessary for consistency (it would be very - * strange if pressing space would commit a different word depending on how fast you press). - * - * @param settingsValues the current value of the settings. - * @param separator the separator that's causing the commit to happen. - */ - private void commitCurrentAutoCorrection(final SettingsValues settingsValues, - final String separator, final LatinIME.UIHandler handler) { - // Complete any pending suggestions query first - if (handler.hasPendingUpdateSuggestions()) { - handler.cancelUpdateSuggestionStrip(); - // To know the input style here, we should retrieve the in-flight "update suggestions" - // message and read its arg1 member here. However, the Handler class does not let - // us retrieve this message, so we can't do that. But in fact, we notice that - // we only ever come here when the input style was typing. In the case of batch - // input, we update the suggestions synchronously when the tail batch comes. Likewise - // for application-specified completions. As for recorrections, we never auto-correct, - // so we don't come here either. Hence, the input style is necessarily - // INPUT_STYLE_TYPING. - performUpdateSuggestionStripSync(settingsValues, SuggestedWords.INPUT_STYLE_TYPING); - } - final SuggestedWordInfo autoCorrectionOrNull = mWordComposer.getAutoCorrectionOrNull(); - final String typedWord = mWordComposer.getTypedWord(); - final String stringToCommit = (autoCorrectionOrNull != null) - ? autoCorrectionOrNull.mWord : typedWord; - if (stringToCommit != null) { - if (TextUtils.isEmpty(typedWord)) { - throw new RuntimeException("We have an auto-correction but the typed word " - + "is empty? Impossible! I must commit suicide."); - } - final boolean isBatchMode = mWordComposer.isBatchMode(); - commitChosenWord(settingsValues, stringToCommit, - LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator); - if (!typedWord.equals(stringToCommit)) { - // This will make the correction flash for a short while as a visual clue - // to the user that auto-correction happened. It has no other effect; in particular - // note that this won't affect the text inside the text field AT ALL: it only makes - // the segment of text starting at the supplied index and running for the length - // of the auto-correction flash. At this moment, the "typedWord" argument is - // ignored by TextView. - mConnection.commitCorrection(new CorrectionInfo( - mConnection.getExpectedSelectionEnd() - stringToCommit.length(), - typedWord, stringToCommit)); - String prevWordsContext = (autoCorrectionOrNull != null) - ? autoCorrectionOrNull.mPrevWordsContext - : ""; - StatsUtils.onAutoCorrection(typedWord, stringToCommit, isBatchMode, - mDictionaryFacilitator, prevWordsContext); - StatsUtils.onWordCommitAutoCorrect(stringToCommit, isBatchMode); - } else { - StatsUtils.onWordCommitUserTyped(stringToCommit, isBatchMode); - } - } - } - - /** - * Commits the chosen word to the text field and saves it for later retrieval. - * - * @param settingsValues the current values of the settings. - * @param chosenWord the word we want to commit. - * @param commitType the type of the commit, as one of LastComposedWord.COMMIT_TYPE_* - * @param separatorString the separator that's causing the commit, or NOT_A_SEPARATOR if none. - */ - private void commitChosenWord(final SettingsValues settingsValues, final String chosenWord, - final int commitType, final String separatorString) { - long startTimeMillis = 0; - if (DebugFlags.DEBUG_ENABLED) { - startTimeMillis = System.currentTimeMillis(); - Log.d(TAG, "commitChosenWord() : [" + chosenWord + "]"); - } - final SuggestedWords suggestedWords = mSuggestedWords; - // TODO: Locale should be determined based on context and the text given. - final Locale locale = getDictionaryFacilitatorLocale(); - final CharSequence chosenWordWithSuggestions = chosenWord; - // b/21926256 - // SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord, - // suggestedWords, locale); - if (DebugFlags.DEBUG_ENABLED) { - long runTimeMillis = System.currentTimeMillis() - startTimeMillis; - Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run " - + "SuggestionSpanUtils.getTextWithSuggestionSpan()"); - startTimeMillis = System.currentTimeMillis(); - } - // When we are composing word, get n-gram context from the 2nd previous word because the - // 1st previous word is the word to be committed. Otherwise get n-gram context from the 1st - // previous word. - final NgramContext ngramContext = mConnection.getNgramContextFromNthPreviousWord( - settingsValues.mSpacingAndPunctuations, mWordComposer.isComposingWord() ? 2 : 1); - if (DebugFlags.DEBUG_ENABLED) { - long runTimeMillis = System.currentTimeMillis() - startTimeMillis; - Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run " - + "Connection.getNgramContextFromNthPreviousWord()"); - Log.d(TAG, "commitChosenWord() : NgramContext = " + ngramContext); - startTimeMillis = System.currentTimeMillis(); - } - mConnection.commitText(chosenWordWithSuggestions, 1); - if (DebugFlags.DEBUG_ENABLED) { - long runTimeMillis = System.currentTimeMillis() - startTimeMillis; - Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run " - + "Connection.commitText"); - startTimeMillis = System.currentTimeMillis(); - } - // Add the word to the user history dictionary - performAdditionToUserHistoryDictionary(settingsValues, chosenWord, ngramContext); - if (DebugFlags.DEBUG_ENABLED) { - long runTimeMillis = System.currentTimeMillis() - startTimeMillis; - Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run " - + "performAdditionToUserHistoryDictionary()"); - startTimeMillis = System.currentTimeMillis(); - } - // TODO: figure out here if this is an auto-correct or if the best word is actually - // what user typed. Note: currently this is done much later in - // LastComposedWord#didCommitTypedWord by string equality of the remembered - // strings. - mLastComposedWord = mWordComposer.commitWord(commitType, - chosenWordWithSuggestions, separatorString, ngramContext); - if (DebugFlags.DEBUG_ENABLED) { - long runTimeMillis = System.currentTimeMillis() - startTimeMillis; - Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run " - + "WordComposer.commitWord()"); - startTimeMillis = System.currentTimeMillis(); - } - } - - /** - * Retry resetting caches in the rich input connection. - * - * When the editor can't be accessed we can't reset the caches, so we schedule a retry. - * This method handles the retry, and re-schedules a new retry if we still can't access. - * We only retry up to 5 times before giving up. - * - * @param tryResumeSuggestions Whether we should resume suggestions or not. - * @param remainingTries How many times we may try again before giving up. - * @return whether true if the caches were successfully reset, false otherwise. - */ - public boolean retryResetCachesAndReturnSuccess(final boolean tryResumeSuggestions, - final int remainingTries, final LatinIME.UIHandler handler) { - final boolean shouldFinishComposition = mConnection.hasSelection() - || !mConnection.isCursorPositionKnown(); - if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess( - mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd(), - shouldFinishComposition)) { - if (0 < remainingTries) { - handler.postResetCaches(tryResumeSuggestions, remainingTries - 1); - return false; - } - // If remainingTries is 0, we should stop waiting for new tries, however we'll still - // return true as we need to perform other tasks (for example, loading the keyboard). - } - mConnection.tryFixLyingCursorPosition(); - if (tryResumeSuggestions) { - handler.postResumeSuggestions(true /* shouldDelay */); - } - return true; - } - - public void getSuggestedWords(final SettingsValues settingsValues, - final Keyboard keyboard, final int keyboardShiftMode, final int inputStyle, - final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { - mWordComposer.adviseCapitalizedModeBeforeFetchingSuggestions( - getActualCapsMode(settingsValues, keyboardShiftMode)); - mSuggest.getSuggestedWords(mWordComposer, - getNgramContextFromNthPreviousWordForSuggestion( - settingsValues.mSpacingAndPunctuations, - // Get the word on which we should search the bigrams. If we are composing - // a word, it's whatever is *before* the half-committed word in the buffer, - // hence 2; if we aren't, we should just skip whitespace if any, so 1. - mWordComposer.isComposingWord() ? 2 : 1), - keyboard, - new SettingsValuesForSuggestion(settingsValues.mBlockPotentiallyOffensive), - settingsValues.mAutoCorrectionEnabledPerUserSettings, - inputStyle, sequenceNumber, callback); - } - - /** - * Used as an injection point for each call of - * {@link RichInputConnection#setComposingText(CharSequence, int)}. - * - * <p>Currently using this method is optional and you can still directly call - * {@link RichInputConnection#setComposingText(CharSequence, int)}, but it is recommended to - * use this method whenever possible.<p> - * <p>TODO: Should we move this mechanism to {@link RichInputConnection}?</p> - * - * @param newComposingText the composing text to be set - * @param newCursorPosition the new cursor position - */ - private void setComposingTextInternal(final CharSequence newComposingText, - final int newCursorPosition) { - setComposingTextInternalWithBackgroundColor(newComposingText, newCursorPosition, - Color.TRANSPARENT, newComposingText.length()); - } - - /** - * Equivalent to {@link #setComposingTextInternal(CharSequence, int)} except that this method - * allows to set {@link BackgroundColorSpan} to the composing text with the given color. - * - * <p>TODO: Currently the background color is exclusive with the black underline, which is - * automatically added by the framework. We need to change the framework if we need to have both - * of them at the same time.</p> - * <p>TODO: Should we move this method to {@link RichInputConnection}?</p> - * - * @param newComposingText the composing text to be set - * @param newCursorPosition the new cursor position - * @param backgroundColor the background color to be set to the composing text. Set - * {@link Color#TRANSPARENT} to disable the background color. - * @param coloredTextLength the length of text, in Java chars, which should be rendered with - * the given background color. - */ - private void setComposingTextInternalWithBackgroundColor(final CharSequence newComposingText, - final int newCursorPosition, final int backgroundColor, final int coloredTextLength) { - final CharSequence composingTextToBeSet; - if (backgroundColor == Color.TRANSPARENT) { - composingTextToBeSet = newComposingText; - } else { - final SpannableString spannable = new SpannableString(newComposingText); - final BackgroundColorSpan backgroundColorSpan = - new BackgroundColorSpan(backgroundColor); - final int spanLength = Math.min(coloredTextLength, spannable.length()); - spannable.setSpan(backgroundColorSpan, 0, spanLength, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); - composingTextToBeSet = spannable; - } - mConnection.setComposingText(composingTextToBeSet, newCursorPosition); - } - - /** - * Gets an object allowing private IME commands to be sent to the - * underlying editor. - * @return An object for sending private commands to the underlying editor. - */ - public PrivateCommandPerformer getPrivateCommandPerformer() { - return mConnection; - } - - /** - * Gets the expected index of the first char of the composing span within the editor's text. - * Returns a negative value in case there appears to be no valid composing span. - * - * @see #getComposingLength() - * @see RichInputConnection#hasSelection() - * @see RichInputConnection#isCursorPositionKnown() - * @see RichInputConnection#getExpectedSelectionStart() - * @see RichInputConnection#getExpectedSelectionEnd() - * @return The expected index in Java chars of the first char of the composing span. - */ - // TODO: try and see if we can get rid of this method. Ideally the users of this class should - // never need to know this. - public int getComposingStart() { - if (!mConnection.isCursorPositionKnown() || mConnection.hasSelection()) { - return -1; - } - return mConnection.getExpectedSelectionStart() - mWordComposer.size(); - } - - /** - * Gets the expected length in Java chars of the composing span. - * May be 0 if there is no valid composing span. - * @see #getComposingStart() - * @return The expected length of the composing span. - */ - // TODO: try and see if we can get rid of this method. Ideally the users of this class should - // never need to know this. - public int getComposingLength() { - return mWordComposer.size(); - } -} diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java deleted file mode 100644 index ddc4ad99c..000000000 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.inputlogic; - -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Message; - -import com.android.inputmethod.compat.LooperCompatUtils; -import com.android.inputmethod.latin.LatinIME; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; -import com.android.inputmethod.latin.common.InputPointers; - -/** - * A helper to manage deferred tasks for the input logic. - */ -class InputLogicHandler implements Handler.Callback { - final Handler mNonUIThreadHandler; - // TODO: remove this reference. - final LatinIME mLatinIME; - final InputLogic mInputLogic; - private final Object mLock = new Object(); - private boolean mInBatchInput; // synchronized using {@link #mLock}. - - private static final int MSG_GET_SUGGESTED_WORDS = 1; - - // A handler that never does anything. This is used for cases where events come before anything - // is initialized, though probably only the monkey can actually do this. - public static final InputLogicHandler NULL_HANDLER = new InputLogicHandler() { - @Override - public void reset() {} - @Override - public boolean handleMessage(final Message msg) { return true; } - @Override - public void onStartBatchInput() {} - @Override - public void onUpdateBatchInput(final InputPointers batchPointers, - final int sequenceNumber) {} - @Override - public void onCancelBatchInput() {} - @Override - public void updateTailBatchInput(final InputPointers batchPointers, - final int sequenceNumber) {} - @Override - public void getSuggestedWords(final int sessionId, final int sequenceNumber, - final OnGetSuggestedWordsCallback callback) {} - }; - - InputLogicHandler() { - mNonUIThreadHandler = null; - mLatinIME = null; - mInputLogic = null; - } - - public InputLogicHandler(final LatinIME latinIME, final InputLogic inputLogic) { - final HandlerThread handlerThread = new HandlerThread( - InputLogicHandler.class.getSimpleName()); - handlerThread.start(); - mNonUIThreadHandler = new Handler(handlerThread.getLooper(), this); - mLatinIME = latinIME; - mInputLogic = inputLogic; - } - - public void reset() { - mNonUIThreadHandler.removeCallbacksAndMessages(null); - } - - // In unit tests, we create several instances of LatinIME, which results in several instances - // of InputLogicHandler. To avoid these handlers lingering, we call this. - public void destroy() { - LooperCompatUtils.quitSafely(mNonUIThreadHandler.getLooper()); - } - - /** - * Handle a message. - * @see android.os.Handler.Callback#handleMessage(android.os.Message) - */ - // Called on the Non-UI handler thread by the Handler code. - @Override - public boolean handleMessage(final Message msg) { - switch (msg.what) { - case MSG_GET_SUGGESTED_WORDS: - mLatinIME.getSuggestedWords(msg.arg1 /* inputStyle */, - msg.arg2 /* sequenceNumber */, (OnGetSuggestedWordsCallback) msg.obj); - break; - } - return true; - } - - // Called on the UI thread by InputLogic. - public void onStartBatchInput() { - synchronized (mLock) { - mInBatchInput = true; - } - } - - public boolean isInBatchInput() { - return mInBatchInput; - } - - /** - * Fetch suggestions corresponding to an update of a batch input. - * @param batchPointers the updated pointers, including the part that was passed last time. - * @param sequenceNumber the sequence number associated with this batch input. - * @param isTailBatchInput true if this is the end of a batch input, false if it's an update. - */ - // This method can be called from any thread and will see to it that the correct threads - // are used for parts that require it. This method will send a message to the Non-UI handler - // thread to pull suggestions, and get the inlined callback to get called on the Non-UI - // handler thread. If this is the end of a batch input, the callback will then proceed to - // send a message to the UI handler in LatinIME so that showing suggestions can be done on - // the UI thread. - private void updateBatchInput(final InputPointers batchPointers, - final int sequenceNumber, final boolean isTailBatchInput) { - synchronized (mLock) { - if (!mInBatchInput) { - // Batch input has ended or canceled while the message was being delivered. - return; - } - mInputLogic.mWordComposer.setBatchInputPointers(batchPointers); - final OnGetSuggestedWordsCallback callback = new OnGetSuggestedWordsCallback() { - @Override - public void onGetSuggestedWords(final SuggestedWords suggestedWords) { - showGestureSuggestionsWithPreviewVisuals(suggestedWords, isTailBatchInput); - } - }; - getSuggestedWords(isTailBatchInput ? SuggestedWords.INPUT_STYLE_TAIL_BATCH - : SuggestedWords.INPUT_STYLE_UPDATE_BATCH, sequenceNumber, callback); - } - } - - void showGestureSuggestionsWithPreviewVisuals(final SuggestedWords suggestedWordsForBatchInput, - final boolean isTailBatchInput) { - final SuggestedWords suggestedWordsToShowSuggestions; - // We're now inside the callback. This always runs on the Non-UI thread, - // no matter what thread updateBatchInput was originally called on. - if (suggestedWordsForBatchInput.isEmpty()) { - // Use old suggestions if we don't have any new ones. - // Previous suggestions are found in InputLogic#mSuggestedWords. - // Since these are the most recent ones and we just recomputed - // new ones to update them, then the previous ones are there. - suggestedWordsToShowSuggestions = mInputLogic.mSuggestedWords; - } else { - suggestedWordsToShowSuggestions = suggestedWordsForBatchInput; - } - mLatinIME.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWordsToShowSuggestions, - isTailBatchInput /* dismissGestureFloatingPreviewText */); - if (isTailBatchInput) { - mInBatchInput = false; - // The following call schedules onEndBatchInputInternal - // to be called on the UI thread. - mLatinIME.mHandler.showTailBatchInputResult(suggestedWordsToShowSuggestions); - } - } - - /** - * Update a batch input. - * - * This fetches suggestions and updates the suggestion strip and the floating text preview. - * - * @param batchPointers the updated batch pointers. - * @param sequenceNumber the sequence number associated with this batch input. - */ - // Called on the UI thread by InputLogic. - public void onUpdateBatchInput(final InputPointers batchPointers, - final int sequenceNumber) { - updateBatchInput(batchPointers, sequenceNumber, false /* isTailBatchInput */); - } - - /** - * Cancel a batch input. - * - * Note that as opposed to updateTailBatchInput, we do the UI side of this immediately on the - * same thread, rather than get this to call a method in LatinIME. This is because - * canceling a batch input does not necessitate the long operation of pulling suggestions. - */ - // Called on the UI thread by InputLogic. - public void onCancelBatchInput() { - synchronized (mLock) { - mInBatchInput = false; - } - } - - /** - * Trigger an update for a tail batch input. - * - * A tail batch input is the last update for a gesture, the one that is triggered after the - * user lifts their finger. This method schedules fetching suggestions on the non-UI thread, - * then when the suggestions are computed it comes back on the UI thread to update the - * suggestion strip, commit the first suggestion, and dismiss the floating text preview. - * - * @param batchPointers the updated batch pointers. - * @param sequenceNumber the sequence number associated with this batch input. - */ - // Called on the UI thread by InputLogic. - public void updateTailBatchInput(final InputPointers batchPointers, - final int sequenceNumber) { - updateBatchInput(batchPointers, sequenceNumber, true /* isTailBatchInput */); - } - - public void getSuggestedWords(final int inputStyle, final int sequenceNumber, - final OnGetSuggestedWordsCallback callback) { - mNonUIThreadHandler.obtainMessage( - MSG_GET_SUGGESTED_WORDS, inputStyle, sequenceNumber, callback).sendToTarget(); - } -} diff --git a/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java b/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java deleted file mode 100644 index 42eaa9c82..000000000 --- a/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.inputlogic; - -import android.os.Bundle; - -/** - * Provides an interface matching - * {@link android.view.inputmethod.InputConnection#performPrivateCommand(String,Bundle)}. - */ -public interface PrivateCommandPerformer { - /** - * API to send private commands from an input method to its connected - * editor. This can be used to provide domain-specific features that are - * only known between certain input methods and their clients. - * - * @param action Name of the command to be performed. This must be a scoped - * name, i.e. prefixed with a package name you own, so that - * different developers will not create conflicting commands. - * @param data Any data to include with the command. - * @return true if the command was sent (regardless of whether the - * associated editor understood it), false if the input connection is no - * longer valid. - */ - boolean performPrivateCommand(String action, Bundle data); -} diff --git a/java/src/com/android/inputmethod/latin/inputlogic/SpaceState.java b/java/src/com/android/inputmethod/latin/inputlogic/SpaceState.java deleted file mode 100644 index ce80c0016..000000000 --- a/java/src/com/android/inputmethod/latin/inputlogic/SpaceState.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.inputlogic; - -/** - * Class for managing space states. - * - * At any given time, the input logic is in one of five possible space states. Depending on the - * current space state, some behavior will change; the prime example of this is the PHANTOM state, - * in which any subsequent letter input will input a space before the letter. Read on the - * description inside this class for each of the space states. - */ -public class SpaceState { - // None: the state where all the keyboard behavior is the most "standard" and no automatic - // input is added or removed. In this state, all self-inserting keys only insert themselves, - // and backspace removes one character. - public static final int NONE = 0; - // Double space: the state where the user pressed space twice quickly, which LatinIME - // resolved as period-space. In this state, pressing backspace will undo the - // double-space-to-period insertion: it will replace ". " with " ". - public static final int DOUBLE = 1; - // Swap punctuation: the state where a weak space and a punctuation from the suggestion strip - // have just been swapped. In this state, pressing backspace will undo the swap: the - // characters will be swapped back back, and the space state will go to WEAK. - public static final int SWAP_PUNCTUATION = 2; - // Weak space: a space that should be swapped only by suggestion strip punctuation. Weak - // spaces happen when the user presses space, accepting the current suggestion (whether - // it's an auto-correction or not). In this state, pressing a punctuation from the suggestion - // strip inserts it before the space (while it inserts it after the space in the NONE state). - public static final int WEAK = 3; - // Phantom space: a not-yet-inserted space that should get inserted on the next input, - // character provided it's not a separator. If it's a separator, the phantom space is dropped. - // Phantom spaces happen when a user chooses a word from the suggestion strip. In this state, - // non-separators insert a space before they get inserted. - public static final int PHANTOM = 4; - - private SpaceState() { - // This class is not publicly instantiable. - } -} diff --git a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java deleted file mode 100644 index 4d253b0cb..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.makedict; - -import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions; -import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Class representing dictionary header. - */ -public final class DictionaryHeader { - public final int mBodyOffset; - @Nonnull - public final DictionaryOptions mDictionaryOptions; - @Nonnull - public final FormatOptions mFormatOptions; - @Nonnull - public final String mLocaleString; - @Nonnull - public final String mVersionString; - @Nonnull - public final String mIdString; - - // Note that these are corresponding definitions in native code in latinime::HeaderPolicy - // and latinime::HeaderReadWriteUtils. - // TODO: Standardize the key names and bump up the format version, taking care not to - // break format version 2 dictionaries. - public static final String DICTIONARY_VERSION_KEY = "version"; - public static final String DICTIONARY_LOCALE_KEY = "locale"; - public static final String DICTIONARY_ID_KEY = "dictionary"; - public static final String DICTIONARY_DESCRIPTION_KEY = "description"; - public static final String DICTIONARY_DATE_KEY = "date"; - public static final String HAS_HISTORICAL_INFO_KEY = "HAS_HISTORICAL_INFO"; - public static final String USES_FORGETTING_CURVE_KEY = "USES_FORGETTING_CURVE"; - public static final String FORGETTING_CURVE_PROBABILITY_VALUES_TABLE_ID_KEY = - "FORGETTING_CURVE_PROBABILITY_VALUES_TABLE_ID"; - public static final String MAX_UNIGRAM_COUNT_KEY = "MAX_UNIGRAM_ENTRY_COUNT"; - public static final String MAX_BIGRAM_COUNT_KEY = "MAX_BIGRAM_ENTRY_COUNT"; - public static final String MAX_TRIGRAM_COUNT_KEY = "MAX_TRIGRAM_ENTRY_COUNT"; - public static final String ATTRIBUTE_VALUE_TRUE = "1"; - public static final String CODE_POINT_TABLE_KEY = "codePointTable"; - - public DictionaryHeader(final int headerSize, - @Nonnull final DictionaryOptions dictionaryOptions, - @Nonnull final FormatOptions formatOptions) throws UnsupportedFormatException { - mDictionaryOptions = dictionaryOptions; - mFormatOptions = formatOptions; - mBodyOffset = formatOptions.mVersion < FormatSpec.VERSION4 ? headerSize : 0; - final String localeString = dictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_KEY); - if (null == localeString) { - throw new UnsupportedFormatException("Cannot create a FileHeader without a locale"); - } - final String versionString = dictionaryOptions.mAttributes.get(DICTIONARY_VERSION_KEY); - if (null == versionString) { - throw new UnsupportedFormatException( - "Cannot create a FileHeader without a version"); - } - final String idString = dictionaryOptions.mAttributes.get(DICTIONARY_ID_KEY); - if (null == idString) { - throw new UnsupportedFormatException("Cannot create a FileHeader without an ID"); - } - mLocaleString = localeString; - mVersionString = versionString; - mIdString = idString; - } - - // Helper method to get the description - @Nullable - public String getDescription() { - // TODO: Right now each dictionary file comes with a description in its own language. - // It will display as is no matter the device's locale. It should be internationalized. - return mDictionaryOptions.mAttributes.get(DICTIONARY_DESCRIPTION_KEY); - } -}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java deleted file mode 100644 index 288261bf0..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.makedict; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; - -import java.util.Date; -import java.util.HashMap; - -/** - * Dictionary File Format Specification. - */ -public final class FormatSpec { - - /* - * File header layout is as follows: - * - * v | - * e | MAGIC_NUMBER + version of the file format, 2 bytes. - * r | - * sion - * - * o | - * p | not used, 2 bytes. - * o | - * nflags - * - * h | - * e | size of the file header, 4bytes - * a | including the size of the magic number, the option flags and the header size - * d | - * ersize - * - * attributes list - * - * attributes list is: - * <key> = | string of characters at the char format described below, with the terminator used - * | to signal the end of the string. - * <value> = | string of characters at the char format described below, with the terminator used - * | to signal the end of the string. - * if the size of already read < headersize, goto key. - * - */ - - /* - * Node array (FusionDictionary.PtNodeArray) layout is as follows: - * - * n | - * o | the number of PtNodes, 1 or 2 bytes. - * d | 1 byte = bbbbbbbb match - * e | case 1xxxxxxx => xxxxxxx << 8 + next byte - * c | otherwise => bbbbbbbb - * o | - * unt - * - * n | - * o | sequence of PtNodes, - * d | the layout of each PtNode is described below. - * e | - * s - * - * f | - * o | forward link address, 3byte - * r | 1 byte = bbbbbbbb match - * w | case 1xxxxxxx => -((xxxxxxx << 16) + (next byte << 8) + next byte) - * a | otherwise => (xxxxxxx << 16) + (next byte << 8) + next byte - * r | - * dlinkaddress - */ - - /* Node (FusionDictionary.PtNode) layout is as follows: - * | CHILDREN_ADDRESS_TYPE 2 bits, 11 : FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES - * | 10 : FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES - * f | 01 : FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE - * l | 00 : FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS - * a | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS - * g | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL - * s | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS - * | has bigrams ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_BIGRAMS - * | is not a word ? 1 bit, 1 = yes, 0 = no : FLAG_IS_NOT_A_WORD - * | is possibly offensive ? 1 bit, 1 = yes, 0 = no : FLAG_IS_POSSIBLY_OFFENSIVE - * - * c | IF FLAG_HAS_MULTIPLE_CHARS - * h | char, char, char, char n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers - * a | end 1 byte, = 0 - * r | ELSE - * s | char 1 or 3 bytes - * | END - * - * f | - * r | IF FLAG_IS_TERMINAL - * e | frequency 1 byte - * q | - * - * c | - * h | children address, CHILDREN_ADDRESS_TYPE bytes - * i | This address is relative to the position of this field. - * l | - * drenaddress - * - * | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS - * | shortcut string list - * | IF FLAG_IS_TERMINAL && FLAG_HAS_BIGRAMS - * | bigrams address list - * - * Char format is: - * 1 byte = bbbbbbbb match - * case 000xxxxx: xxxxx << 16 + next byte << 8 + next byte - * else: if 00011111 (= 0x1F) : this is the terminator. This is a relevant choice because - * unicode code points range from 0 to 0x10FFFF, so any 3-byte value starting with - * 00011111 would be outside unicode. - * else: iso-latin-1 code - * This allows for the whole unicode range to be encoded, including chars outside of - * the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control - * characters which should never happen anyway (and still work, but take 3 bytes). - * - * bigram address list is: - * <flags> = | hasNext = 1 bit, 1 = yes, 0 = no : FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT - * | addressSign = 1 bit, : FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE - * | 1 = must take -address, 0 = must take +address - * | xx : mask with MASK_BIGRAM_ATTR_ADDRESS_TYPE - * | addressFormat = 2 bits, 00 = unused : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE - * | 01 = 1 byte : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE - * | 10 = 2 bytes : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES - * | 11 = 3 bytes : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES - * | 4 bits : frequency : mask with FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY - * <address> | IF (01 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE == addressFormat) - * | read 1 byte, add top 4 bits - * | ELSIF (10 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES == addressFormat) - * | read 2 bytes, add top 4 bits - * | ELSE // 11 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES == addressFormat - * | read 3 bytes, add top 4 bits - * | END - * | if (FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE) then address = -address - * if (FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT) goto bigram_and_shortcut_address_list_is - * - * shortcut string list is: - * <byte size> = PTNODE_SHORTCUT_LIST_SIZE_SIZE bytes, big-endian: size of the list, in bytes. - * <flags> = | hasNext = 1 bit, 1 = yes, 0 = no : FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT - * | reserved = 3 bits, must be 0 - * | 4 bits : frequency : mask with FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY - * <shortcut> = | string of characters at the char format described above, with the terminator - * | used to signal the end of the string. - * if (FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT goto flags - */ - - public static final int MAGIC_NUMBER = 0x9BC13AFE; - static final int NOT_A_VERSION_NUMBER = -1; - - // These MUST have the same values as the relevant constants in format_utils.h. - // From version 2.01 on, we use version * 100 + revision as a version number. That allows - // us to change the format during development while having testing devices remove - // older files with each upgrade, while still having a readable versioning scheme. - // When we bump up the dictionary format version, we should update - // ExpandableDictionary.needsToMigrateDictionary() and - // ExpandableDictionary.matchesExpectedBinaryDictFormatVersionForThisType(). - public static final int VERSION2 = 2; - public static final int VERSION201 = 201; - public static final int VERSION202 = 202; - // format version for Fava Dictionaries. - public static final int VERSION_DELIGHT3 = 86736212; - public static final int MINIMUM_SUPPORTED_VERSION_OF_CODE_POINT_TABLE = VERSION201; - // Dictionary version used for testing. - public static final int VERSION4_ONLY_FOR_TESTING = 399; - public static final int VERSION402 = 402; - public static final int VERSION403 = 403; - public static final int VERSION4 = VERSION403; - public static final int MINIMUM_SUPPORTED_STATIC_VERSION = VERSION202; - public static final int MAXIMUM_SUPPORTED_STATIC_VERSION = VERSION_DELIGHT3; - static final int MINIMUM_SUPPORTED_DYNAMIC_VERSION = VERSION4; - static final int MAXIMUM_SUPPORTED_DYNAMIC_VERSION = VERSION403; - - // TODO: Make this value adaptative to content data, store it in the header, and - // use it in the reading code. - static final int MAX_WORD_LENGTH = DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH; - - // These flags are used only in the static dictionary. - static final int MASK_CHILDREN_ADDRESS_TYPE = 0xC0; - static final int FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS = 0x00; - static final int FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE = 0x40; - static final int FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES = 0x80; - static final int FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = 0xC0; - - static final int FLAG_HAS_MULTIPLE_CHARS = 0x20; - - static final int FLAG_IS_TERMINAL = 0x10; - static final int FLAG_HAS_SHORTCUT_TARGETS = 0x08; - static final int FLAG_HAS_BIGRAMS = 0x04; - static final int FLAG_IS_NOT_A_WORD = 0x02; - static final int FLAG_IS_POSSIBLY_OFFENSIVE = 0x01; - - static final int FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT = 0x80; - static final int FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE = 0x40; - static final int MASK_BIGRAM_ATTR_ADDRESS_TYPE = 0x30; - static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE = 0x10; - static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES = 0x20; - static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES = 0x30; - static final int FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY = 0x0F; - - static final int PTNODE_CHARACTERS_TERMINATOR = 0x1F; - - static final int PTNODE_TERMINATOR_SIZE = 1; - static final int PTNODE_FLAGS_SIZE = 1; - static final int PTNODE_FREQUENCY_SIZE = 1; - static final int PTNODE_MAX_ADDRESS_SIZE = 3; - static final int PTNODE_ATTRIBUTE_FLAGS_SIZE = 1; - static final int PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE = 3; - static final int PTNODE_SHORTCUT_LIST_SIZE_SIZE = 2; - - static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE; - static final int INVALID_CHARACTER = -1; - - static final int MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT = 0x7F; // 127 - // Large PtNode array size field size is 2 bytes. - static final int LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG = 0x8000; - static final int MAX_PTNODES_IN_A_PT_NODE_ARRAY = 0x7FFF; // 32767 - static final int MAX_BIGRAMS_IN_A_PTNODE = 10000; - static final int MAX_SHORTCUT_LIST_SIZE_IN_A_PTNODE = 0xFFFF; - - static final int MAX_TERMINAL_FREQUENCY = 255; - static final int MAX_BIGRAM_FREQUENCY = 15; - - public static final int SHORTCUT_WHITELIST_FREQUENCY = 15; - - // This option needs to be the same numeric value as the one in binary_format.h. - static final int NOT_VALID_WORD = -99; - - static final int UINT8_MAX = 0xFF; - static final int UINT16_MAX = 0xFFFF; - static final int UINT24_MAX = 0xFFFFFF; - static final int MSB8 = 0x80; - static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20; - static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF; - - /** - * Options about file format. - */ - public static final class FormatOptions { - public final int mVersion; - public final boolean mHasTimestamp; - - @UsedForTesting - public FormatOptions(final int version) { - this(version, false /* hasTimestamp */); - } - - public FormatOptions(final int version, final boolean hasTimestamp) { - mVersion = version; - mHasTimestamp = hasTimestamp; - } - } - - /** - * Options global to the dictionary. - */ - public static final class DictionaryOptions { - public final HashMap<String, String> mAttributes; - public DictionaryOptions(final HashMap<String, String> attributes) { - mAttributes = attributes; - } - @Override - public String toString() { // Convenience method - return toString(0, false); - } - public String toString(final int indentCount, final boolean plumbing) { - final StringBuilder indent = new StringBuilder(); - if (plumbing) { - indent.append("H:"); - } else { - for (int i = 0; i < indentCount; ++i) { - indent.append(" "); - } - } - final StringBuilder s = new StringBuilder(); - for (final String optionKey : mAttributes.keySet()) { - s.append(indent); - s.append(optionKey); - s.append(" = "); - if ("date".equals(optionKey) && !plumbing) { - // Date needs a number of milliseconds, but the dictionary contains seconds - s.append(new Date( - 1000 * Long.parseLong(mAttributes.get(optionKey))).toString()); - } else { - s.append(mAttributes.get(optionKey)); - } - s.append("\n"); - } - return s.toString(); - } - } - - private FormatSpec() { - // This utility class is not publicly instantiable. - } -} diff --git a/java/src/com/android/inputmethod/latin/makedict/NgramProperty.java b/java/src/com/android/inputmethod/latin/makedict/NgramProperty.java deleted file mode 100644 index b1d19dc3c..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/NgramProperty.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.makedict; - -import com.android.inputmethod.latin.NgramContext; - -public class NgramProperty { - public final WeightedString mTargetWord; - public final NgramContext mNgramContext; - - public NgramProperty(final WeightedString targetWord, final NgramContext ngramContext) { - mTargetWord = targetWord; - mNgramContext = ngramContext; - } - - @Override - public int hashCode() { - return mTargetWord.hashCode() ^ mNgramContext.hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof NgramProperty)) return false; - final NgramProperty n = (NgramProperty)o; - return mTargetWord.equals(n.mTargetWord) && mNgramContext.equals(n.mNgramContext); - } -} diff --git a/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java b/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java deleted file mode 100644 index 03c2ece1d..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.makedict; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.BinaryDictionary; -import com.android.inputmethod.latin.utils.CombinedFormatUtils; - -import java.util.Arrays; - -public final class ProbabilityInfo { - public final int mProbability; - // mTimestamp, mLevel and mCount are historical info. These values are depend on the - // implementation in native code; thus, we must not use them and have any assumptions about - // them except for tests. - public final int mTimestamp; - public final int mLevel; - public final int mCount; - - @UsedForTesting - public static ProbabilityInfo max(final ProbabilityInfo probabilityInfo1, - final ProbabilityInfo probabilityInfo2) { - if (probabilityInfo1 == null) { - return probabilityInfo2; - } - if (probabilityInfo2 == null) { - return probabilityInfo1; - } - return (probabilityInfo1.mProbability > probabilityInfo2.mProbability) ? probabilityInfo1 - : probabilityInfo2; - } - - public ProbabilityInfo(final int probability) { - this(probability, BinaryDictionary.NOT_A_VALID_TIMESTAMP, 0, 0); - } - - public ProbabilityInfo(final int probability, final int timestamp, final int level, - final int count) { - mProbability = probability; - mTimestamp = timestamp; - mLevel = level; - mCount = count; - } - - public boolean hasHistoricalInfo() { - return mTimestamp != BinaryDictionary.NOT_A_VALID_TIMESTAMP; - } - - @Override - public int hashCode() { - if (hasHistoricalInfo()) { - return Arrays.hashCode(new Object[] { mProbability, mTimestamp, mLevel, mCount }); - } - return Arrays.hashCode(new Object[] { mProbability }); - } - - @Override - public String toString() { - return CombinedFormatUtils.formatProbabilityInfo(this); - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof ProbabilityInfo)) return false; - final ProbabilityInfo p = (ProbabilityInfo)o; - if (!hasHistoricalInfo() && !p.hasHistoricalInfo()) { - return mProbability == p.mProbability; - } - return mProbability == p.mProbability && mTimestamp == p.mTimestamp && mLevel == p.mLevel - && mCount == p.mCount; - } -}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java b/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java deleted file mode 100644 index 4f3861526..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.makedict; - -/** - * Simple exception thrown when a file format is not recognized. - */ -public final class UnsupportedFormatException extends Exception { - public UnsupportedFormatException(String description) { - super(description); - } -} diff --git a/java/src/com/android/inputmethod/latin/makedict/WeightedString.java b/java/src/com/android/inputmethod/latin/makedict/WeightedString.java deleted file mode 100644 index f6782df9e..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/WeightedString.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.makedict; - -import com.android.inputmethod.annotations.UsedForTesting; - -import java.util.Arrays; - -/** - * A string with a probability. - * - * This represents an "attribute", that is either a bigram or a shortcut. - */ -public final class WeightedString { - public final String mWord; - public ProbabilityInfo mProbabilityInfo; - - public WeightedString(final String word, final int probability) { - this(word, new ProbabilityInfo(probability)); - } - - public WeightedString(final String word, final ProbabilityInfo probabilityInfo) { - mWord = word; - mProbabilityInfo = probabilityInfo; - } - - @UsedForTesting - public int getProbability() { - return mProbabilityInfo.mProbability; - } - - public void setProbability(final int probability) { - mProbabilityInfo = new ProbabilityInfo(probability); - } - - @Override - public int hashCode() { - return Arrays.hashCode(new Object[] { mWord, mProbabilityInfo}); - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof WeightedString)) return false; - final WeightedString w = (WeightedString)o; - return mWord.equals(w.mWord) && mProbabilityInfo.equals(w.mProbabilityInfo); - } -}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java deleted file mode 100644 index 264e75710..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.makedict; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.BinaryDictionary; -import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.NgramContext; -import com.android.inputmethod.latin.NgramContext.WordInfo; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.utils.CombinedFormatUtils; - -import java.util.ArrayList; -import java.util.Arrays; - -import javax.annotation.Nullable; - -/** - * Utility class for a word with a probability. - * - * This is chiefly used to iterate a dictionary. - */ -public final class WordProperty implements Comparable<WordProperty> { - public final String mWord; - public final ProbabilityInfo mProbabilityInfo; - public final ArrayList<NgramProperty> mNgrams; - // TODO: Support mIsBeginningOfSentence. - public final boolean mIsBeginningOfSentence; - public final boolean mIsNotAWord; - public final boolean mIsPossiblyOffensive; - public final boolean mHasNgrams; - - private int mHashCode = 0; - - // TODO: Support n-gram. - @UsedForTesting - public WordProperty(final String word, final ProbabilityInfo probabilityInfo, - @Nullable final ArrayList<WeightedString> bigrams, - final boolean isNotAWord, final boolean isPossiblyOffensive) { - mWord = word; - mProbabilityInfo = probabilityInfo; - if (null == bigrams) { - mNgrams = null; - } else { - mNgrams = new ArrayList<>(); - final NgramContext ngramContext = new NgramContext(new WordInfo(mWord)); - for (final WeightedString bigramTarget : bigrams) { - mNgrams.add(new NgramProperty(bigramTarget, ngramContext)); - } - } - mIsBeginningOfSentence = false; - mIsNotAWord = isNotAWord; - mIsPossiblyOffensive = isPossiblyOffensive; - mHasNgrams = bigrams != null && !bigrams.isEmpty(); - } - - private static ProbabilityInfo createProbabilityInfoFromArray(final int[] probabilityInfo) { - return new ProbabilityInfo( - probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_PROBABILITY_INDEX], - probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX], - probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_LEVEL_INDEX], - probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_COUNT_INDEX]); - } - - // Construct word property using information from native code. - // This represents invalid word when the probability is BinaryDictionary.NOT_A_PROBABILITY. - public WordProperty(final int[] codePoints, final boolean isNotAWord, - final boolean isPossiblyOffensive, final boolean hasBigram, - final boolean isBeginningOfSentence, final int[] probabilityInfo, - final ArrayList<int[][]> ngramPrevWordsArray, - final ArrayList<boolean[]> ngramPrevWordIsBeginningOfSentenceArray, - final ArrayList<int[]> ngramTargets, final ArrayList<int[]> ngramProbabilityInfo) { - mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints); - mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo); - final ArrayList<NgramProperty> ngrams = new ArrayList<>(); - mIsBeginningOfSentence = isBeginningOfSentence; - mIsNotAWord = isNotAWord; - mIsPossiblyOffensive = isPossiblyOffensive; - mHasNgrams = hasBigram; - - final int relatedNgramCount = ngramTargets.size(); - for (int i = 0; i < relatedNgramCount; i++) { - final String ngramTargetString = - StringUtils.getStringFromNullTerminatedCodePointArray(ngramTargets.get(i)); - final WeightedString ngramTarget = new WeightedString(ngramTargetString, - createProbabilityInfoFromArray(ngramProbabilityInfo.get(i))); - final int[][] prevWords = ngramPrevWordsArray.get(i); - final boolean[] isBeginningOfSentenceArray = - ngramPrevWordIsBeginningOfSentenceArray.get(i); - final WordInfo[] wordInfoArray = new WordInfo[prevWords.length]; - for (int j = 0; j < prevWords.length; j++) { - wordInfoArray[j] = isBeginningOfSentenceArray[j] - ? WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO - : new WordInfo(StringUtils.getStringFromNullTerminatedCodePointArray( - prevWords[j])); - } - final NgramContext ngramContext = new NgramContext(wordInfoArray); - ngrams.add(new NgramProperty(ngramTarget, ngramContext)); - } - mNgrams = ngrams.isEmpty() ? null : ngrams; - } - - // TODO: Remove - @UsedForTesting - public ArrayList<WeightedString> getBigrams() { - if (null == mNgrams) { - return null; - } - final ArrayList<WeightedString> bigrams = new ArrayList<>(); - for (final NgramProperty ngram : mNgrams) { - if (ngram.mNgramContext.getPrevWordCount() == 1) { - bigrams.add(ngram.mTargetWord); - } - } - return bigrams; - } - - public int getProbability() { - return mProbabilityInfo.mProbability; - } - - private static int computeHashCode(WordProperty word) { - return Arrays.hashCode(new Object[] { - word.mWord, - word.mProbabilityInfo, - word.mNgrams, - word.mIsNotAWord, - word.mIsPossiblyOffensive - }); - } - - /** - * Three-way comparison. - * - * A Word x is greater than a word y if x has a higher frequency. If they have the same - * frequency, they are sorted in lexicographic order. - */ - @Override - public int compareTo(final WordProperty w) { - if (getProbability() < w.getProbability()) return 1; - if (getProbability() > w.getProbability()) return -1; - return mWord.compareTo(w.mWord); - } - - /** - * Equality test. - * - * Words are equal if they have the same frequency, the same spellings, and the same - * attributes. - */ - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof WordProperty)) return false; - WordProperty w = (WordProperty)o; - return mProbabilityInfo.equals(w.mProbabilityInfo) - && mWord.equals(w.mWord) && equals(mNgrams, w.mNgrams) - && mIsNotAWord == w.mIsNotAWord && mIsPossiblyOffensive == w.mIsPossiblyOffensive - && mHasNgrams == w.mHasNgrams; - } - - // TDOO: Have a utility method like java.util.Objects.equals. - private static <T> boolean equals(final ArrayList<T> a, final ArrayList<T> b) { - if (null == a) { - return null == b; - } - return a.equals(b); - } - - @Override - public int hashCode() { - if (mHashCode == 0) { - mHashCode = computeHashCode(this); - } - return mHashCode; - } - - @UsedForTesting - public boolean isValid() { - return getProbability() != Dictionary.NOT_A_PROBABILITY; - } - - @Override - public String toString() { - return CombinedFormatUtils.formatWordProperty(this); - } -} diff --git a/java/src/com/android/inputmethod/latin/network/AuthException.java b/java/src/com/android/inputmethod/latin/network/AuthException.java deleted file mode 100644 index 1bce4c156..000000000 --- a/java/src/com/android/inputmethod/latin/network/AuthException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.network; - -/** - * Authentication exception. When this exception is thrown, the client may - * try to refresh the authentication token and try again. - */ -public class AuthException extends Exception { - public AuthException() { - super(); - } - - public AuthException(Throwable throwable) { - super(throwable); - } - - public AuthException(String detailMessage) { - super(detailMessage); - } -}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java b/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java deleted file mode 100644 index 079d07eac..000000000 --- a/java/src/com/android/inputmethod/latin/network/BlockingHttpClient.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.network; - -import android.util.Log; - -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * A client for executing HTTP requests synchronously. - * This must never be called from the main thread. - */ -public class BlockingHttpClient { - private static final boolean DEBUG = false; - private static final String TAG = BlockingHttpClient.class.getSimpleName(); - - private final HttpURLConnection mConnection; - - /** - * Interface that handles processing the response for a request. - */ - public interface ResponseProcessor<T> { - /** - * Called when the HTTP request finishes successfully. - * The {@link InputStream} is closed by the client after the method finishes, - * so any processing must be done in this method itself. - * - * @param response An input stream that can be used to read the HTTP response. - */ - T onSuccess(InputStream response) throws IOException; - } - - public BlockingHttpClient(HttpURLConnection connection) { - mConnection = connection; - } - - /** - * Executes the request on the underlying {@link HttpURLConnection}. - * - * @param request The request payload, if any, or null. - * @param responseProcessor A processor for the HTTP response. - */ - public <T> T execute(@Nullable byte[] request, @Nonnull ResponseProcessor<T> responseProcessor) - throws IOException, AuthException, HttpException { - if (DEBUG) { - Log.d(TAG, "execute: " + mConnection.getURL()); - } - try { - if (request != null) { - if (DEBUG) { - Log.d(TAG, "request size: " + request.length); - } - OutputStream out = new BufferedOutputStream(mConnection.getOutputStream()); - out.write(request); - out.flush(); - out.close(); - } - - final int responseCode = mConnection.getResponseCode(); - if (responseCode != HttpURLConnection.HTTP_OK) { - Log.w(TAG, "Response error: " + responseCode + ", Message: " - + mConnection.getResponseMessage()); - if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { - throw new AuthException(mConnection.getResponseMessage()); - } - throw new HttpException(responseCode); - } - if (DEBUG) { - Log.d(TAG, "request executed successfully"); - } - return responseProcessor.onSuccess(mConnection.getInputStream()); - } finally { - mConnection.disconnect(); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/network/HttpException.java b/java/src/com/android/inputmethod/latin/network/HttpException.java deleted file mode 100644 index b9d8b6372..000000000 --- a/java/src/com/android/inputmethod/latin/network/HttpException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.network; - -import com.android.inputmethod.annotations.UsedForTesting; - -/** - * The HttpException exception represents a XML/HTTP fault with a HTTP status code. - */ -public class HttpException extends Exception { - - /** - * The HTTP status code. - */ - private final int mStatusCode; - - /** - * @param statusCode int HTTP status code. - */ - public HttpException(int statusCode) { - super("Response Code: " + statusCode); - mStatusCode = statusCode; - } - - /** - * @return the HTTP status code related to this exception. - */ - @UsedForTesting - public int getHttpStatusCode() { - return mStatusCode; - } -}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java b/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java deleted file mode 100644 index df54bf464..000000000 --- a/java/src/com/android/inputmethod/latin/network/HttpUrlConnectionBuilder.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.network; - -import android.text.TextUtils; - -import com.android.inputmethod.annotations.UsedForTesting; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashMap; -import java.util.Map.Entry; - -/** - * Builder for {@link HttpURLConnection}s. - * - * TODO: Remove @UsedForTesting after this is actually used. - */ -@UsedForTesting -public class HttpUrlConnectionBuilder { - private static final int DEFAULT_TIMEOUT_MILLIS = 5 * 1000; - - /** - * Request header key for authentication. - */ - public static final String HTTP_HEADER_AUTHORIZATION = "Authorization"; - - /** - * Request header key for cache control. - */ - public static final String KEY_CACHE_CONTROL = "Cache-Control"; - /** - * Request header value for cache control indicating no caching. - * @see #KEY_CACHE_CONTROL - */ - public static final String VALUE_NO_CACHE = "no-cache"; - - /** - * Indicates that the request is unidirectional - upload-only. - * TODO: Remove @UsedForTesting after this is actually used. - */ - @UsedForTesting - public static final int MODE_UPLOAD_ONLY = 1; - /** - * Indicates that the request is unidirectional - download only. - * TODO: Remove @UsedForTesting after this is actually used. - */ - @UsedForTesting - public static final int MODE_DOWNLOAD_ONLY = 2; - /** - * Indicates that the request is bi-directional. - * TODO: Remove @UsedForTesting after this is actually used. - */ - @UsedForTesting - public static final int MODE_BI_DIRECTIONAL = 3; - - private final HashMap<String, String> mHeaderMap = new HashMap<>(); - - private URL mUrl; - private int mConnectTimeoutMillis = DEFAULT_TIMEOUT_MILLIS; - private int mReadTimeoutMillis = DEFAULT_TIMEOUT_MILLIS; - private int mContentLength = -1; - private boolean mUseCache; - private int mMode; - - /** - * Sets the URL that'll be used for the request. - * This *must* be set before calling {@link #build()} - * - * TODO: Remove @UsedForTesting after this method is actually used. - */ - @UsedForTesting - public HttpUrlConnectionBuilder setUrl(String url) throws MalformedURLException { - if (TextUtils.isEmpty(url)) { - throw new IllegalArgumentException("URL must not be empty"); - } - mUrl = new URL(url); - return this; - } - - /** - * Sets the connect timeout. Defaults to {@value #DEFAULT_TIMEOUT_MILLIS} milliseconds. - * - * TODO: Remove @UsedForTesting after this method is actually used. - */ - @UsedForTesting - public HttpUrlConnectionBuilder setConnectTimeout(int timeoutMillis) { - if (timeoutMillis < 0) { - throw new IllegalArgumentException("connect-timeout must be >= 0, but was " - + timeoutMillis); - } - mConnectTimeoutMillis = timeoutMillis; - return this; - } - - /** - * Sets the read timeout. Defaults to {@value #DEFAULT_TIMEOUT_MILLIS} milliseconds. - * - * TODO: Remove @UsedForTesting after this method is actually used. - */ - @UsedForTesting - public HttpUrlConnectionBuilder setReadTimeout(int timeoutMillis) { - if (timeoutMillis < 0) { - throw new IllegalArgumentException("read-timeout must be >= 0, but was " - + timeoutMillis); - } - mReadTimeoutMillis = timeoutMillis; - return this; - } - - /** - * Adds an entry to the request header. - * - * TODO: Remove @UsedForTesting after this method is actually used. - */ - @UsedForTesting - public HttpUrlConnectionBuilder addHeader(String key, String value) { - mHeaderMap.put(key, value); - return this; - } - - /** - * Sets an authentication token. - * - * TODO: Remove @UsedForTesting after this method is actually used. - */ - @UsedForTesting - public HttpUrlConnectionBuilder setAuthToken(String value) { - mHeaderMap.put(HTTP_HEADER_AUTHORIZATION, value); - return this; - } - - /** - * Sets the request to be executed such that the input is not buffered. - * This may be set when the request size is known beforehand. - * - * TODO: Remove @UsedForTesting after this method is actually used. - */ - @UsedForTesting - public HttpUrlConnectionBuilder setFixedLengthForStreaming(int length) { - mContentLength = length; - return this; - } - - /** - * Indicates if the request can use cached responses or not. - * - * TODO: Remove @UsedForTesting after this method is actually used. - */ - @UsedForTesting - public HttpUrlConnectionBuilder setUseCache(boolean useCache) { - mUseCache = useCache; - return this; - } - - /** - * The request mode. - * Sets the request mode to be one of: upload-only, download-only or bidirectional. - * - * @see #MODE_UPLOAD_ONLY - * @see #MODE_DOWNLOAD_ONLY - * @see #MODE_BI_DIRECTIONAL - * - * TODO: Remove @UsedForTesting after this method is actually used - */ - @UsedForTesting - public HttpUrlConnectionBuilder setMode(int mode) { - if (mode != MODE_UPLOAD_ONLY - && mode != MODE_DOWNLOAD_ONLY - && mode != MODE_BI_DIRECTIONAL) { - throw new IllegalArgumentException("Invalid mode specified:" + mode); - } - mMode = mode; - return this; - } - - /** - * Builds the {@link HttpURLConnection} instance that can be used to execute the request. - * - * TODO: Remove @UsedForTesting after this method is actually used. - */ - @UsedForTesting - public HttpURLConnection build() throws IOException { - if (mUrl == null) { - throw new IllegalArgumentException("A URL must be specified!"); - } - final HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); - connection.setConnectTimeout(mConnectTimeoutMillis); - connection.setReadTimeout(mReadTimeoutMillis); - connection.setUseCaches(mUseCache); - switch (mMode) { - case MODE_UPLOAD_ONLY: - connection.setDoInput(true); - connection.setDoOutput(false); - break; - case MODE_DOWNLOAD_ONLY: - connection.setDoInput(false); - connection.setDoOutput(true); - break; - case MODE_BI_DIRECTIONAL: - connection.setDoInput(true); - connection.setDoOutput(true); - break; - } - for (final Entry<String, String> entry : mHeaderMap.entrySet()) { - connection.addRequestProperty(entry.getKey(), entry.getValue()); - } - if (mContentLength >= 0) { - connection.setFixedLengthStreamingMode(mContentLength); - } - return connection; - } -}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/permissions/PermissionsActivity.java b/java/src/com/android/inputmethod/latin/permissions/PermissionsActivity.java deleted file mode 100644 index 36d8ed943..000000000 --- a/java/src/com/android/inputmethod/latin/permissions/PermissionsActivity.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.inputmethod.latin.permissions; - - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; - -/** - * An activity to help request permissions. It's used when no other activity is available, e.g. in - * InputMethodService. This activity assumes that all permissions are not granted yet. - */ -public final class PermissionsActivity - extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback { - - /** - * Key to retrieve requested permissions from the intent. - */ - public static final String EXTRA_PERMISSION_REQUESTED_PERMISSIONS = "requested_permissions"; - - /** - * Key to retrieve request code from the intent. - */ - public static final String EXTRA_PERMISSION_REQUEST_CODE = "request_code"; - - private static final int INVALID_REQUEST_CODE = -1; - - private int mPendingRequestCode = INVALID_REQUEST_CODE; - - /** - * Starts a PermissionsActivity and checks/requests supplied permissions. - */ - public static void run( - @NonNull Context context, int requestCode, @NonNull String... permissionStrings) { - Intent intent = new Intent(context.getApplicationContext(), PermissionsActivity.class); - intent.putExtra(EXTRA_PERMISSION_REQUESTED_PERMISSIONS, permissionStrings); - intent.putExtra(EXTRA_PERMISSION_REQUEST_CODE, requestCode); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - context.startActivity(intent); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mPendingRequestCode = (savedInstanceState != null) - ? savedInstanceState.getInt(EXTRA_PERMISSION_REQUEST_CODE, INVALID_REQUEST_CODE) - : INVALID_REQUEST_CODE; - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(EXTRA_PERMISSION_REQUEST_CODE, mPendingRequestCode); - } - - @Override - protected void onResume() { - super.onResume(); - // Only do request when there is no pending request to avoid duplicated requests. - if (mPendingRequestCode == INVALID_REQUEST_CODE) { - final Bundle extras = getIntent().getExtras(); - final String[] permissionsToRequest = - extras.getStringArray(EXTRA_PERMISSION_REQUESTED_PERMISSIONS); - mPendingRequestCode = extras.getInt(EXTRA_PERMISSION_REQUEST_CODE); - // Assuming that all supplied permissions are not granted yet, so that we don't need to - // check them again. - PermissionsUtil.requestPermissions(this, mPendingRequestCode, permissionsToRequest); - } - } - - @Override - public void onRequestPermissionsResult( - int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - mPendingRequestCode = INVALID_REQUEST_CODE; - PermissionsManager.get(this).onRequestPermissionsResult( - requestCode, permissions, grantResults); - finish(); - } -} diff --git a/java/src/com/android/inputmethod/latin/permissions/PermissionsManager.java b/java/src/com/android/inputmethod/latin/permissions/PermissionsManager.java deleted file mode 100644 index 08c623ab5..000000000 --- a/java/src/com/android/inputmethod/latin/permissions/PermissionsManager.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.inputmethod.latin.permissions; - -import android.app.Activity; -import android.content.Context; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Manager to perform permission related tasks. Always call on the UI thread. - */ -public class PermissionsManager { - - public interface PermissionsResultCallback { - void onRequestPermissionsResult(boolean allGranted); - } - - private int mRequestCodeId; - - private final Context mContext; - private final Map<Integer, PermissionsResultCallback> mRequestIdToCallback = new HashMap<>(); - - private static PermissionsManager sInstance; - - public PermissionsManager(Context context) { - mContext = context; - } - - @Nonnull - public static synchronized PermissionsManager get(@Nonnull Context context) { - if (sInstance == null) { - sInstance = new PermissionsManager(context); - } - return sInstance; - } - - private synchronized int getNextRequestId() { - return ++mRequestCodeId; - } - - - public synchronized void requestPermissions(@Nonnull PermissionsResultCallback callback, - @Nullable Activity activity, - String... permissionsToRequest) { - List<String> deniedPermissions = PermissionsUtil.getDeniedPermissions( - mContext, permissionsToRequest); - if (deniedPermissions.isEmpty()) { - return; - } - // otherwise request the permissions. - int requestId = getNextRequestId(); - String[] permissionsArray = deniedPermissions.toArray( - new String[deniedPermissions.size()]); - - mRequestIdToCallback.put(requestId, callback); - if (activity != null) { - PermissionsUtil.requestPermissions(activity, requestId, permissionsArray); - } else { - PermissionsActivity.run(mContext, requestId, permissionsArray); - } - } - - public synchronized void onRequestPermissionsResult( - int requestCode, String[] permissions, int[] grantResults) { - PermissionsResultCallback permissionsResultCallback = mRequestIdToCallback.get(requestCode); - mRequestIdToCallback.remove(requestCode); - - boolean allGranted = PermissionsUtil.allGranted(grantResults); - permissionsResultCallback.onRequestPermissionsResult(allGranted); - } -} diff --git a/java/src/com/android/inputmethod/latin/permissions/PermissionsUtil.java b/java/src/com/android/inputmethod/latin/permissions/PermissionsUtil.java deleted file mode 100644 index 9a618a755..000000000 --- a/java/src/com/android/inputmethod/latin/permissions/PermissionsUtil.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.inputmethod.latin.permissions; - -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import java.util.ArrayList; -import java.util.List; - -/** - * Utility class for permissions. - */ -public class PermissionsUtil { - - /** - * Returns the list of permissions not granted from the given list of permissions. - * @param context Context - * @param permissions list of permissions to check. - * @return the list of permissions that do not have permission to use. - */ - public static List<String> getDeniedPermissions(Context context, - String... permissions) { - final List<String> deniedPermissions = new ArrayList<>(); - for (String permission : permissions) { - if (ContextCompat.checkSelfPermission(context, permission) - != PackageManager.PERMISSION_GRANTED) { - deniedPermissions.add(permission); - } - } - return deniedPermissions; - } - - /** - * Uses the given activity and requests the user for permissions. - * @param activity activity to use. - * @param requestCode request code/id to use. - * @param permissions String array of permissions that needs to be requested. - */ - public static void requestPermissions(Activity activity, int requestCode, - String[] permissions) { - ActivityCompat.requestPermissions(activity, permissions, requestCode); - } - - /** - * Checks if all the permissions are granted. - */ - public static boolean allGranted(@NonNull int[] grantResults) { - for (int result : grantResults) { - if (result != PackageManager.PERMISSION_GRANTED) { - return false; - } - } - return true; - } - - /** - * Queries if al the permissions are granted for the given permission strings. - */ - public static boolean checkAllPermissionsGranted(Context context, String... permissions) { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { - // For all pre-M devices, we should have all the premissions granted on install. - return true; - } - - for (String permission : permissions) { - if (ContextCompat.checkSelfPermission(context, permission) - != PackageManager.PERMISSION_GRANTED) { - return false; - } - } - return true; - } -} diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java deleted file mode 100644 index ab3ef964e..000000000 --- a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.personalization; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.content.Context; -import android.util.Patterns; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -public class AccountUtils { - private AccountUtils() { - // This utility class is not publicly instantiable. - } - - private static Account[] getAccounts(final Context context) { - return AccountManager.get(context).getAccounts(); - } - - public static List<String> getDeviceAccountsEmailAddresses(final Context context) { - final ArrayList<String> retval = new ArrayList<>(); - for (final Account account : getAccounts(context)) { - final String name = account.name; - if (Patterns.EMAIL_ADDRESS.matcher(name).matches()) { - retval.add(name); - retval.add(name.split("@")[0]); - } - } - return retval; - } - - /** - * Get all device accounts having specified domain name. - * @param context application context - * @param domain domain name used for filtering - * @return List of account names that contain the specified domain name - */ - public static List<String> getDeviceAccountsWithDomain( - final Context context, final String domain) { - final ArrayList<String> retval = new ArrayList<>(); - final String atDomain = "@" + domain.toLowerCase(Locale.ROOT); - for (final Account account : getAccounts(context)) { - if (account.name.toLowerCase(Locale.ROOT).endsWith(atDomain)) { - retval.add(account.name); - } - } - return retval; - } -} diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java deleted file mode 100644 index 298e46c0a..000000000 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.personalization; - -import android.content.Context; -import android.util.Log; - -import com.android.inputmethod.latin.common.FileUtils; - -import java.io.File; -import java.io.FilenameFilter; -import java.lang.ref.SoftReference; -import java.util.Locale; -import java.util.concurrent.ConcurrentHashMap; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Helps handle and manage personalized dictionaries such as {@link UserHistoryDictionary}. - */ -public class PersonalizationHelper { - private static final String TAG = PersonalizationHelper.class.getSimpleName(); - private static final boolean DEBUG = false; - - private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>> - sLangUserHistoryDictCache = new ConcurrentHashMap<>(); - - @Nonnull - public static UserHistoryDictionary getUserHistoryDictionary( - final Context context, final Locale locale, @Nullable final String accountName) { - String lookupStr = locale.toString(); - if (accountName != null) { - lookupStr += "." + accountName; - } - synchronized (sLangUserHistoryDictCache) { - if (sLangUserHistoryDictCache.containsKey(lookupStr)) { - final SoftReference<UserHistoryDictionary> ref = - sLangUserHistoryDictCache.get(lookupStr); - final UserHistoryDictionary dict = ref == null ? null : ref.get(); - if (dict != null) { - if (DEBUG) { - Log.d(TAG, "Use cached UserHistoryDictionary with lookup: " + lookupStr); - } - dict.reloadDictionaryIfRequired(); - return dict; - } - } - final UserHistoryDictionary dict = new UserHistoryDictionary( - context, locale, accountName); - sLangUserHistoryDictCache.put(lookupStr, new SoftReference<>(dict)); - return dict; - } - } - - public static void removeAllUserHistoryDictionaries(final Context context) { - synchronized (sLangUserHistoryDictCache) { - for (final ConcurrentHashMap.Entry<String, SoftReference<UserHistoryDictionary>> entry - : sLangUserHistoryDictCache.entrySet()) { - if (entry.getValue() != null) { - final UserHistoryDictionary dict = entry.getValue().get(); - if (dict != null) { - dict.clear(); - } - } - } - sLangUserHistoryDictCache.clear(); - final File filesDir = context.getFilesDir(); - if (filesDir == null) { - Log.e(TAG, "context.getFilesDir() returned null."); - return; - } - final boolean filesDeleted = FileUtils.deleteFilteredFiles( - filesDir, new DictFilter(UserHistoryDictionary.NAME)); - if (!filesDeleted) { - Log.e(TAG, "Cannot remove dictionary files. filesDir: " + filesDir.getAbsolutePath() - + ", dictNamePrefix: " + UserHistoryDictionary.NAME); - } - } - } - - private static class DictFilter implements FilenameFilter { - private final String mName; - - DictFilter(final String name) { - mName = name; - } - - @Override - public boolean accept(final File dir, final String name) { - return name.startsWith(mName); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java deleted file mode 100644 index cbf0829b5..000000000 --- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.personalization; - -import android.content.Context; - -import com.android.inputmethod.annotations.ExternallyReferenced; -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.BinaryDictionary; -import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.ExpandableBinaryDictionary; -import com.android.inputmethod.latin.NgramContext; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; -import com.android.inputmethod.latin.define.ProductionFlags; -import com.android.inputmethod.latin.makedict.DictionaryHeader; - -import java.io.File; -import java.util.Locale; -import java.util.Map; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Locally gathers statistics about the words user types and various other signals like - * auto-correction cancellation or manual picks. This allows the keyboard to adapt to the - * typist over time. - */ -public class UserHistoryDictionary extends ExpandableBinaryDictionary { - static final String NAME = UserHistoryDictionary.class.getSimpleName(); - - // TODO: Make this constructor private - UserHistoryDictionary(final Context context, final Locale locale, - @Nullable final String account) { - super(context, getUserHistoryDictName(NAME, locale, null /* dictFile */, account), locale, Dictionary.TYPE_USER_HISTORY, null); - if (mLocale != null && mLocale.toString().length() > 1) { - reloadDictionaryIfRequired(); - } - } - - /** - * @returns the name of the {@link UserHistoryDictionary}. - */ - @UsedForTesting - static String getUserHistoryDictName(final String name, final Locale locale, - @Nullable final File dictFile, @Nullable final String account) { - if (!ProductionFlags.ENABLE_PER_ACCOUNT_USER_HISTORY_DICTIONARY) { - return getDictName(name, locale, dictFile); - } - return getUserHistoryDictNamePerAccount(name, locale, dictFile, account); - } - - /** - * Uses the currently signed in account to determine the dictionary name. - */ - private static String getUserHistoryDictNamePerAccount(final String name, final Locale locale, - @Nullable final File dictFile, @Nullable final String account) { - if (dictFile != null) { - return dictFile.getName(); - } - String dictName = name + "." + locale.toString(); - if (account != null) { - dictName += "." + account; - } - return dictName; - } - - // Note: This method is called by {@link DictionaryFacilitator} using Java reflection. - @SuppressWarnings("unused") - @ExternallyReferenced - public static UserHistoryDictionary getDictionary(final Context context, final Locale locale, - final File dictFile, final String dictNamePrefix, @Nullable final String account) { - return PersonalizationHelper.getUserHistoryDictionary(context, locale, account); - } - - /** - * Add a word to the user history dictionary. - * - * @param userHistoryDictionary the user history dictionary - * @param ngramContext the n-gram context - * @param word the word the user inputted - * @param isValid whether the word is valid or not - * @param timestamp the timestamp when the word has been inputted - */ - public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary, - @Nonnull final NgramContext ngramContext, final String word, final boolean isValid, - final int timestamp) { - if (word.length() > BinaryDictionary.DICTIONARY_MAX_WORD_LENGTH) { - return; - } - userHistoryDictionary.updateEntriesForWord(ngramContext, word, - isValid, 1 /* count */, timestamp); - } - - @Override - public void close() { - // Flush pending writes. - asyncFlushBinaryDictionary(); - super.close(); - } - - @Override - protected Map<String, String> getHeaderAttributeMap() { - final Map<String, String> attributeMap = super.getHeaderAttributeMap(); - attributeMap.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY, - DictionaryHeader.ATTRIBUTE_VALUE_TRUE); - attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY, - DictionaryHeader.ATTRIBUTE_VALUE_TRUE); - return attributeMap; - } - - @Override - protected void loadInitialContentsLocked() { - // No initial contents. - } - - @Override - public boolean isValidWord(final String word) { - // Strings out of this dictionary should not be considered existing words. - return false; - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java deleted file mode 100644 index b39e6b477..000000000 --- a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import static com.android.inputmethod.latin.settings.LocalSettingsConstants.PREF_ACCOUNT_NAME; -import static com.android.inputmethod.latin.settings.LocalSettingsConstants.PREF_ENABLE_CLOUD_SYNC; - -import android.Manifest; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnShowListener; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.os.AsyncTask; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.TwoStatePreference; -import android.text.TextUtils; -import android.text.method.LinkMovementMethod; -import android.widget.ListView; -import android.widget.TextView; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.accounts.AccountStateChangedListener; -import com.android.inputmethod.latin.accounts.LoginAccountUtils; -import com.android.inputmethod.latin.define.ProductionFlags; -import com.android.inputmethod.latin.permissions.PermissionsUtil; -import com.android.inputmethod.latin.utils.ManagedProfileUtils; - -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.annotation.Nullable; - -/** - * "Accounts & Privacy" settings sub screen. - * - * This settings sub screen handles the following preferences: - * <li> Account selection/management for IME </li> - * <li> Sync preferences </li> - * <li> Privacy preferences </li> - */ -public final class AccountsSettingsFragment extends SubScreenFragment { - private static final String PREF_ENABLE_SYNC_NOW = "pref_enable_cloud_sync"; - private static final String PREF_SYNC_NOW = "pref_sync_now"; - private static final String PREF_CLEAR_SYNC_DATA = "pref_clear_sync_data"; - - static final String PREF_ACCCOUNT_SWITCHER = "account_switcher"; - - /** - * Onclick listener for sync now pref. - */ - private final Preference.OnPreferenceClickListener mSyncNowListener = - new SyncNowListener(); - /** - * Onclick listener for delete sync pref. - */ - private final Preference.OnPreferenceClickListener mDeleteSyncDataListener = - new DeleteSyncDataListener(); - - /** - * Onclick listener for enable sync pref. - */ - private final Preference.OnPreferenceClickListener mEnableSyncClickListener = - new EnableSyncClickListener(); - - /** - * Enable sync checkbox pref. - */ - private TwoStatePreference mEnableSyncPreference; - - /** - * Enable sync checkbox pref. - */ - private Preference mSyncNowPreference; - - /** - * Clear sync data pref. - */ - private Preference mClearSyncDataPreference; - - /** - * Account switcher preference. - */ - private Preference mAccountSwitcher; - - /** - * Stores if we are currently detecting a managed profile. - */ - private AtomicBoolean mManagedProfileBeingDetected = new AtomicBoolean(true); - - /** - * Stores if we have successfully detected if the device has a managed profile. - */ - private AtomicBoolean mHasManagedProfile = new AtomicBoolean(false); - - @Override - public void onCreate(final Bundle icicle) { - super.onCreate(icicle); - addPreferencesFromResource(R.xml.prefs_screen_accounts); - - mAccountSwitcher = findPreference(PREF_ACCCOUNT_SWITCHER); - mEnableSyncPreference = (TwoStatePreference) findPreference(PREF_ENABLE_SYNC_NOW); - mSyncNowPreference = findPreference(PREF_SYNC_NOW); - mClearSyncDataPreference = findPreference(PREF_CLEAR_SYNC_DATA); - - if (ProductionFlags.IS_METRICS_LOGGING_SUPPORTED) { - final Preference enableMetricsLogging = - findPreference(Settings.PREF_ENABLE_METRICS_LOGGING); - final Resources res = getResources(); - if (enableMetricsLogging != null) { - final String enableMetricsLoggingTitle = res.getString( - R.string.enable_metrics_logging, getApplicationName()); - enableMetricsLogging.setTitle(enableMetricsLoggingTitle); - } - } else { - removePreference(Settings.PREF_ENABLE_METRICS_LOGGING); - } - - if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) { - removeSyncPreferences(); - } else { - // Disable by default till we are sure we can enable this. - disableSyncPreferences(); - new ManagedProfileCheckerTask(this).execute(); - } - } - - /** - * Task to check work profile. If found, it removes the sync prefs. If not, - * it enables them. - */ - private static class ManagedProfileCheckerTask extends AsyncTask<Void, Void, Boolean> { - private final AccountsSettingsFragment mFragment; - - private ManagedProfileCheckerTask(final AccountsSettingsFragment fragment) { - mFragment = fragment; - } - - @Override - protected void onPreExecute() { - mFragment.mManagedProfileBeingDetected.set(true); - } - @Override - protected Boolean doInBackground(Void... params) { - return ManagedProfileUtils.getInstance().hasWorkProfile(mFragment.getActivity()); - } - - @Override - protected void onPostExecute(final Boolean hasWorkProfile) { - mFragment.mHasManagedProfile.set(hasWorkProfile); - mFragment.mManagedProfileBeingDetected.set(false); - mFragment.refreshSyncSettingsUI(); - } - } - - private void enableSyncPreferences(final String[] accountsForLogin, - final String currentAccountName) { - if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) { - return; - } - mAccountSwitcher.setEnabled(true); - - mEnableSyncPreference.setEnabled(true); - mEnableSyncPreference.setOnPreferenceClickListener(mEnableSyncClickListener); - - mSyncNowPreference.setEnabled(true); - mSyncNowPreference.setOnPreferenceClickListener(mSyncNowListener); - - mClearSyncDataPreference.setEnabled(true); - mClearSyncDataPreference.setOnPreferenceClickListener(mDeleteSyncDataListener); - - if (currentAccountName != null) { - mAccountSwitcher.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(final Preference preference) { - if (accountsForLogin.length > 0) { - // TODO: Add addition of account. - createAccountPicker(accountsForLogin, getSignedInAccountName(), - new AccountChangedListener(null)).show(); - } - return true; - } - }); - } - } - - /** - * Two reasons for disable - work profile or no accounts on device. - */ - private void disableSyncPreferences() { - if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) { - return; - } - - mAccountSwitcher.setEnabled(false); - mEnableSyncPreference.setEnabled(false); - mSyncNowPreference.setEnabled(false); - mClearSyncDataPreference.setEnabled(false); - } - - /** - * Called only when ProductionFlag is turned off. - */ - private void removeSyncPreferences() { - removePreference(PREF_ACCCOUNT_SWITCHER); - removePreference(PREF_ENABLE_CLOUD_SYNC); - removePreference(PREF_SYNC_NOW); - removePreference(PREF_CLEAR_SYNC_DATA); - } - - @Override - public void onResume() { - super.onResume(); - refreshSyncSettingsUI(); - } - - @Override - public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - if (TextUtils.equals(key, PREF_ACCOUNT_NAME)) { - refreshSyncSettingsUI(); - } else if (TextUtils.equals(key, PREF_ENABLE_CLOUD_SYNC)) { - mEnableSyncPreference = (TwoStatePreference) findPreference(PREF_ENABLE_SYNC_NOW); - final boolean syncEnabled = prefs.getBoolean(PREF_ENABLE_CLOUD_SYNC, false); - if (isSyncEnabled()) { - mEnableSyncPreference.setSummary(getString(R.string.cloud_sync_summary)); - } else { - mEnableSyncPreference.setSummary(getString(R.string.cloud_sync_summary_disabled)); - } - AccountStateChangedListener.onSyncPreferenceChanged(getSignedInAccountName(), - syncEnabled); - } - } - - /** - * Checks different states like whether account is present or managed profile is present - * and sets the sync settings accordingly. - */ - private void refreshSyncSettingsUI() { - if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) { - return; - } - boolean hasAccountsPermission = PermissionsUtil.checkAllPermissionsGranted( - getActivity(), Manifest.permission.READ_CONTACTS); - - final String[] accountsForLogin = hasAccountsPermission ? - LoginAccountUtils.getAccountsForLogin(getActivity()) : new String[0]; - final String currentAccount = hasAccountsPermission ? getSignedInAccountName() : null; - - if (hasAccountsPermission && !mManagedProfileBeingDetected.get() && - !mHasManagedProfile.get() && accountsForLogin.length > 0) { - // Sync can be used by user; enable all preferences. - enableSyncPreferences(accountsForLogin, currentAccount); - } else { - // Sync cannot be used by user; disable all preferences. - disableSyncPreferences(); - } - refreshSyncSettingsMessaging(hasAccountsPermission, mManagedProfileBeingDetected.get(), - mHasManagedProfile.get(), accountsForLogin.length > 0, - currentAccount); - } - - /** - * @param hasAccountsPermission whether the app has the permission to read accounts. - * @param managedProfileBeingDetected whether we are in process of determining work profile. - * @param hasManagedProfile whether the device has work profile. - * @param hasAccountsForLogin whether the device has enough accounts for login. - * @param currentAccount the account currently selected in the application. - */ - private void refreshSyncSettingsMessaging(boolean hasAccountsPermission, - boolean managedProfileBeingDetected, - boolean hasManagedProfile, - boolean hasAccountsForLogin, - String currentAccount) { - if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) { - return; - } - - if (!hasAccountsPermission) { - mEnableSyncPreference.setChecked(false); - mEnableSyncPreference.setSummary(getString(R.string.cloud_sync_summary_disabled)); - mAccountSwitcher.setSummary(""); - return; - } else if (managedProfileBeingDetected) { - // If we are determining eligiblity, we show empty summaries. - // Once we have some deterministic result, we set summaries based on different results. - mEnableSyncPreference.setSummary(""); - mAccountSwitcher.setSummary(""); - } else if (hasManagedProfile) { - mEnableSyncPreference.setSummary( - getString(R.string.cloud_sync_summary_disabled_work_profile)); - } else if (!hasAccountsForLogin) { - mEnableSyncPreference.setSummary(getString(R.string.add_account_to_enable_sync)); - } else if (isSyncEnabled()) { - mEnableSyncPreference.setSummary(getString(R.string.cloud_sync_summary)); - } else { - mEnableSyncPreference.setSummary(getString(R.string.cloud_sync_summary_disabled)); - } - - // Set some interdependent settings. - // No account automatically turns off sync. - if (!managedProfileBeingDetected && !hasManagedProfile) { - if (currentAccount != null) { - mAccountSwitcher.setSummary(getString(R.string.account_selected, currentAccount)); - } else { - mEnableSyncPreference.setChecked(false); - mAccountSwitcher.setSummary(getString(R.string.no_accounts_selected)); - } - } - } - - @Nullable - String getSignedInAccountName() { - return getSharedPreferences().getString(LocalSettingsConstants.PREF_ACCOUNT_NAME, null); - } - - boolean isSyncEnabled() { - return getSharedPreferences().getBoolean(PREF_ENABLE_CLOUD_SYNC, false); - } - - /** - * Creates an account picker dialog showing the given accounts in a list and selecting - * the selected account by default. The list of accounts must not be null/empty. - * - * Package-private for testing. - * - * @param accounts list of accounts on the device. - * @param selectedAccount currently selected account - * @param positiveButtonClickListener listener that gets called when positive button is - * clicked - */ - @UsedForTesting - AlertDialog createAccountPicker(final String[] accounts, - final String selectedAccount, - final DialogInterface.OnClickListener positiveButtonClickListener) { - if (accounts == null || accounts.length == 0) { - throw new IllegalArgumentException("List of accounts must not be empty"); - } - - // See if the currently selected account is in the list. - // If it is, the entry is selected, and a sign-out button is provided. - // If it isn't, select the 0th account by default which will get picked up - // if the user presses OK. - int index = 0; - boolean isSignedIn = false; - for (int i = 0; i < accounts.length; i++) { - if (TextUtils.equals(accounts[i], selectedAccount)) { - index = i; - isSignedIn = true; - break; - } - } - final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.account_select_title) - .setSingleChoiceItems(accounts, index, null) - .setPositiveButton(R.string.account_select_ok, positiveButtonClickListener) - .setNegativeButton(R.string.account_select_cancel, null); - if (isSignedIn) { - builder.setNeutralButton(R.string.account_select_sign_out, positiveButtonClickListener); - } - return builder.create(); - } - - /** - * Listener for a account selection changes from the picker. - * Persists/removes the account to/from shared preferences and sets up sync if required. - */ - class AccountChangedListener implements DialogInterface.OnClickListener { - /** - * Represents preference that should be changed based on account chosen. - */ - private TwoStatePreference mDependentPreference; - - AccountChangedListener(final TwoStatePreference dependentPreference) { - mDependentPreference = dependentPreference; - } - - @Override - public void onClick(final DialogInterface dialog, final int which) { - final String oldAccount = getSignedInAccountName(); - switch (which) { - case DialogInterface.BUTTON_POSITIVE: // Signed in - final ListView lv = ((AlertDialog)dialog).getListView(); - final String newAccount = - (String) lv.getItemAtPosition(lv.getCheckedItemPosition()); - getSharedPreferences() - .edit() - .putString(PREF_ACCOUNT_NAME, newAccount) - .apply(); - AccountStateChangedListener.onAccountSignedIn(oldAccount, newAccount); - if (mDependentPreference != null) { - mDependentPreference.setChecked(true); - } - break; - case DialogInterface.BUTTON_NEUTRAL: // Signed out - AccountStateChangedListener.onAccountSignedOut(oldAccount); - getSharedPreferences() - .edit() - .remove(PREF_ACCOUNT_NAME) - .apply(); - break; - } - } - } - - /** - * Listener that initiates the process of sync in the background. - */ - class SyncNowListener implements Preference.OnPreferenceClickListener { - @Override - public boolean onPreferenceClick(final Preference preference) { - AccountStateChangedListener.forceSync(getSignedInAccountName()); - return true; - } - } - - /** - * Listener that initiates the process of deleting user's data from the cloud. - */ - class DeleteSyncDataListener implements Preference.OnPreferenceClickListener { - @Override - public boolean onPreferenceClick(final Preference preference) { - final AlertDialog confirmationDialog = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.clear_sync_data_title) - .setMessage(R.string.clear_sync_data_confirmation) - .setPositiveButton(R.string.clear_sync_data_ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, final int which) { - if (which == DialogInterface.BUTTON_POSITIVE) { - AccountStateChangedListener.forceDelete( - getSignedInAccountName()); - } - } - }) - .setNegativeButton(R.string.cloud_sync_cancel, null /* OnClickListener */) - .create(); - confirmationDialog.show(); - return true; - } - } - - /** - * Listens to events when user clicks on "Enable sync" feature. - */ - class EnableSyncClickListener implements OnShowListener, Preference.OnPreferenceClickListener { - // TODO(cvnguyen): Write tests. - @Override - public boolean onPreferenceClick(final Preference preference) { - final TwoStatePreference syncPreference = (TwoStatePreference) preference; - if (syncPreference.isChecked()) { - // Uncheck for now. - syncPreference.setChecked(false); - - // Show opt-in. - final AlertDialog optInDialog = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.cloud_sync_title) - .setMessage(R.string.cloud_sync_opt_in_text) - .setPositiveButton(R.string.account_select_ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, - final int which) { - if (which == DialogInterface.BUTTON_POSITIVE) { - final Context context = getActivity(); - final String[] accountsForLogin = - LoginAccountUtils.getAccountsForLogin(context); - createAccountPicker(accountsForLogin, - getSignedInAccountName(), - new AccountChangedListener(syncPreference)) - .show(); - } - } - }) - .setNegativeButton(R.string.cloud_sync_cancel, null) - .create(); - optInDialog.setOnShowListener(this); - optInDialog.show(); - } - return true; - } - - @Override - public void onShow(DialogInterface dialog) { - TextView messageView = (TextView) ((AlertDialog) dialog).findViewById( - android.R.id.message); - if (messageView != null) { - messageView.setMovementMethod(LinkMovementMethod.getInstance()); - } - } - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java b/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java deleted file mode 100644 index 4e8a10b1f..000000000 --- a/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.settings; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceFragment; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.latin.RichInputMethodSubtype; -import com.android.inputmethod.latin.RichInputMethodManager; - -import javax.annotation.Nonnull; - -/** - * Utility class for managing additional features settings. - */ -@SuppressWarnings("unused") -public class AdditionalFeaturesSettingUtils { - public static final int ADDITIONAL_FEATURES_SETTINGS_SIZE = 0; - - private AdditionalFeaturesSettingUtils() { - // This utility class is not publicly instantiable. - } - - public static void addAdditionalFeaturesPreferences( - final Context context, final PreferenceFragment settingsFragment) { - // do nothing. - } - - public static void readAdditionalFeaturesPreferencesIntoArray(final Context context, - final SharedPreferences prefs, final int[] additionalFeaturesPreferences) { - // do nothing. - } - - @Nonnull - public static RichInputMethodSubtype createRichInputMethodSubtype( - @Nonnull final RichInputMethodManager imm, - @Nonnull final InputMethodSubtype subtype, - final Context context) { - return new RichInputMethodSubtype(subtype); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java deleted file mode 100644 index a6fb7f1f1..000000000 --- a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.media.AudioManager; -import android.os.Bundle; -import android.preference.ListPreference; -import android.preference.Preference; - -import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SystemBroadcastReceiver; -import com.android.inputmethod.latin.define.ProductionFlags; - -/** - * "Advanced" settings sub screen. - * - * This settings sub screen handles the following advanced preferences. - * - Key popup dismiss delay - * - Keypress vibration duration - * - Keypress sound volume - * - Show app icon - * - Improve keyboard - * - Debug settings - */ -public final class AdvancedSettingsFragment extends SubScreenFragment { - @Override - public void onCreate(final Bundle icicle) { - super.onCreate(icicle); - addPreferencesFromResource(R.xml.prefs_screen_advanced); - - final Resources res = getResources(); - final Context context = getActivity(); - - // When we are called from the Settings application but we are not already running, some - // singleton and utility classes may not have been initialized. We have to call - // initialization method of these classes here. See {@link LatinIME#onCreate()}. - AudioAndHapticFeedbackManager.init(context); - - final SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); - - if (!Settings.isInternal(prefs)) { - removePreference(Settings.SCREEN_DEBUG); - } - - if (!AudioAndHapticFeedbackManager.getInstance().hasVibrator()) { - removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS); - } - - // TODO: consolidate key preview dismiss delay with the key preview animation parameters. - if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) { - removePreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY); - } else { - // TODO: Cleanup this setup. - final ListPreference keyPreviewPopupDismissDelay = - (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY); - final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger( - R.integer.config_key_preview_linger_timeout)); - keyPreviewPopupDismissDelay.setEntries(new String[] { - res.getString(R.string.key_preview_popup_dismiss_no_delay), - res.getString(R.string.key_preview_popup_dismiss_default_delay), - }); - keyPreviewPopupDismissDelay.setEntryValues(new String[] { - "0", - popupDismissDelayDefaultValue - }); - if (null == keyPreviewPopupDismissDelay.getValue()) { - keyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue); - } - keyPreviewPopupDismissDelay.setEnabled( - Settings.readKeyPreviewPopupEnabled(prefs, res)); - } - - setupKeypressVibrationDurationSettings(); - setupKeypressSoundVolumeSettings(); - setupKeyLongpressTimeoutSettings(); - refreshEnablingsOfKeypressSoundAndVibrationSettings(); - } - - @Override - public void onResume() { - super.onResume(); - final SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); - updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY); - } - - @Override - public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - final Resources res = getResources(); - if (key.equals(Settings.PREF_POPUP_ON)) { - setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, - Settings.readKeyPreviewPopupEnabled(prefs, res)); - } else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) { - SystemBroadcastReceiver.toggleAppIcon(getActivity()); - } - updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY); - refreshEnablingsOfKeypressSoundAndVibrationSettings(); - } - - private void refreshEnablingsOfKeypressSoundAndVibrationSettings() { - final SharedPreferences prefs = getSharedPreferences(); - final Resources res = getResources(); - setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS, - Settings.readVibrationEnabled(prefs, res)); - setPreferenceEnabled(Settings.PREF_KEYPRESS_SOUND_VOLUME, - Settings.readKeypressSoundEnabled(prefs, res)); - } - - private void setupKeypressVibrationDurationSettings() { - final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference( - Settings.PREF_VIBRATION_DURATION_SETTINGS); - if (pref == null) { - return; - } - final SharedPreferences prefs = getSharedPreferences(); - final Resources res = getResources(); - pref.setInterface(new SeekBarDialogPreference.ValueProxy() { - @Override - public void writeValue(final int value, final String key) { - prefs.edit().putInt(key, value).apply(); - } - - @Override - public void writeDefaultValue(final String key) { - prefs.edit().remove(key).apply(); - } - - @Override - public int readValue(final String key) { - return Settings.readKeypressVibrationDuration(prefs, res); - } - - @Override - public int readDefaultValue(final String key) { - return Settings.readDefaultKeypressVibrationDuration(res); - } - - @Override - public void feedbackValue(final int value) { - AudioAndHapticFeedbackManager.getInstance().vibrate(value); - } - - @Override - public String getValueText(final int value) { - if (value < 0) { - return res.getString(R.string.settings_system_default); - } - return res.getString(R.string.abbreviation_unit_milliseconds, value); - } - }); - } - - private void setupKeypressSoundVolumeSettings() { - final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference( - Settings.PREF_KEYPRESS_SOUND_VOLUME); - if (pref == null) { - return; - } - final SharedPreferences prefs = getSharedPreferences(); - final Resources res = getResources(); - final AudioManager am = (AudioManager)getActivity().getSystemService(Context.AUDIO_SERVICE); - pref.setInterface(new SeekBarDialogPreference.ValueProxy() { - private static final float PERCENTAGE_FLOAT = 100.0f; - - private float getValueFromPercentage(final int percentage) { - return percentage / PERCENTAGE_FLOAT; - } - - private int getPercentageFromValue(final float floatValue) { - return (int)(floatValue * PERCENTAGE_FLOAT); - } - - @Override - public void writeValue(final int value, final String key) { - prefs.edit().putFloat(key, getValueFromPercentage(value)).apply(); - } - - @Override - public void writeDefaultValue(final String key) { - prefs.edit().remove(key).apply(); - } - - @Override - public int readValue(final String key) { - return getPercentageFromValue(Settings.readKeypressSoundVolume(prefs, res)); - } - - @Override - public int readDefaultValue(final String key) { - return getPercentageFromValue(Settings.readDefaultKeypressSoundVolume(res)); - } - - @Override - public String getValueText(final int value) { - if (value < 0) { - return res.getString(R.string.settings_system_default); - } - return Integer.toString(value); - } - - @Override - public void feedbackValue(final int value) { - am.playSoundEffect( - AudioManager.FX_KEYPRESS_STANDARD, getValueFromPercentage(value)); - } - }); - } - - private void setupKeyLongpressTimeoutSettings() { - final SharedPreferences prefs = getSharedPreferences(); - final Resources res = getResources(); - final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference( - Settings.PREF_KEY_LONGPRESS_TIMEOUT); - if (pref == null) { - return; - } - pref.setInterface(new SeekBarDialogPreference.ValueProxy() { - @Override - public void writeValue(final int value, final String key) { - prefs.edit().putInt(key, value).apply(); - } - - @Override - public void writeDefaultValue(final String key) { - prefs.edit().remove(key).apply(); - } - - @Override - public int readValue(final String key) { - return Settings.readKeyLongpressTimeout(prefs, res); - } - - @Override - public int readDefaultValue(final String key) { - return Settings.readDefaultKeyLongpressTimeout(res); - } - - @Override - public String getValueText(final int value) { - return res.getString(R.string.abbreviation_unit_milliseconds, value); - } - - @Override - public void feedbackValue(final int value) {} - }); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java deleted file mode 100644 index 554edc85c..000000000 --- a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.os.Bundle; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.define.ProductionFlags; - -/** - * "Appearance" settings sub screen. - */ -public final class AppearanceSettingsFragment extends SubScreenFragment { - @Override - public void onCreate(final Bundle icicle) { - super.onCreate(icicle); - addPreferencesFromResource(R.xml.prefs_screen_appearance); - if (!ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED || - Constants.isPhone(Settings.readScreenMetrics(getResources()))) { - removePreference(Settings.PREF_ENABLE_SPLIT_KEYBOARD); - } - } - - @Override - public void onResume() { - super.onResume(); - CustomInputStyleSettingsFragment.updateCustomInputStylesSummary( - findPreference(Settings.PREF_CUSTOM_INPUT_STYLES)); - ThemeSettingsFragment.updateKeyboardThemeSummary(findPreference(Settings.SCREEN_THEME)); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java deleted file mode 100644 index dfe899ece..000000000 --- a/java/src/com/android/inputmethod/latin/settings/CorrectionSettingsFragment.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Build; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.SwitchPreference; -import android.text.TextUtils; - -import com.android.inputmethod.dictionarypack.DictionarySettingsActivity; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.permissions.PermissionsManager; -import com.android.inputmethod.latin.permissions.PermissionsUtil; -import com.android.inputmethod.latin.userdictionary.UserDictionaryList; -import com.android.inputmethod.latin.userdictionary.UserDictionarySettings; - -import java.util.TreeSet; - -/** - * "Text correction" settings sub screen. - * - * This settings sub screen handles the following text correction preferences. - * - Personal dictionary - * - Add-on dictionaries - * - Block offensive words - * - Auto-correction - * - Show correction suggestions - * - Personalized suggestions - * - Suggest Contact names - * - Next-word suggestions - */ -public final class CorrectionSettingsFragment extends SubScreenFragment - implements SharedPreferences.OnSharedPreferenceChangeListener, - PermissionsManager.PermissionsResultCallback { - - private static final boolean DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS = false; - private static final boolean USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS = - DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS - || Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2; - - private SwitchPreference mUseContactsPreference; - - @Override - public void onCreate(final Bundle icicle) { - super.onCreate(icicle); - addPreferencesFromResource(R.xml.prefs_screen_correction); - - final Context context = getActivity(); - final PackageManager pm = context.getPackageManager(); - - final Preference dictionaryLink = findPreference(Settings.PREF_CONFIGURE_DICTIONARIES_KEY); - final Intent intent = dictionaryLink.getIntent(); - intent.setClassName(context.getPackageName(), DictionarySettingsActivity.class.getName()); - final int number = pm.queryIntentActivities(intent, 0).size(); - if (0 >= number) { - removePreference(Settings.PREF_CONFIGURE_DICTIONARIES_KEY); - } - - final Preference editPersonalDictionary = - findPreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY); - final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent(); - final ResolveInfo ri = USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS ? null - : pm.resolveActivity( - editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY); - if (ri == null) { - overwriteUserDictionaryPreference(editPersonalDictionary); - } - - mUseContactsPreference = (SwitchPreference) findPreference(Settings.PREF_KEY_USE_CONTACTS_DICT); - turnOffUseContactsIfNoPermission(); - } - - private void overwriteUserDictionaryPreference(final Preference userDictionaryPreference) { - final Activity activity = getActivity(); - final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity); - if (null == localeList) { - // The locale list is null if and only if the user dictionary service is - // not present or disabled. In this case we need to remove the preference. - getPreferenceScreen().removePreference(userDictionaryPreference); - } else if (localeList.size() <= 1) { - userDictionaryPreference.setFragment(UserDictionarySettings.class.getName()); - // If the size of localeList is 0, we don't set the locale parameter in the - // extras. This will be interpreted by the UserDictionarySettings class as - // meaning "the current locale". - // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesSet() - // the locale list always has at least one element, since it always includes the current - // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesSet(). - if (localeList.size() == 1) { - final String locale = (String)localeList.toArray()[0]; - userDictionaryPreference.getExtras().putString("locale", locale); - } - } else { - userDictionaryPreference.setFragment(UserDictionaryList.class.getName()); - } - } - - @Override - public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) { - if (!TextUtils.equals(key, Settings.PREF_KEY_USE_CONTACTS_DICT)) { - return; - } - if (!sharedPreferences.getBoolean(key, false)) { - // don't care if the preference is turned off. - return; - } - - // Check for permissions. - if (PermissionsUtil.checkAllPermissionsGranted( - getActivity() /* context */, Manifest.permission.READ_CONTACTS)) { - return; // all permissions granted, no need to request permissions. - } - - PermissionsManager.get(getActivity() /* context */).requestPermissions( - this /* PermissionsResultCallback */, - getActivity() /* activity */, - Manifest.permission.READ_CONTACTS); - } - - @Override - public void onRequestPermissionsResult(boolean allGranted) { - turnOffUseContactsIfNoPermission(); - } - - private void turnOffUseContactsIfNoPermission() { - if (!PermissionsUtil.checkAllPermissionsGranted( - getActivity(), Manifest.permission.READ_CONTACTS)) { - mUseContactsPreference.setChecked(false); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java deleted file mode 100644 index 44a98c1be..000000000 --- a/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Parcel; -import android.os.Parcelable; -import android.preference.DialogPreference; -import android.preference.Preference; -import android.util.Log; -import android.view.View; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodSubtype; -import android.widget.ArrayAdapter; -import android.widget.Spinner; -import android.widget.SpinnerAdapter; - -import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; -import com.android.inputmethod.compat.ViewCompatUtils; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodManager; -import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; - -import java.util.TreeSet; - -final class CustomInputStylePreference extends DialogPreference - implements DialogInterface.OnCancelListener { - private static final boolean DEBUG_SUBTYPE_ID = false; - - interface Listener { - public void onRemoveCustomInputStyle(CustomInputStylePreference stylePref); - public void onSaveCustomInputStyle(CustomInputStylePreference stylePref); - public void onAddCustomInputStyle(CustomInputStylePreference stylePref); - public SubtypeLocaleAdapter getSubtypeLocaleAdapter(); - public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter(); - } - - private static final String KEY_PREFIX = "subtype_pref_"; - private static final String KEY_NEW_SUBTYPE = KEY_PREFIX + "new"; - - private InputMethodSubtype mSubtype; - private InputMethodSubtype mPreviousSubtype; - - private final Listener mProxy; - private Spinner mSubtypeLocaleSpinner; - private Spinner mKeyboardLayoutSetSpinner; - - public static CustomInputStylePreference newIncompleteSubtypePreference( - final Context context, final Listener proxy) { - return new CustomInputStylePreference(context, null, proxy); - } - - public CustomInputStylePreference(final Context context, final InputMethodSubtype subtype, - final Listener proxy) { - super(context, null); - setDialogLayoutResource(R.layout.additional_subtype_dialog); - setPersistent(false); - mProxy = proxy; - setSubtype(subtype); - } - - public void show() { - showDialog(null); - } - - public final boolean isIncomplete() { - return mSubtype == null; - } - - public InputMethodSubtype getSubtype() { - return mSubtype; - } - - public void setSubtype(final InputMethodSubtype subtype) { - mPreviousSubtype = mSubtype; - mSubtype = subtype; - if (isIncomplete()) { - setTitle(null); - setDialogTitle(R.string.add_style); - setKey(KEY_NEW_SUBTYPE); - } else { - final String displayName = - SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype); - setTitle(displayName); - setDialogTitle(displayName); - setKey(KEY_PREFIX + subtype.getLocale() + "_" - + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype)); - } - } - - public void revert() { - setSubtype(mPreviousSubtype); - } - - public boolean hasBeenModified() { - return mSubtype != null && !mSubtype.equals(mPreviousSubtype); - } - - @Override - protected View onCreateDialogView() { - final View v = super.onCreateDialogView(); - mSubtypeLocaleSpinner = (Spinner) v.findViewById(R.id.subtype_locale_spinner); - mSubtypeLocaleSpinner.setAdapter(mProxy.getSubtypeLocaleAdapter()); - mKeyboardLayoutSetSpinner = (Spinner) v.findViewById(R.id.keyboard_layout_set_spinner); - mKeyboardLayoutSetSpinner.setAdapter(mProxy.getKeyboardLayoutSetAdapter()); - // All keyboard layout names are in the Latin script and thus left to right. That means - // the view would align them to the left even if the system locale is RTL, but that - // would look strange. To fix this, we align them to the view's start, which will be - // natural for any direction. - ViewCompatUtils.setTextAlignment( - mKeyboardLayoutSetSpinner, ViewCompatUtils.TEXT_ALIGNMENT_VIEW_START); - return v; - } - - @Override - protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) { - builder.setCancelable(true).setOnCancelListener(this); - if (isIncomplete()) { - builder.setPositiveButton(R.string.add, this) - .setNegativeButton(android.R.string.cancel, this); - } else { - builder.setPositiveButton(R.string.save, this) - .setNeutralButton(android.R.string.cancel, this) - .setNegativeButton(R.string.remove, this); - final SubtypeLocaleItem localeItem = new SubtypeLocaleItem(mSubtype); - final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(mSubtype); - setSpinnerPosition(mSubtypeLocaleSpinner, localeItem); - setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem); - } - } - - private static void setSpinnerPosition(final Spinner spinner, final Object itemToSelect) { - final SpinnerAdapter adapter = spinner.getAdapter(); - final int count = adapter.getCount(); - for (int i = 0; i < count; i++) { - final Object item = spinner.getItemAtPosition(i); - if (item.equals(itemToSelect)) { - spinner.setSelection(i); - return; - } - } - } - - @Override - public void onCancel(final DialogInterface dialog) { - if (isIncomplete()) { - mProxy.onRemoveCustomInputStyle(this); - } - } - - @Override - public void onClick(final DialogInterface dialog, final int which) { - super.onClick(dialog, which); - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - final boolean isEditing = !isIncomplete(); - final SubtypeLocaleItem locale = - (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem(); - final KeyboardLayoutSetItem layout = - (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem(); - final InputMethodSubtype subtype = - AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( - locale.mLocaleString, layout.mLayoutName); - setSubtype(subtype); - notifyChanged(); - if (isEditing) { - mProxy.onSaveCustomInputStyle(this); - } else { - mProxy.onAddCustomInputStyle(this); - } - break; - case DialogInterface.BUTTON_NEUTRAL: - // Nothing to do - break; - case DialogInterface.BUTTON_NEGATIVE: - mProxy.onRemoveCustomInputStyle(this); - break; - } - } - - @Override - protected Parcelable onSaveInstanceState() { - final Parcelable superState = super.onSaveInstanceState(); - final Dialog dialog = getDialog(); - if (dialog == null || !dialog.isShowing()) { - return superState; - } - - final SavedState myState = new SavedState(superState); - myState.mSubtype = mSubtype; - return myState; - } - - @Override - protected void onRestoreInstanceState(final Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - - final SavedState myState = (SavedState) state; - super.onRestoreInstanceState(myState.getSuperState()); - setSubtype(myState.mSubtype); - } - - static final class SavedState extends Preference.BaseSavedState { - InputMethodSubtype mSubtype; - - public SavedState(final Parcelable superState) { - super(superState); - } - - @Override - public void writeToParcel(final Parcel dest, final int flags) { - super.writeToParcel(dest, flags); - dest.writeParcelable(mSubtype, 0); - } - - public SavedState(final Parcel source) { - super(source); - mSubtype = (InputMethodSubtype)source.readParcelable(null); - } - - @SuppressWarnings("hiding") - public static final Parcelable.Creator<SavedState> CREATOR = - new Parcelable.Creator<SavedState>() { - @Override - public SavedState createFromParcel(final Parcel source) { - return new SavedState(source); - } - - @Override - public SavedState[] newArray(final int size) { - return new SavedState[size]; - } - }; - } - - static final class SubtypeLocaleItem implements Comparable<SubtypeLocaleItem> { - public final String mLocaleString; - private final String mDisplayName; - - public SubtypeLocaleItem(final InputMethodSubtype subtype) { - mLocaleString = subtype.getLocale(); - mDisplayName = SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale( - mLocaleString); - } - - // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()} - // to get display name. - @Override - public String toString() { - return mDisplayName; - } - - @Override - public int compareTo(final SubtypeLocaleItem o) { - return mLocaleString.compareTo(o.mLocaleString); - } - } - - static final class SubtypeLocaleAdapter extends ArrayAdapter<SubtypeLocaleItem> { - private static final String TAG_SUBTYPE = SubtypeLocaleAdapter.class.getSimpleName(); - - public SubtypeLocaleAdapter(final Context context) { - super(context, android.R.layout.simple_spinner_item); - setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - - final TreeSet<SubtypeLocaleItem> items = new TreeSet<>(); - final InputMethodInfo imi = RichInputMethodManager.getInstance() - .getInputMethodInfoOfThisIme(); - final int count = imi.getSubtypeCount(); - for (int i = 0; i < count; i++) { - final InputMethodSubtype subtype = imi.getSubtypeAt(i); - if (DEBUG_SUBTYPE_ID) { - Log.d(TAG_SUBTYPE, String.format("%-6s 0x%08x %11d %s", - subtype.getLocale(), subtype.hashCode(), subtype.hashCode(), - SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype))); - } - if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) { - items.add(new SubtypeLocaleItem(subtype)); - } - } - // TODO: Should filter out already existing combinations of locale and layout. - addAll(items); - } - } - - static final class KeyboardLayoutSetItem { - public final String mLayoutName; - private final String mDisplayName; - - public KeyboardLayoutSetItem(final InputMethodSubtype subtype) { - mLayoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); - mDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype); - } - - // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()} - // to get display name. - @Override - public String toString() { - return mDisplayName; - } - } - - static final class KeyboardLayoutSetAdapter extends ArrayAdapter<KeyboardLayoutSetItem> { - public KeyboardLayoutSetAdapter(final Context context) { - super(context, android.R.layout.simple_spinner_item); - setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - - final String[] predefinedKeyboardLayoutSet = context.getResources().getStringArray( - R.array.predefined_layouts); - // TODO: Should filter out already existing combinations of locale and layout. - for (final String layout : predefinedKeyboardLayoutSet) { - // This is a placeholder for a subtype with NO_LANGUAGE, only for display. - final InputMethodSubtype subtype = - AdditionalSubtypeUtils.createDummyAdditionalSubtype( - SubtypeLocaleUtils.NO_LANGUAGE, layout); - add(new KeyboardLayoutSetItem(subtype)); - } - } - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java deleted file mode 100644 index 56e8f1623..000000000 --- a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.settings; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.preference.PreferenceGroup; -import androidx.core.view.ViewCompat; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.InputMethodSubtype; -import android.widget.Toast; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodManager; -import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; -import com.android.inputmethod.latin.utils.DialogUtils; -import com.android.inputmethod.latin.utils.IntentUtils; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; - -import java.util.ArrayList; - -public final class CustomInputStyleSettingsFragment extends PreferenceFragment - implements CustomInputStylePreference.Listener { - private static final String TAG = CustomInputStyleSettingsFragment.class.getSimpleName(); - // Note: We would like to turn this debug flag true in order to see what input styles are - // defined in a bug-report. - private static final boolean DEBUG_CUSTOM_INPUT_STYLES = true; - - private RichInputMethodManager mRichImm; - private SharedPreferences mPrefs; - private CustomInputStylePreference.SubtypeLocaleAdapter mSubtypeLocaleAdapter; - private CustomInputStylePreference.KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter; - - private boolean mIsAddingNewSubtype; - private AlertDialog mSubtypeEnablerNotificationDialog; - private String mSubtypePreferenceKeyForSubtypeEnabler; - - private static final String KEY_IS_ADDING_NEW_SUBTYPE = "is_adding_new_subtype"; - private static final String KEY_IS_SUBTYPE_ENABLER_NOTIFICATION_DIALOG_OPEN = - "is_subtype_enabler_notification_dialog_open"; - private static final String KEY_SUBTYPE_FOR_SUBTYPE_ENABLER = "subtype_for_subtype_enabler"; - - public CustomInputStyleSettingsFragment() { - // Empty constructor for fragment generation. - } - - static void updateCustomInputStylesSummary(final Preference pref) { - // When we are called from the Settings application but we are not already running, some - // singleton and utility classes may not have been initialized. We have to call - // initialization method of these classes here. See {@link LatinIME#onCreate()}. - SubtypeLocaleUtils.init(pref.getContext()); - - final Resources res = pref.getContext().getResources(); - final SharedPreferences prefs = pref.getSharedPreferences(); - final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res); - final InputMethodSubtype[] subtypes = - AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype); - final ArrayList<String> subtypeNames = new ArrayList<>(); - for (final InputMethodSubtype subtype : subtypes) { - subtypeNames.add(SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)); - } - // TODO: A delimiter of custom input styles should be localized. - pref.setSummary(TextUtils.join(", ", subtypeNames)); - } - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mPrefs = getPreferenceManager().getSharedPreferences(); - RichInputMethodManager.init(getActivity()); - mRichImm = RichInputMethodManager.getInstance(); - addPreferencesFromResource(R.xml.additional_subtype_settings); - setHasOptionsMenu(true); - } - - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - final View view = super.onCreateView(inflater, container, savedInstanceState); - // For correct display in RTL locales, we need to set the layout direction of the - // fragment's top view. - ViewCompat.setLayoutDirection(view, ViewCompat.LAYOUT_DIRECTION_LOCALE); - return view; - } - - @Override - public void onActivityCreated(final Bundle savedInstanceState) { - final Context context = getActivity(); - mSubtypeLocaleAdapter = new CustomInputStylePreference.SubtypeLocaleAdapter(context); - mKeyboardLayoutSetAdapter = - new CustomInputStylePreference.KeyboardLayoutSetAdapter(context); - - final String prefSubtypes = - Settings.readPrefAdditionalSubtypes(mPrefs, getResources()); - if (DEBUG_CUSTOM_INPUT_STYLES) { - Log.i(TAG, "Load custom input styles: " + prefSubtypes); - } - setPrefSubtypes(prefSubtypes, context); - - mIsAddingNewSubtype = (savedInstanceState != null) - && savedInstanceState.containsKey(KEY_IS_ADDING_NEW_SUBTYPE); - if (mIsAddingNewSubtype) { - getPreferenceScreen().addPreference( - CustomInputStylePreference.newIncompleteSubtypePreference(context, this)); - } - - super.onActivityCreated(savedInstanceState); - - if (savedInstanceState != null && savedInstanceState.containsKey( - KEY_IS_SUBTYPE_ENABLER_NOTIFICATION_DIALOG_OPEN)) { - mSubtypePreferenceKeyForSubtypeEnabler = savedInstanceState.getString( - KEY_SUBTYPE_FOR_SUBTYPE_ENABLER); - mSubtypeEnablerNotificationDialog = createDialog(); - mSubtypeEnablerNotificationDialog.show(); - } - } - - @Override - public void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - if (mIsAddingNewSubtype) { - outState.putBoolean(KEY_IS_ADDING_NEW_SUBTYPE, true); - } - if (mSubtypeEnablerNotificationDialog != null - && mSubtypeEnablerNotificationDialog.isShowing()) { - outState.putBoolean(KEY_IS_SUBTYPE_ENABLER_NOTIFICATION_DIALOG_OPEN, true); - outState.putString( - KEY_SUBTYPE_FOR_SUBTYPE_ENABLER, mSubtypePreferenceKeyForSubtypeEnabler); - } - } - - @Override - public void onRemoveCustomInputStyle(final CustomInputStylePreference stylePref) { - mIsAddingNewSubtype = false; - final PreferenceGroup group = getPreferenceScreen(); - group.removePreference(stylePref); - mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); - } - - @Override - public void onSaveCustomInputStyle(final CustomInputStylePreference stylePref) { - final InputMethodSubtype subtype = stylePref.getSubtype(); - if (!stylePref.hasBeenModified()) { - return; - } - if (findDuplicatedSubtype(subtype) == null) { - mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); - return; - } - - // Saved subtype is duplicated. - final PreferenceGroup group = getPreferenceScreen(); - group.removePreference(stylePref); - stylePref.revert(); - group.addPreference(stylePref); - showSubtypeAlreadyExistsToast(subtype); - } - - @Override - public void onAddCustomInputStyle(final CustomInputStylePreference stylePref) { - mIsAddingNewSubtype = false; - final InputMethodSubtype subtype = stylePref.getSubtype(); - if (findDuplicatedSubtype(subtype) == null) { - mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); - mSubtypePreferenceKeyForSubtypeEnabler = stylePref.getKey(); - mSubtypeEnablerNotificationDialog = createDialog(); - mSubtypeEnablerNotificationDialog.show(); - return; - } - - // Newly added subtype is duplicated. - final PreferenceGroup group = getPreferenceScreen(); - group.removePreference(stylePref); - showSubtypeAlreadyExistsToast(subtype); - } - - @Override - public CustomInputStylePreference.SubtypeLocaleAdapter getSubtypeLocaleAdapter() { - return mSubtypeLocaleAdapter; - } - - @Override - public CustomInputStylePreference.KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter() { - return mKeyboardLayoutSetAdapter; - } - - private void showSubtypeAlreadyExistsToast(final InputMethodSubtype subtype) { - final Context context = getActivity(); - final Resources res = context.getResources(); - final String message = res.getString(R.string.custom_input_style_already_exists, - SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)); - Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); - } - - private InputMethodSubtype findDuplicatedSubtype(final InputMethodSubtype subtype) { - final String localeString = subtype.getLocale(); - final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); - return mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( - localeString, keyboardLayoutSetName); - } - - private AlertDialog createDialog() { - final String imeId = mRichImm.getInputMethodIdOfThisIme(); - final AlertDialog.Builder builder = new AlertDialog.Builder( - DialogUtils.getPlatformDialogThemeContext(getActivity())); - builder.setTitle(R.string.custom_input_styles_title) - .setMessage(R.string.custom_input_style_note_message) - .setNegativeButton(R.string.not_now, null) - .setPositiveButton(R.string.enable, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final Intent intent = IntentUtils.getInputLanguageSelectionIntent( - imeId, - Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - // TODO: Add newly adding subtype to extra value of the intent as a hint - // for the input language selection activity. - // intent.putExtra("newlyAddedSubtype", subtypePref.getSubtype()); - startActivity(intent); - } - }); - - return builder.create(); - } - - private void setPrefSubtypes(final String prefSubtypes, final Context context) { - final PreferenceGroup group = getPreferenceScreen(); - group.removeAll(); - final InputMethodSubtype[] subtypesArray = - AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtypes); - for (final InputMethodSubtype subtype : subtypesArray) { - final CustomInputStylePreference pref = - new CustomInputStylePreference(context, subtype, this); - group.addPreference(pref); - } - } - - private InputMethodSubtype[] getSubtypes() { - final PreferenceGroup group = getPreferenceScreen(); - final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); - final int count = group.getPreferenceCount(); - for (int i = 0; i < count; i++) { - final Preference pref = group.getPreference(i); - if (pref instanceof CustomInputStylePreference) { - final CustomInputStylePreference subtypePref = (CustomInputStylePreference)pref; - // We should not save newly adding subtype to preference because it is incomplete. - if (subtypePref.isIncomplete()) continue; - subtypes.add(subtypePref.getSubtype()); - } - } - return subtypes.toArray(new InputMethodSubtype[subtypes.size()]); - } - - @Override - public void onPause() { - super.onPause(); - final String oldSubtypes = Settings.readPrefAdditionalSubtypes(mPrefs, getResources()); - final InputMethodSubtype[] subtypes = getSubtypes(); - final String prefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(subtypes); - if (DEBUG_CUSTOM_INPUT_STYLES) { - Log.i(TAG, "Save custom input styles: " + prefSubtypes); - } - if (prefSubtypes.equals(oldSubtypes)) { - return; - } - Settings.writePrefAdditionalSubtypes(mPrefs, prefSubtypes); - mRichImm.setAdditionalInputMethodSubtypes(subtypes); - } - - @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - inflater.inflate(R.menu.add_style, menu); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - final int itemId = item.getItemId(); - if (itemId == R.id.action_add_style) { - final CustomInputStylePreference newSubtype = - CustomInputStylePreference.newIncompleteSubtypePreference(getActivity(), this); - getPreferenceScreen().addPreference(newSubtype); - newSubtype.show(); - mIsAddingNewSubtype = true; - return true; - } - return super.onOptionsItemSelected(item); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java deleted file mode 100644 index 6fffb8e9d..000000000 --- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.inputmethod.latin.settings; - -/** - * Debug settings for the application. - * - * Note: Even though these settings are stored in the default shared preferences file, - * they shouldn't be restored across devices. - * If a new key is added here, it should also be blacklisted for restore in - * {@link LocalSettingsConstants}. - */ -public final class DebugSettings { - public static final String PREF_DEBUG_MODE = "debug_mode"; - public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch"; - public static final String PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS = - "pref_has_custom_key_preview_animation_params"; - public static final String PREF_RESIZE_KEYBOARD = "pref_resize_keyboard"; - public static final String PREF_KEYBOARD_HEIGHT_SCALE = "pref_keyboard_height_scale"; - public static final String PREF_KEY_PREVIEW_DISMISS_DURATION = - "pref_key_preview_dismiss_duration"; - public static final String PREF_KEY_PREVIEW_DISMISS_END_X_SCALE = - "pref_key_preview_dismiss_end_x_scale"; - public static final String PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE = - "pref_key_preview_dismiss_end_y_scale"; - public static final String PREF_KEY_PREVIEW_SHOW_UP_DURATION = - "pref_key_preview_show_up_duration"; - public static final String PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE = - "pref_key_preview_show_up_start_x_scale"; - public static final String PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE = - "pref_key_preview_show_up_start_y_scale"; - public static final String PREF_SHOULD_SHOW_LXX_SUGGESTION_UI = - "pref_should_show_lxx_suggestion_ui"; - public static final String PREF_SLIDING_KEY_INPUT_PREVIEW = "pref_sliding_key_input_preview"; - - private DebugSettings() { - // This class is not publicly instantiable. - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java deleted file mode 100644 index 37855377d..000000000 --- a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.os.Bundle; -import android.os.Process; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceGroup; -import android.preference.TwoStatePreference; - -import com.android.inputmethod.latin.DictionaryDumpBroadcastReceiver; -import com.android.inputmethod.latin.DictionaryFacilitatorImpl; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.ApplicationUtils; -import com.android.inputmethod.latin.utils.ResourceUtils; - -import java.util.Locale; - -/** - * "Debug mode" settings sub screen. - * - * This settings sub screen handles a several preference options for debugging. - */ -public final class DebugSettingsFragment extends SubScreenFragment - implements OnPreferenceClickListener { - private static final String PREF_KEY_DUMP_DICTS = "pref_key_dump_dictionaries"; - private static final String PREF_KEY_DUMP_DICT_PREFIX = "pref_key_dump_dictionaries"; - - private boolean mServiceNeedsRestart = false; - private TwoStatePreference mDebugMode; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - addPreferencesFromResource(R.xml.prefs_screen_debug); - - if (!Settings.SHOULD_SHOW_LXX_SUGGESTION_UI) { - removePreference(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI); - } - - final PreferenceGroup dictDumpPreferenceGroup = - (PreferenceGroup)findPreference(PREF_KEY_DUMP_DICTS); - for (final String dictName : DictionaryFacilitatorImpl.DICT_TYPE_TO_CLASS.keySet()) { - final Preference pref = new DictDumpPreference(getActivity(), dictName); - pref.setOnPreferenceClickListener(this); - dictDumpPreferenceGroup.addPreference(pref); - } - final Resources res = getResources(); - setupKeyPreviewAnimationDuration(DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION, - res.getInteger(R.integer.config_key_preview_show_up_duration)); - setupKeyPreviewAnimationDuration(DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION, - res.getInteger(R.integer.config_key_preview_dismiss_duration)); - final float defaultKeyPreviewShowUpStartScale = ResourceUtils.getFloatFromFraction( - res, R.fraction.config_key_preview_show_up_start_scale); - final float defaultKeyPreviewDismissEndScale = ResourceUtils.getFloatFromFraction( - res, R.fraction.config_key_preview_dismiss_end_scale); - setupKeyPreviewAnimationScale(DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE, - defaultKeyPreviewShowUpStartScale); - setupKeyPreviewAnimationScale(DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE, - defaultKeyPreviewShowUpStartScale); - setupKeyPreviewAnimationScale(DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_X_SCALE, - defaultKeyPreviewDismissEndScale); - setupKeyPreviewAnimationScale(DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE, - defaultKeyPreviewDismissEndScale); - setupKeyboardHeight( - DebugSettings.PREF_KEYBOARD_HEIGHT_SCALE, SettingsValues.DEFAULT_SIZE_SCALE); - - mServiceNeedsRestart = false; - mDebugMode = (TwoStatePreference) findPreference(DebugSettings.PREF_DEBUG_MODE); - updateDebugMode(); - } - - private static class DictDumpPreference extends Preference { - public final String mDictName; - - public DictDumpPreference(final Context context, final String dictName) { - super(context); - setKey(PREF_KEY_DUMP_DICT_PREFIX + dictName); - setTitle("Dump " + dictName + " dictionary"); - mDictName = dictName; - } - } - - @Override - public boolean onPreferenceClick(final Preference pref) { - final Context context = getActivity(); - if (pref instanceof DictDumpPreference) { - final DictDumpPreference dictDumpPref = (DictDumpPreference)pref; - final String dictName = dictDumpPref.mDictName; - final Intent intent = new Intent( - DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION); - intent.putExtra(DictionaryDumpBroadcastReceiver.DICTIONARY_NAME_KEY, dictName); - context.sendBroadcast(intent); - return true; - } - return true; - } - - @Override - public void onStop() { - super.onStop(); - if (mServiceNeedsRestart) { - Process.killProcess(Process.myPid()); - } - } - - @Override - public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - if (key.equals(DebugSettings.PREF_DEBUG_MODE) && mDebugMode != null) { - mDebugMode.setChecked(prefs.getBoolean(DebugSettings.PREF_DEBUG_MODE, false)); - updateDebugMode(); - mServiceNeedsRestart = true; - return; - } - if (key.equals(DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH)) { - mServiceNeedsRestart = true; - return; - } - } - - private void updateDebugMode() { - boolean isDebugMode = mDebugMode.isChecked(); - final String version = getString( - R.string.version_text, ApplicationUtils.getVersionName(getActivity())); - if (!isDebugMode) { - mDebugMode.setTitle(version); - mDebugMode.setSummary(null); - } else { - mDebugMode.setTitle(getString(R.string.prefs_debug_mode)); - mDebugMode.setSummary(version); - } - } - - private void setupKeyPreviewAnimationScale(final String prefKey, final float defaultValue) { - final SharedPreferences prefs = getSharedPreferences(); - final Resources res = getResources(); - final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(prefKey); - if (pref == null) { - return; - } - pref.setInterface(new SeekBarDialogPreference.ValueProxy() { - private static final float PERCENTAGE_FLOAT = 100.0f; - - private float getValueFromPercentage(final int percentage) { - return percentage / PERCENTAGE_FLOAT; - } - - private int getPercentageFromValue(final float floatValue) { - return (int)(floatValue * PERCENTAGE_FLOAT); - } - - @Override - public void writeValue(final int value, final String key) { - prefs.edit().putFloat(key, getValueFromPercentage(value)).apply(); - } - - @Override - public void writeDefaultValue(final String key) { - prefs.edit().remove(key).apply(); - } - - @Override - public int readValue(final String key) { - return getPercentageFromValue( - Settings.readKeyPreviewAnimationScale(prefs, key, defaultValue)); - } - - @Override - public int readDefaultValue(final String key) { - return getPercentageFromValue(defaultValue); - } - - @Override - public String getValueText(final int value) { - if (value < 0) { - return res.getString(R.string.settings_system_default); - } - return String.format(Locale.ROOT, "%d%%", value); - } - - @Override - public void feedbackValue(final int value) {} - }); - } - - private void setupKeyPreviewAnimationDuration(final String prefKey, final int defaultValue) { - final SharedPreferences prefs = getSharedPreferences(); - final Resources res = getResources(); - final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(prefKey); - if (pref == null) { - return; - } - pref.setInterface(new SeekBarDialogPreference.ValueProxy() { - @Override - public void writeValue(final int value, final String key) { - prefs.edit().putInt(key, value).apply(); - } - - @Override - public void writeDefaultValue(final String key) { - prefs.edit().remove(key).apply(); - } - - @Override - public int readValue(final String key) { - return Settings.readKeyPreviewAnimationDuration(prefs, key, defaultValue); - } - - @Override - public int readDefaultValue(final String key) { - return defaultValue; - } - - @Override - public String getValueText(final int value) { - return res.getString(R.string.abbreviation_unit_milliseconds, value); - } - - @Override - public void feedbackValue(final int value) {} - }); - } - - private void setupKeyboardHeight(final String prefKey, final float defaultValue) { - final SharedPreferences prefs = getSharedPreferences(); - final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(prefKey); - if (pref == null) { - return; - } - pref.setInterface(new SeekBarDialogPreference.ValueProxy() { - private static final float PERCENTAGE_FLOAT = 100.0f; - private float getValueFromPercentage(final int percentage) { - return percentage / PERCENTAGE_FLOAT; - } - - private int getPercentageFromValue(final float floatValue) { - return (int)(floatValue * PERCENTAGE_FLOAT); - } - - @Override - public void writeValue(final int value, final String key) { - prefs.edit().putFloat(key, getValueFromPercentage(value)).apply(); - } - - @Override - public void writeDefaultValue(final String key) { - prefs.edit().remove(key).apply(); - } - - @Override - public int readValue(final String key) { - return getPercentageFromValue(Settings.readKeyboardHeight(prefs, defaultValue)); - } - - @Override - public int readDefaultValue(final String key) { - return getPercentageFromValue(defaultValue); - } - - @Override - public String getValueText(final int value) { - return String.format(Locale.ROOT, "%d%%", value); - } - - @Override - public void feedbackValue(final int value) {} - }); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/GestureSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/GestureSettingsFragment.java deleted file mode 100644 index 22b0655b4..000000000 --- a/java/src/com/android/inputmethod/latin/settings/GestureSettingsFragment.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.os.Bundle; - -import com.android.inputmethod.latin.R; - -/** - * "Gesture typing preferences" settings sub screen. - * - * This settings sub screen handles the following gesture typing preferences. - * - Enable gesture typing - * - Dynamic floating preview - * - Show gesture trail - * - Phrase gesture - */ -public final class GestureSettingsFragment extends SubScreenFragment { - @Override - public void onCreate(final Bundle icicle) { - super.onCreate(icicle); - addPreferencesFromResource(R.xml.prefs_screen_gesture); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java b/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java deleted file mode 100644 index 5c416ab18..000000000 --- a/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -/** - * Collection of device specific preference constants. - */ -public class LocalSettingsConstants { - // Preference file for storing preferences that are tied to a device - // and are not backed up. - public static final String PREFS_FILE = "local_prefs"; - - // Preference key for the current account. - // Do not restore. - public static final String PREF_ACCOUNT_NAME = "pref_account_name"; - // Preference key for enabling cloud sync feature. - // Do not restore. - public static final String PREF_ENABLE_CLOUD_SYNC = "pref_enable_cloud_sync"; - - // List of preference keys to skip from being restored by backup agent. - // These preferences are tied to a device and hence should not be restored. - // e.g. account name. - // Ideally they could have been kept in a separate file that wasn't backed up - // however the preference UI currently only deals with the default - // shared preferences which makes it non-trivial to move these out to - // a different shared preferences file. - public static final String[] PREFS_TO_SKIP_RESTORING = new String[] { - PREF_ACCOUNT_NAME, - PREF_ENABLE_CLOUD_SYNC, - // The debug settings are not restored on a new device. - // If a feature relies on these, it should ensure that the defaults are - // correctly set for it to work on a new device. - DebugSettings.PREF_DEBUG_MODE, - DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, - DebugSettings.PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS, - DebugSettings.PREF_KEYBOARD_HEIGHT_SCALE, - DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION, - DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_X_SCALE, - DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE, - DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION, - DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE, - DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE, - DebugSettings.PREF_RESIZE_KEYBOARD, - DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, - DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW - }; -} diff --git a/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java deleted file mode 100644 index d9858e61f..000000000 --- a/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.os.Build; -import android.os.Bundle; -import android.preference.Preference; - -import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodManager; - -/** - * "Preferences" settings sub screen. - * - * This settings sub screen handles the following input preferences. - * - Auto-capitalization - * - Double-space period - * - Vibrate on keypress - * - Sound on keypress - * - Popup on keypress - * - Voice input key - */ -public final class PreferencesSettingsFragment extends SubScreenFragment { - - private static final boolean VOICE_IME_ENABLED = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; - - @Override - public void onCreate(final Bundle icicle) { - super.onCreate(icicle); - addPreferencesFromResource(R.xml.prefs_screen_preferences); - - final Resources res = getResources(); - final Context context = getActivity(); - - // When we are called from the Settings application but we are not already running, some - // singleton and utility classes may not have been initialized. We have to call - // initialization method of these classes here. See {@link LatinIME#onCreate()}. - RichInputMethodManager.init(context); - - final boolean showVoiceKeyOption = res.getBoolean( - R.bool.config_enable_show_voice_key_option); - if (!showVoiceKeyOption) { - removePreference(Settings.PREF_VOICE_INPUT_KEY); - } - if (!AudioAndHapticFeedbackManager.getInstance().hasVibrator()) { - removePreference(Settings.PREF_VIBRATE_ON); - } - if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) { - removePreference(Settings.PREF_POPUP_ON); - } - - refreshEnablingsOfKeypressSoundAndVibrationSettings(); - } - - @Override - public void onResume() { - super.onResume(); - final Preference voiceInputKeyOption = findPreference(Settings.PREF_VOICE_INPUT_KEY); - if (voiceInputKeyOption != null) { - RichInputMethodManager.getInstance().refreshSubtypeCaches(); - voiceInputKeyOption.setEnabled(VOICE_IME_ENABLED); - voiceInputKeyOption.setSummary(VOICE_IME_ENABLED - ? null : getText(R.string.voice_input_disabled_summary)); - } - } - - @Override - public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - final Resources res = getResources(); - if (key.equals(Settings.PREF_POPUP_ON)) { - setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, - Settings.readKeyPreviewPopupEnabled(prefs, res)); - } - refreshEnablingsOfKeypressSoundAndVibrationSettings(); - } - - private void refreshEnablingsOfKeypressSoundAndVibrationSettings() { - final SharedPreferences prefs = getSharedPreferences(); - final Resources res = getResources(); - setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS, - Settings.readVibrationEnabled(prefs, res)); - setPreferenceEnabled(Settings.PREF_KEYPRESS_SOUND_VOLUME, - Settings.readKeypressSoundEnabled(prefs, res)); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/RadioButtonPreference.java b/java/src/com/android/inputmethod/latin/settings/RadioButtonPreference.java deleted file mode 100644 index 91444604d..000000000 --- a/java/src/com/android/inputmethod/latin/settings/RadioButtonPreference.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.content.Context; -import android.preference.Preference; -import android.util.AttributeSet; -import android.view.View; -import android.widget.RadioButton; - -import com.android.inputmethod.latin.R; - -/** - * Radio Button preference - */ -public class RadioButtonPreference extends Preference { - interface OnRadioButtonClickedListener { - /** - * Called when this preference needs to be saved its state. - * - * @param preference This preference. - */ - public void onRadioButtonClicked(RadioButtonPreference preference); - } - - private boolean mIsSelected; - private RadioButton mRadioButton; - private OnRadioButtonClickedListener mListener; - private final View.OnClickListener mClickListener = new View.OnClickListener() { - @Override - public void onClick(final View v) { - callListenerOnRadioButtonClicked(); - } - }; - - public RadioButtonPreference(final Context context) { - this(context, null); - } - - public RadioButtonPreference(final Context context, final AttributeSet attrs) { - this(context, attrs, android.R.attr.preferenceStyle); - } - - public RadioButtonPreference(final Context context, final AttributeSet attrs, - final int defStyleAttr) { - super(context, attrs, defStyleAttr); - setWidgetLayoutResource(R.layout.radio_button_preference_widget); - } - - public void setOnRadioButtonClickedListener(final OnRadioButtonClickedListener listener) { - mListener = listener; - } - - void callListenerOnRadioButtonClicked() { - if (mListener != null) { - mListener.onRadioButtonClicked(this); - } - } - - @Override - protected void onBindView(final View view) { - super.onBindView(view); - mRadioButton = (RadioButton)view.findViewById(R.id.radio_button); - mRadioButton.setChecked(mIsSelected); - mRadioButton.setOnClickListener(mClickListener); - view.setOnClickListener(mClickListener); - } - - public boolean isSelected() { - return mIsSelected; - } - - public void setSelected(final boolean selected) { - if (selected == mIsSelected) { - return; - } - mIsSelected = selected; - if (mRadioButton != null) { - mRadioButton.setChecked(selected); - } - notifyChanged(); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/SeekBarDialogPreference.java b/java/src/com/android/inputmethod/latin/settings/SeekBarDialogPreference.java deleted file mode 100644 index 802574aa8..000000000 --- a/java/src/com/android/inputmethod/latin/settings/SeekBarDialogPreference.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.settings; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.res.TypedArray; -import android.preference.DialogPreference; -import android.util.AttributeSet; -import android.view.View; -import android.widget.SeekBar; -import android.widget.TextView; - -import com.android.inputmethod.latin.R; - -public final class SeekBarDialogPreference extends DialogPreference - implements SeekBar.OnSeekBarChangeListener { - public interface ValueProxy { - public int readValue(final String key); - public int readDefaultValue(final String key); - public void writeValue(final int value, final String key); - public void writeDefaultValue(final String key); - public String getValueText(final int value); - public void feedbackValue(final int value); - } - - private final int mMaxValue; - private final int mMinValue; - private final int mStepValue; - - private TextView mValueView; - private SeekBar mSeekBar; - - private ValueProxy mValueProxy; - - public SeekBarDialogPreference(final Context context, final AttributeSet attrs) { - super(context, attrs); - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.SeekBarDialogPreference, 0, 0); - mMaxValue = a.getInt(R.styleable.SeekBarDialogPreference_maxValue, 0); - mMinValue = a.getInt(R.styleable.SeekBarDialogPreference_minValue, 0); - mStepValue = a.getInt(R.styleable.SeekBarDialogPreference_stepValue, 0); - a.recycle(); - setDialogLayoutResource(R.layout.seek_bar_dialog); - } - - public void setInterface(final ValueProxy proxy) { - mValueProxy = proxy; - final int value = mValueProxy.readValue(getKey()); - setSummary(mValueProxy.getValueText(value)); - } - - @Override - protected View onCreateDialogView() { - final View view = super.onCreateDialogView(); - mSeekBar = (SeekBar)view.findViewById(R.id.seek_bar_dialog_bar); - mSeekBar.setMax(mMaxValue - mMinValue); - mSeekBar.setOnSeekBarChangeListener(this); - mValueView = (TextView)view.findViewById(R.id.seek_bar_dialog_value); - return view; - } - - private int getProgressFromValue(final int value) { - return value - mMinValue; - } - - private int getValueFromProgress(final int progress) { - return progress + mMinValue; - } - - private int clipValue(final int value) { - final int clippedValue = Math.min(mMaxValue, Math.max(mMinValue, value)); - if (mStepValue <= 1) { - return clippedValue; - } - return clippedValue - (clippedValue % mStepValue); - } - - private int getClippedValueFromProgress(final int progress) { - return clipValue(getValueFromProgress(progress)); - } - - @Override - protected void onBindDialogView(final View view) { - final int value = mValueProxy.readValue(getKey()); - mValueView.setText(mValueProxy.getValueText(value)); - mSeekBar.setProgress(getProgressFromValue(clipValue(value))); - } - - @Override - protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) { - builder.setPositiveButton(android.R.string.ok, this) - .setNegativeButton(android.R.string.cancel, this) - .setNeutralButton(R.string.button_default, this); - } - - @Override - public void onClick(final DialogInterface dialog, final int which) { - super.onClick(dialog, which); - final String key = getKey(); - if (which == DialogInterface.BUTTON_NEUTRAL) { - final int value = mValueProxy.readDefaultValue(key); - setSummary(mValueProxy.getValueText(value)); - mValueProxy.writeDefaultValue(key); - return; - } - if (which == DialogInterface.BUTTON_POSITIVE) { - final int value = getClippedValueFromProgress(mSeekBar.getProgress()); - setSummary(mValueProxy.getValueText(value)); - mValueProxy.writeValue(value, key); - return; - } - } - - @Override - public void onProgressChanged(final SeekBar seekBar, final int progress, - final boolean fromUser) { - final int value = getClippedValueFromProgress(progress); - mValueView.setText(mValueProxy.getValueText(value)); - if (!fromUser) { - mSeekBar.setProgress(getProgressFromValue(value)); - } - } - - @Override - public void onStartTrackingTouch(final SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(final SeekBar seekBar) { - mValueProxy.feedbackValue(getClippedValueFromProgress(seekBar.getProgress())); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java deleted file mode 100644 index 940f1bdfc..000000000 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ /dev/null @@ -1,458 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.settings; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Build; -import android.preference.PreferenceManager; -import android.util.Log; - -import com.android.inputmethod.compat.BuildCompatUtils; -import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; -import com.android.inputmethod.latin.InputAttributes; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; -import com.android.inputmethod.latin.utils.ResourceUtils; -import com.android.inputmethod.latin.utils.RunInLocale; -import com.android.inputmethod.latin.utils.StatsUtils; - -import java.util.Collections; -import java.util.Locale; -import java.util.Set; -import java.util.concurrent.locks.ReentrantLock; - -import javax.annotation.Nonnull; - -public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener { - private static final String TAG = Settings.class.getSimpleName(); - // Settings screens - public static final String SCREEN_ACCOUNTS = "screen_accounts"; - public static final String SCREEN_THEME = "screen_theme"; - public static final String SCREEN_DEBUG = "screen_debug"; - // In the same order as xml/prefs.xml - public static final String PREF_AUTO_CAP = "auto_cap"; - public static final String PREF_VIBRATE_ON = "vibrate_on"; - public static final String PREF_SOUND_ON = "sound_on"; - public static final String PREF_POPUP_ON = "popup_on"; - // PREF_VOICE_MODE_OBSOLETE is obsolete. Use PREF_VOICE_INPUT_KEY instead. - public static final String PREF_VOICE_MODE_OBSOLETE = "voice_mode"; - public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key"; - public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary"; - public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key"; - // PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE is obsolete. Use PREF_AUTO_CORRECTION instead. - public static final String PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE = - "auto_correction_threshold"; - public static final String PREF_AUTO_CORRECTION = "pref_key_auto_correction"; - // PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE is obsolete. Use PREF_SHOW_SUGGESTIONS instead. - public static final String PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE = "show_suggestions_setting"; - public static final String PREF_SHOW_SUGGESTIONS = "show_suggestions"; - public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict"; - public static final String PREF_KEY_USE_PERSONALIZED_DICTS = "pref_key_use_personalized_dicts"; - public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD = - "pref_key_use_double_space_period"; - public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE = - "pref_key_block_potentially_offensive"; - public static final boolean ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS = - BuildCompatUtils.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.KITKAT; - public static final boolean SHOULD_SHOW_LXX_SUGGESTION_UI = - BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.LOLLIPOP; - public static final String PREF_SHOW_LANGUAGE_SWITCH_KEY = - "pref_show_language_switch_key"; - public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST = - "pref_include_other_imes_in_language_switch_list"; - public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles"; - public static final String PREF_ENABLE_SPLIT_KEYBOARD = "pref_split_keyboard"; - // TODO: consolidate key preview dismiss delay with the key preview animation parameters. - public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY = - "pref_key_preview_popup_dismiss_delay"; - public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction"; - public static final String PREF_GESTURE_INPUT = "gesture_input"; - public static final String PREF_VIBRATION_DURATION_SETTINGS = - "pref_vibration_duration_settings"; - public static final String PREF_KEYPRESS_SOUND_VOLUME = "pref_keypress_sound_volume"; - public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout"; - public static final String PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY = - "pref_enable_emoji_alt_physical_key"; - public static final String PREF_GESTURE_PREVIEW_TRAIL = "pref_gesture_preview_trail"; - public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT = - "pref_gesture_floating_preview_text"; - public static final String PREF_SHOW_SETUP_WIZARD_ICON = "pref_show_setup_wizard_icon"; - - public static final String PREF_KEY_IS_INTERNAL = "pref_key_is_internal"; - - public static final String PREF_ENABLE_METRICS_LOGGING = "pref_enable_metrics_logging"; - // This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead. - // This is being used only for the backward compatibility. - private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY = - "pref_suppress_language_switch_key"; - - private static final String PREF_LAST_USED_PERSONALIZATION_TOKEN = - "pref_last_used_personalization_token"; - private static final String PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME = - "pref_last_used_personalization_dict_wiped_time"; - private static final String PREF_CORPUS_HANDLES_FOR_PERSONALIZATION = - "pref_corpus_handles_for_personalization"; - - // Emoji - public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys"; - public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id"; - public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_ID = "last_shown_emoji_category_id"; - - private static final float UNDEFINED_PREFERENCE_VALUE_FLOAT = -1.0f; - private static final int UNDEFINED_PREFERENCE_VALUE_INT = -1; - - private Context mContext; - private Resources mRes; - private SharedPreferences mPrefs; - private SettingsValues mSettingsValues; - private final ReentrantLock mSettingsValuesLock = new ReentrantLock(); - - private static final Settings sInstance = new Settings(); - - public static Settings getInstance() { - return sInstance; - } - - public static void init(final Context context) { - sInstance.onCreate(context); - } - - private Settings() { - // Intentional empty constructor for singleton. - } - - private void onCreate(final Context context) { - mContext = context; - mRes = context.getResources(); - mPrefs = PreferenceManager.getDefaultSharedPreferences(context); - mPrefs.registerOnSharedPreferenceChangeListener(this); - upgradeAutocorrectionSettings(mPrefs, mRes); - } - - public void onDestroy() { - mPrefs.unregisterOnSharedPreferenceChangeListener(this); - } - - @Override - public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - mSettingsValuesLock.lock(); - try { - if (mSettingsValues == null) { - // TODO: Introduce a static function to register this class and ensure that - // loadSettings must be called before "onSharedPreferenceChanged" is called. - Log.w(TAG, "onSharedPreferenceChanged called before loadSettings."); - return; - } - loadSettings(mContext, mSettingsValues.mLocale, mSettingsValues.mInputAttributes); - StatsUtils.onLoadSettings(mSettingsValues); - } finally { - mSettingsValuesLock.unlock(); - } - } - - public void loadSettings(final Context context, final Locale locale, - @Nonnull final InputAttributes inputAttributes) { - mSettingsValuesLock.lock(); - mContext = context; - try { - final SharedPreferences prefs = mPrefs; - final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() { - @Override - protected SettingsValues job(final Resources res) { - return new SettingsValues(context, prefs, res, inputAttributes); - } - }; - mSettingsValues = job.runInLocale(mRes, locale); - } finally { - mSettingsValuesLock.unlock(); - } - } - - // TODO: Remove this method and add proxy method to SettingsValues. - public SettingsValues getCurrent() { - return mSettingsValues; - } - - public boolean isInternal() { - return mSettingsValues.mIsInternal; - } - - public static int readScreenMetrics(final Resources res) { - return res.getInteger(R.integer.config_screen_metrics); - } - - // Accessed from the settings interface, hence public - public static boolean readKeypressSoundEnabled(final SharedPreferences prefs, - final Resources res) { - return prefs.getBoolean(PREF_SOUND_ON, - res.getBoolean(R.bool.config_default_sound_enabled)); - } - - public static boolean readVibrationEnabled(final SharedPreferences prefs, - final Resources res) { - final boolean hasVibrator = AudioAndHapticFeedbackManager.getInstance().hasVibrator(); - return hasVibrator && prefs.getBoolean(PREF_VIBRATE_ON, - res.getBoolean(R.bool.config_default_vibration_enabled)); - } - - public static boolean readAutoCorrectEnabled(final SharedPreferences prefs, - final Resources res) { - return prefs.getBoolean(PREF_AUTO_CORRECTION, true); - } - - public static float readPlausibilityThreshold(final Resources res) { - return Float.parseFloat(res.getString(R.string.plausibility_threshold)); - } - - public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs, - final Resources res) { - return prefs.getBoolean(PREF_BLOCK_POTENTIALLY_OFFENSIVE, - res.getBoolean(R.bool.config_block_potentially_offensive)); - } - - public static boolean readFromBuildConfigIfGestureInputEnabled(final Resources res) { - return res.getBoolean(R.bool.config_gesture_input_enabled_by_build_config); - } - - public static boolean readGestureInputEnabled(final SharedPreferences prefs, - final Resources res) { - return readFromBuildConfigIfGestureInputEnabled(res) - && prefs.getBoolean(PREF_GESTURE_INPUT, true); - } - - public static boolean readFromBuildConfigIfToShowKeyPreviewPopupOption(final Resources res) { - return res.getBoolean(R.bool.config_enable_show_key_preview_popup_option); - } - - public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs, - final Resources res) { - final boolean defaultKeyPreviewPopup = res.getBoolean( - R.bool.config_default_key_preview_popup); - if (!readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) { - return defaultKeyPreviewPopup; - } - return prefs.getBoolean(PREF_POPUP_ON, defaultKeyPreviewPopup); - } - - public static int readKeyPreviewPopupDismissDelay(final SharedPreferences prefs, - final Resources res) { - return Integer.parseInt(prefs.getString(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, - Integer.toString(res.getInteger( - R.integer.config_key_preview_linger_timeout)))); - } - - public static boolean readShowsLanguageSwitchKey(final SharedPreferences prefs) { - if (prefs.contains(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY)) { - final boolean suppressLanguageSwitchKey = prefs.getBoolean( - PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false); - final SharedPreferences.Editor editor = prefs.edit(); - editor.remove(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY); - editor.putBoolean(PREF_SHOW_LANGUAGE_SWITCH_KEY, !suppressLanguageSwitchKey); - editor.apply(); - } - return prefs.getBoolean(PREF_SHOW_LANGUAGE_SWITCH_KEY, true); - } - - public static String readPrefAdditionalSubtypes(final SharedPreferences prefs, - final Resources res) { - final String predefinedPrefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes( - res.getStringArray(R.array.predefined_subtypes)); - return prefs.getString(PREF_CUSTOM_INPUT_STYLES, predefinedPrefSubtypes); - } - - public static void writePrefAdditionalSubtypes(final SharedPreferences prefs, - final String prefSubtypes) { - prefs.edit().putString(PREF_CUSTOM_INPUT_STYLES, prefSubtypes).apply(); - } - - public static float readKeypressSoundVolume(final SharedPreferences prefs, - final Resources res) { - final float volume = prefs.getFloat( - PREF_KEYPRESS_SOUND_VOLUME, UNDEFINED_PREFERENCE_VALUE_FLOAT); - return (volume != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? volume - : readDefaultKeypressSoundVolume(res); - } - - // Default keypress sound volume for unknown devices. - // The negative value means system default. - private static final String DEFAULT_KEYPRESS_SOUND_VOLUME = Float.toString(-1.0f); - - public static float readDefaultKeypressSoundVolume(final Resources res) { - return Float.parseFloat(ResourceUtils.getDeviceOverrideValue(res, - R.array.keypress_volumes, DEFAULT_KEYPRESS_SOUND_VOLUME)); - } - - public static int readKeyLongpressTimeout(final SharedPreferences prefs, - final Resources res) { - final int milliseconds = prefs.getInt( - PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT); - return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds - : readDefaultKeyLongpressTimeout(res); - } - - public static int readDefaultKeyLongpressTimeout(final Resources res) { - return res.getInteger(R.integer.config_default_longpress_key_timeout); - } - - public static int readKeypressVibrationDuration(final SharedPreferences prefs, - final Resources res) { - final int milliseconds = prefs.getInt( - PREF_VIBRATION_DURATION_SETTINGS, UNDEFINED_PREFERENCE_VALUE_INT); - return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds - : readDefaultKeypressVibrationDuration(res); - } - - // Default keypress vibration duration for unknown devices. - // The negative value means system default. - private static final String DEFAULT_KEYPRESS_VIBRATION_DURATION = Integer.toString(-1); - - public static int readDefaultKeypressVibrationDuration(final Resources res) { - return Integer.parseInt(ResourceUtils.getDeviceOverrideValue(res, - R.array.keypress_vibration_durations, DEFAULT_KEYPRESS_VIBRATION_DURATION)); - } - - public static float readKeyPreviewAnimationScale(final SharedPreferences prefs, - final String prefKey, final float defaultValue) { - final float fraction = prefs.getFloat(prefKey, UNDEFINED_PREFERENCE_VALUE_FLOAT); - return (fraction != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? fraction : defaultValue; - } - - public static int readKeyPreviewAnimationDuration(final SharedPreferences prefs, - final String prefKey, final int defaultValue) { - final int milliseconds = prefs.getInt(prefKey, UNDEFINED_PREFERENCE_VALUE_INT); - return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds : defaultValue; - } - - public static float readKeyboardHeight(final SharedPreferences prefs, - final float defaultValue) { - final float percentage = prefs.getFloat( - DebugSettings.PREF_KEYBOARD_HEIGHT_SCALE, UNDEFINED_PREFERENCE_VALUE_FLOAT); - return (percentage != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? percentage : defaultValue; - } - - public static boolean readUseFullscreenMode(final Resources res) { - return res.getBoolean(R.bool.config_use_fullscreen_mode); - } - - public static boolean readShowSetupWizardIcon(final SharedPreferences prefs, - final Context context) { - if (!prefs.contains(PREF_SHOW_SETUP_WIZARD_ICON)) { - final ApplicationInfo appInfo = context.getApplicationInfo(); - final boolean isApplicationInSystemImage = - (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - // Default value - return !isApplicationInSystemImage; - } - return prefs.getBoolean(PREF_SHOW_SETUP_WIZARD_ICON, false); - } - - public static boolean readHasHardwareKeyboard(final Configuration conf) { - // The standard way of finding out whether we have a hardware keyboard. This code is taken - // from InputMethodService#onEvaluateInputShown, which canonically determines this. - // In a nutshell, we have a keyboard if the configuration says the type of hardware keyboard - // is NOKEYS and if it's not hidden (e.g. folded inside the device). - return conf.keyboard != Configuration.KEYBOARD_NOKEYS - && conf.hardKeyboardHidden != Configuration.HARDKEYBOARDHIDDEN_YES; - } - - public static boolean isInternal(final SharedPreferences prefs) { - return prefs.getBoolean(PREF_KEY_IS_INTERNAL, false); - } - - public void writeLastUsedPersonalizationToken(byte[] token) { - if (token == null) { - mPrefs.edit().remove(PREF_LAST_USED_PERSONALIZATION_TOKEN).apply(); - } else { - final String tokenStr = StringUtils.byteArrayToHexString(token); - mPrefs.edit().putString(PREF_LAST_USED_PERSONALIZATION_TOKEN, tokenStr).apply(); - } - } - - public byte[] readLastUsedPersonalizationToken() { - final String tokenStr = mPrefs.getString(PREF_LAST_USED_PERSONALIZATION_TOKEN, null); - return StringUtils.hexStringToByteArray(tokenStr); - } - - public void writeLastPersonalizationDictWipedTime(final long timestamp) { - mPrefs.edit().putLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, timestamp).apply(); - } - - public long readLastPersonalizationDictGeneratedTime() { - return mPrefs.getLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, 0); - } - - public void writeCorpusHandlesForPersonalization(final Set<String> corpusHandles) { - mPrefs.edit().putStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, corpusHandles).apply(); - } - - public Set<String> readCorpusHandlesForPersonalization() { - final Set<String> emptySet = Collections.emptySet(); - return mPrefs.getStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, emptySet); - } - - public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) { - prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply(); - } - - public static String readEmojiRecentKeys(final SharedPreferences prefs) { - return prefs.getString(PREF_EMOJI_RECENT_KEYS, ""); - } - - public static void writeLastTypedEmojiCategoryPageId( - final SharedPreferences prefs, final int categoryId, final int categoryPageId) { - final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + categoryId; - prefs.edit().putInt(key, categoryPageId).apply(); - } - - public static int readLastTypedEmojiCategoryPageId( - final SharedPreferences prefs, final int categoryId) { - final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + categoryId; - return prefs.getInt(key, 0); - } - - public static void writeLastShownEmojiCategoryId( - final SharedPreferences prefs, final int categoryId) { - prefs.edit().putInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, categoryId).apply(); - } - - public static int readLastShownEmojiCategoryId( - final SharedPreferences prefs, final int defValue) { - return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, defValue); - } - - private void upgradeAutocorrectionSettings(final SharedPreferences prefs, final Resources res) { - final String thresholdSetting = - prefs.getString(PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE, null); - if (thresholdSetting != null) { - SharedPreferences.Editor editor = prefs.edit(); - editor.remove(PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE); - final String autoCorrectionOff = - res.getString(R.string.auto_correction_threshold_mode_index_off); - if (thresholdSetting.equals(autoCorrectionOff)) { - editor.putBoolean(PREF_AUTO_CORRECTION, false); - } else { - editor.putBoolean(PREF_AUTO_CORRECTION, true); - } - editor.commit(); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java b/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java deleted file mode 100644 index ac1657762..000000000 --- a/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.settings; - -import com.android.inputmethod.latin.permissions.PermissionsManager; -import com.android.inputmethod.latin.utils.FragmentUtils; -import com.android.inputmethod.latin.utils.StatsUtils; -import com.android.inputmethod.latin.utils.StatsUtilsManager; - -import android.app.ActionBar; -import android.content.Intent; -import android.os.Bundle; -import android.preference.PreferenceActivity; -import androidx.core.app.ActivityCompat; -import android.view.MenuItem; - -public final class SettingsActivity extends PreferenceActivity - implements ActivityCompat.OnRequestPermissionsResultCallback { - private static final String DEFAULT_FRAGMENT = SettingsFragment.class.getName(); - - public static final String EXTRA_SHOW_HOME_AS_UP = "show_home_as_up"; - public static final String EXTRA_ENTRY_KEY = "entry"; - public static final String EXTRA_ENTRY_VALUE_LONG_PRESS_COMMA = "long_press_comma"; - public static final String EXTRA_ENTRY_VALUE_APP_ICON = "app_icon"; - public static final String EXTRA_ENTRY_VALUE_NOTICE_DIALOG = "important_notice"; - public static final String EXTRA_ENTRY_VALUE_SYSTEM_SETTINGS = "system_settings"; - - private boolean mShowHomeAsUp; - - @Override - protected void onCreate(final Bundle savedState) { - super.onCreate(savedState); - final ActionBar actionBar = getActionBar(); - final Intent intent = getIntent(); - if (actionBar != null) { - mShowHomeAsUp = intent.getBooleanExtra(EXTRA_SHOW_HOME_AS_UP, true); - actionBar.setDisplayHomeAsUpEnabled(mShowHomeAsUp); - actionBar.setHomeButtonEnabled(mShowHomeAsUp); - } - StatsUtils.onSettingsActivity( - intent.hasExtra(EXTRA_ENTRY_KEY) ? intent.getStringExtra(EXTRA_ENTRY_KEY) - : EXTRA_ENTRY_VALUE_SYSTEM_SETTINGS); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - if (mShowHomeAsUp && item.getItemId() == android.R.id.home) { - finish(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public Intent getIntent() { - final Intent intent = super.getIntent(); - final String fragment = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); - if (fragment == null) { - intent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT); - } - intent.putExtra(EXTRA_NO_HEADERS, true); - return intent; - } - - @Override - public boolean isValidFragment(final String fragmentName) { - return FragmentUtils.isValidFragment(fragmentName); - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - PermissionsManager.get(this).onRequestPermissionsResult(requestCode, permissions, grantResults); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java deleted file mode 100644 index 874f221c6..000000000 --- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.inputmethod.latin.settings; - -import android.app.Activity; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceScreen; -import android.provider.Settings.Secure; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.define.ProductionFlags; -import com.android.inputmethod.latin.utils.ApplicationUtils; -import com.android.inputmethod.latin.utils.FeedbackUtils; -import com.android.inputmethodcommon.InputMethodSettingsFragment; - -public final class SettingsFragment extends InputMethodSettingsFragment { - // We don't care about menu grouping. - private static final int NO_MENU_GROUP = Menu.NONE; - // The first menu item id and order. - private static final int MENU_ABOUT = Menu.FIRST; - // The second menu item id and order. - private static final int MENU_HELP_AND_FEEDBACK = Menu.FIRST + 1; - - @Override - public void onCreate(final Bundle icicle) { - super.onCreate(icicle); - setHasOptionsMenu(true); - setInputMethodSettingsCategoryTitle(R.string.language_selection_title); - setSubtypeEnablerTitle(R.string.select_language); - addPreferencesFromResource(R.xml.prefs); - final PreferenceScreen preferenceScreen = getPreferenceScreen(); - preferenceScreen.setTitle( - ApplicationUtils.getActivityTitleResId(getActivity(), SettingsActivity.class)); - if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) { - final Preference accountsPreference = findPreference(Settings.SCREEN_ACCOUNTS); - preferenceScreen.removePreference(accountsPreference); - } - } - - @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - if (FeedbackUtils.isHelpAndFeedbackFormSupported()) { - menu.add(NO_MENU_GROUP, MENU_HELP_AND_FEEDBACK /* itemId */, - MENU_HELP_AND_FEEDBACK /* order */, R.string.help_and_feedback); - } - final int aboutResId = FeedbackUtils.getAboutKeyboardTitleResId(); - if (aboutResId != 0) { - menu.add(NO_MENU_GROUP, MENU_ABOUT /* itemId */, MENU_ABOUT /* order */, aboutResId); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - final Activity activity = getActivity(); - if (!isUserSetupComplete(activity)) { - // If setup is not complete, it's not safe to launch Help or other activities - // because they might go to the Play Store. See b/19866981. - return true; - } - final int itemId = item.getItemId(); - if (itemId == MENU_HELP_AND_FEEDBACK) { - FeedbackUtils.showHelpAndFeedbackForm(activity); - return true; - } - if (itemId == MENU_ABOUT) { - final Intent aboutIntent = FeedbackUtils.getAboutKeyboardIntent(activity); - if (aboutIntent != null) { - startActivity(aboutIntent); - return true; - } - } - return super.onOptionsItemSelected(item); - } - - private static boolean isUserSetupComplete(final Activity activity) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return true; - } - return Secure.getInt(activity.getContentResolver(), "user_setup_complete", 0) != 0; - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java deleted file mode 100644 index 57018244f..000000000 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.settings; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Build; -import android.util.Log; -import android.view.inputmethod.EditorInfo; - -import com.android.inputmethod.compat.AppWorkaroundsUtils; -import com.android.inputmethod.latin.InputAttributes; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodManager; -import com.android.inputmethod.latin.utils.AsyncResultHolder; -import com.android.inputmethod.latin.utils.ResourceUtils; -import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; - -import java.util.Arrays; -import java.util.Locale; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * When you call the constructor of this class, you may want to change the current system locale by - * using {@link com.android.inputmethod.latin.utils.RunInLocale}. - */ -// Non-final for testing via mock library. -public class SettingsValues { - private static final String TAG = SettingsValues.class.getSimpleName(); - // "floatMaxValue" and "floatNegativeInfinity" are special marker strings for - // Float.NEGATIVE_INFINITE and Float.MAX_VALUE. Currently used for auto-correction settings. - private static final String FLOAT_MAX_VALUE_MARKER_STRING = "floatMaxValue"; - private static final String FLOAT_NEGATIVE_INFINITY_MARKER_STRING = "floatNegativeInfinity"; - private static final int TIMEOUT_TO_GET_TARGET_PACKAGE = 5; // seconds - public static final float DEFAULT_SIZE_SCALE = 1.0f; // 100% - - // From resources: - public final SpacingAndPunctuations mSpacingAndPunctuations; - public final int mDelayInMillisecondsToUpdateOldSuggestions; - public final long mDoubleSpacePeriodTimeout; - // From configuration: - public final Locale mLocale; - public final boolean mHasHardwareKeyboard; - public final int mDisplayOrientation; - // From preferences, in the same order as xml/prefs.xml: - public final boolean mAutoCap; - public final boolean mVibrateOn; - public final boolean mSoundOn; - public final boolean mKeyPreviewPopupOn; - public final boolean mShowsVoiceInputKey; - public final boolean mIncludesOtherImesInLanguageSwitchList; - public final boolean mShowsLanguageSwitchKey; - public final boolean mUseContactsDict; - public final boolean mUsePersonalizedDicts; - public final boolean mUseDoubleSpacePeriod; - public final boolean mBlockPotentiallyOffensive; - // Use bigrams to predict the next word when there is no input for it yet - public final boolean mBigramPredictionEnabled; - public final boolean mGestureInputEnabled; - public final boolean mGestureTrailEnabled; - public final boolean mGestureFloatingPreviewTextEnabled; - public final boolean mSlidingKeyInputPreviewEnabled; - public final int mKeyLongpressTimeout; - public final boolean mEnableEmojiAltPhysicalKey; - public final boolean mShowAppIcon; - public final boolean mIsShowAppIconSettingInPreferences; - public final boolean mCloudSyncEnabled; - public final boolean mEnableMetricsLogging; - public final boolean mShouldShowLxxSuggestionUi; - // Use split layout for keyboard. - public final boolean mIsSplitKeyboardEnabled; - public final int mScreenMetrics; - - // From the input box - @Nonnull - public final InputAttributes mInputAttributes; - - // Deduced settings - public final int mKeypressVibrationDuration; - public final float mKeypressSoundVolume; - public final int mKeyPreviewPopupDismissDelay; - private final boolean mAutoCorrectEnabled; - public final float mAutoCorrectionThreshold; - public final float mPlausibilityThreshold; - public final boolean mAutoCorrectionEnabledPerUserSettings; - private final boolean mSuggestionsEnabledPerUserSettings; - private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds; - - // Debug settings - public final boolean mIsInternal; - public final boolean mHasCustomKeyPreviewAnimationParams; - public final boolean mHasKeyboardResize; - public final float mKeyboardHeightScale; - public final int mKeyPreviewShowUpDuration; - public final int mKeyPreviewDismissDuration; - public final float mKeyPreviewShowUpStartXScale; - public final float mKeyPreviewShowUpStartYScale; - public final float mKeyPreviewDismissEndXScale; - public final float mKeyPreviewDismissEndYScale; - - @Nullable public final String mAccount; - - public SettingsValues(final Context context, final SharedPreferences prefs, final Resources res, - @Nonnull final InputAttributes inputAttributes) { - mLocale = res.getConfiguration().locale; - // Get the resources - mDelayInMillisecondsToUpdateOldSuggestions = - res.getInteger(R.integer.config_delay_in_milliseconds_to_update_old_suggestions); - mSpacingAndPunctuations = new SpacingAndPunctuations(res); - - // Store the input attributes - mInputAttributes = inputAttributes; - - // Get the settings preferences - mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true); - mVibrateOn = Settings.readVibrationEnabled(prefs, res); - mSoundOn = Settings.readKeypressSoundEnabled(prefs, res); - mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res); - mSlidingKeyInputPreviewEnabled = prefs.getBoolean( - DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true); - mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res) - && mInputAttributes.mShouldShowVoiceInputKey - && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; - mIncludesOtherImesInLanguageSwitchList = Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS - ? prefs.getBoolean(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false) - : true /* forcibly */; - mShowsLanguageSwitchKey = Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS - ? Settings.readShowsLanguageSwitchKey(prefs) : true /* forcibly */; - mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true); - mUsePersonalizedDicts = prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true); - mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true) - && inputAttributes.mIsGeneralTextInput; - mBlockPotentiallyOffensive = Settings.readBlockPotentiallyOffensive(prefs, res); - mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(prefs, res); - final String autoCorrectionThresholdRawValue = mAutoCorrectEnabled - ? res.getString(R.string.auto_correction_threshold_mode_index_modest) - : res.getString(R.string.auto_correction_threshold_mode_index_off); - mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res); - mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout); - mHasHardwareKeyboard = Settings.readHasHardwareKeyboard(res.getConfiguration()); - mEnableMetricsLogging = prefs.getBoolean(Settings.PREF_ENABLE_METRICS_LOGGING, true); - mIsSplitKeyboardEnabled = prefs.getBoolean(Settings.PREF_ENABLE_SPLIT_KEYBOARD, false); - mScreenMetrics = Settings.readScreenMetrics(res); - - mShouldShowLxxSuggestionUi = Settings.SHOULD_SHOW_LXX_SUGGESTION_UI - && prefs.getBoolean(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, true); - // Compute other readable settings - mKeyLongpressTimeout = Settings.readKeyLongpressTimeout(prefs, res); - mKeypressVibrationDuration = Settings.readKeypressVibrationDuration(prefs, res); - mKeypressSoundVolume = Settings.readKeypressSoundVolume(prefs, res); - mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res); - mEnableEmojiAltPhysicalKey = prefs.getBoolean( - Settings.PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY, true); - mShowAppIcon = Settings.readShowSetupWizardIcon(prefs, context); - mIsShowAppIconSettingInPreferences = prefs.contains(Settings.PREF_SHOW_SETUP_WIZARD_ICON); - mAutoCorrectionThreshold = readAutoCorrectionThreshold(res, - autoCorrectionThresholdRawValue); - mPlausibilityThreshold = Settings.readPlausibilityThreshold(res); - mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res); - mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true); - mCloudSyncEnabled = prefs.getBoolean(LocalSettingsConstants.PREF_ENABLE_CLOUD_SYNC, false); - mAccount = prefs.getString(LocalSettingsConstants.PREF_ACCOUNT_NAME, - null /* default */); - mGestureFloatingPreviewTextEnabled = !mInputAttributes.mDisableGestureFloatingPreviewText - && prefs.getBoolean(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true); - mAutoCorrectionEnabledPerUserSettings = mAutoCorrectEnabled - && !mInputAttributes.mInputTypeNoAutoCorrect; - mSuggestionsEnabledPerUserSettings = readSuggestionsEnabled(prefs); - mIsInternal = Settings.isInternal(prefs); - mHasCustomKeyPreviewAnimationParams = prefs.getBoolean( - DebugSettings.PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS, false); - mHasKeyboardResize = prefs.getBoolean(DebugSettings.PREF_RESIZE_KEYBOARD, false); - mKeyboardHeightScale = Settings.readKeyboardHeight(prefs, DEFAULT_SIZE_SCALE); - mKeyPreviewShowUpDuration = Settings.readKeyPreviewAnimationDuration( - prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION, - res.getInteger(R.integer.config_key_preview_show_up_duration)); - mKeyPreviewDismissDuration = Settings.readKeyPreviewAnimationDuration( - prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION, - res.getInteger(R.integer.config_key_preview_dismiss_duration)); - final float defaultKeyPreviewShowUpStartScale = ResourceUtils.getFloatFromFraction( - res, R.fraction.config_key_preview_show_up_start_scale); - final float defaultKeyPreviewDismissEndScale = ResourceUtils.getFloatFromFraction( - res, R.fraction.config_key_preview_dismiss_end_scale); - mKeyPreviewShowUpStartXScale = Settings.readKeyPreviewAnimationScale( - prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE, - defaultKeyPreviewShowUpStartScale); - mKeyPreviewShowUpStartYScale = Settings.readKeyPreviewAnimationScale( - prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE, - defaultKeyPreviewShowUpStartScale); - mKeyPreviewDismissEndXScale = Settings.readKeyPreviewAnimationScale( - prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_X_SCALE, - defaultKeyPreviewDismissEndScale); - mKeyPreviewDismissEndYScale = Settings.readKeyPreviewAnimationScale( - prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE, - defaultKeyPreviewDismissEndScale); - mDisplayOrientation = res.getConfiguration().orientation; - mAppWorkarounds = new AsyncResultHolder<>("AppWorkarounds"); - final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo( - mInputAttributes.mTargetApplicationPackageName); - if (null != packageInfo) { - mAppWorkarounds.set(new AppWorkaroundsUtils(packageInfo)); - } else { - new TargetPackageInfoGetterTask(context, mAppWorkarounds) - .execute(mInputAttributes.mTargetApplicationPackageName); - } - } - - public boolean isMetricsLoggingEnabled() { - return mEnableMetricsLogging; - } - - public boolean isApplicationSpecifiedCompletionsOn() { - return mInputAttributes.mApplicationSpecifiedCompletionOn; - } - - public boolean needsToLookupSuggestions() { - return mInputAttributes.mShouldShowSuggestions - && (mAutoCorrectionEnabledPerUserSettings || isSuggestionsEnabledPerUserSettings()); - } - - public boolean isSuggestionsEnabledPerUserSettings() { - return mSuggestionsEnabledPerUserSettings; - } - - public boolean isPersonalizationEnabled() { - return mUsePersonalizedDicts; - } - - public boolean isWordSeparator(final int code) { - return mSpacingAndPunctuations.isWordSeparator(code); - } - - public boolean isWordConnector(final int code) { - return mSpacingAndPunctuations.isWordConnector(code); - } - - public boolean isWordCodePoint(final int code) { - return Character.isLetter(code) || isWordConnector(code) - || Character.COMBINING_SPACING_MARK == Character.getType(code); - } - - public boolean isUsuallyPrecededBySpace(final int code) { - return mSpacingAndPunctuations.isUsuallyPrecededBySpace(code); - } - - public boolean isUsuallyFollowedBySpace(final int code) { - return mSpacingAndPunctuations.isUsuallyFollowedBySpace(code); - } - - public boolean shouldInsertSpacesAutomatically() { - return mInputAttributes.mShouldInsertSpacesAutomatically; - } - - public boolean isLanguageSwitchKeyEnabled() { - if (!mShowsLanguageSwitchKey) { - return false; - } - final RichInputMethodManager imm = RichInputMethodManager.getInstance(); - if (mIncludesOtherImesInLanguageSwitchList) { - return imm.hasMultipleEnabledIMEsOrSubtypes(false /* include aux subtypes */); - } - return imm.hasMultipleEnabledSubtypesInThisIme(false /* include aux subtypes */); - } - - public boolean isSameInputType(final EditorInfo editorInfo) { - return mInputAttributes.isSameInputType(editorInfo); - } - - public boolean hasSameOrientation(final Configuration configuration) { - return mDisplayOrientation == configuration.orientation; - } - - public boolean isBeforeJellyBean() { - final AppWorkaroundsUtils appWorkaroundUtils - = mAppWorkarounds.get(null, TIMEOUT_TO_GET_TARGET_PACKAGE); - return null == appWorkaroundUtils ? false : appWorkaroundUtils.isBeforeJellyBean(); - } - - public boolean isBrokenByRecorrection() { - final AppWorkaroundsUtils appWorkaroundUtils - = mAppWorkarounds.get(null, TIMEOUT_TO_GET_TARGET_PACKAGE); - return null == appWorkaroundUtils ? false : appWorkaroundUtils.isBrokenByRecorrection(); - } - - private static final String SUGGESTIONS_VISIBILITY_HIDE_VALUE_OBSOLETE = "2"; - - private static boolean readSuggestionsEnabled(final SharedPreferences prefs) { - if (prefs.contains(Settings.PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE)) { - final boolean alwaysHide = SUGGESTIONS_VISIBILITY_HIDE_VALUE_OBSOLETE.equals( - prefs.getString(Settings.PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE, null)); - prefs.edit() - .remove(Settings.PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE) - .putBoolean(Settings.PREF_SHOW_SUGGESTIONS, !alwaysHide) - .apply(); - } - return prefs.getBoolean(Settings.PREF_SHOW_SUGGESTIONS, true); - } - - private static boolean readBigramPredictionEnabled(final SharedPreferences prefs, - final Resources res) { - return prefs.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, res.getBoolean( - R.bool.config_default_next_word_prediction)); - } - - private static float readAutoCorrectionThreshold(final Resources res, - final String currentAutoCorrectionSetting) { - final String[] autoCorrectionThresholdValues = res.getStringArray( - R.array.auto_correction_threshold_values); - // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off. - final float autoCorrectionThreshold; - try { - final int arrayIndex = Integer.parseInt(currentAutoCorrectionSetting); - if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) { - final String val = autoCorrectionThresholdValues[arrayIndex]; - if (FLOAT_MAX_VALUE_MARKER_STRING.equals(val)) { - autoCorrectionThreshold = Float.MAX_VALUE; - } else if (FLOAT_NEGATIVE_INFINITY_MARKER_STRING.equals(val)) { - autoCorrectionThreshold = Float.NEGATIVE_INFINITY; - } else { - autoCorrectionThreshold = Float.parseFloat(val); - } - } else { - autoCorrectionThreshold = Float.MAX_VALUE; - } - } catch (final NumberFormatException e) { - // Whenever the threshold settings are correct, never come here. - Log.w(TAG, "Cannot load auto correction threshold setting." - + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting - + ", autoCorrectionThresholdValues: " - + Arrays.toString(autoCorrectionThresholdValues), e); - return Float.MAX_VALUE; - } - return autoCorrectionThreshold; - } - - private static boolean needsToShowVoiceInputKey(final SharedPreferences prefs, - final Resources res) { - // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to - // {@link Settings#PREF_VOICE_INPUT_KEY}. - if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) { - final String voiceModeMain = res.getString(R.string.voice_mode_main); - final String voiceMode = prefs.getString( - Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain); - final boolean shouldShowVoiceInputKey = voiceModeMain.equals(voiceMode); - prefs.edit() - .putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey) - // Remove the obsolete preference if exists. - .remove(Settings.PREF_VOICE_MODE_OBSOLETE) - .apply(); - } - return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true); - } - - public String dump() { - final StringBuilder sb = new StringBuilder("Current settings :"); - sb.append("\n mSpacingAndPunctuations = "); - sb.append("" + mSpacingAndPunctuations.dump()); - sb.append("\n mDelayInMillisecondsToUpdateOldSuggestions = "); - sb.append("" + mDelayInMillisecondsToUpdateOldSuggestions); - sb.append("\n mAutoCap = "); - sb.append("" + mAutoCap); - sb.append("\n mVibrateOn = "); - sb.append("" + mVibrateOn); - sb.append("\n mSoundOn = "); - sb.append("" + mSoundOn); - sb.append("\n mKeyPreviewPopupOn = "); - sb.append("" + mKeyPreviewPopupOn); - sb.append("\n mShowsVoiceInputKey = "); - sb.append("" + mShowsVoiceInputKey); - sb.append("\n mIncludesOtherImesInLanguageSwitchList = "); - sb.append("" + mIncludesOtherImesInLanguageSwitchList); - sb.append("\n mShowsLanguageSwitchKey = "); - sb.append("" + mShowsLanguageSwitchKey); - sb.append("\n mUseContactsDict = "); - sb.append("" + mUseContactsDict); - sb.append("\n mUsePersonalizedDicts = "); - sb.append("" + mUsePersonalizedDicts); - sb.append("\n mUseDoubleSpacePeriod = "); - sb.append("" + mUseDoubleSpacePeriod); - sb.append("\n mBlockPotentiallyOffensive = "); - sb.append("" + mBlockPotentiallyOffensive); - sb.append("\n mBigramPredictionEnabled = "); - sb.append("" + mBigramPredictionEnabled); - sb.append("\n mGestureInputEnabled = "); - sb.append("" + mGestureInputEnabled); - sb.append("\n mGestureTrailEnabled = "); - sb.append("" + mGestureTrailEnabled); - sb.append("\n mGestureFloatingPreviewTextEnabled = "); - sb.append("" + mGestureFloatingPreviewTextEnabled); - sb.append("\n mSlidingKeyInputPreviewEnabled = "); - sb.append("" + mSlidingKeyInputPreviewEnabled); - sb.append("\n mKeyLongpressTimeout = "); - sb.append("" + mKeyLongpressTimeout); - sb.append("\n mLocale = "); - sb.append("" + mLocale); - sb.append("\n mInputAttributes = "); - sb.append("" + mInputAttributes); - sb.append("\n mKeypressVibrationDuration = "); - sb.append("" + mKeypressVibrationDuration); - sb.append("\n mKeypressSoundVolume = "); - sb.append("" + mKeypressSoundVolume); - sb.append("\n mKeyPreviewPopupDismissDelay = "); - sb.append("" + mKeyPreviewPopupDismissDelay); - sb.append("\n mAutoCorrectEnabled = "); - sb.append("" + mAutoCorrectEnabled); - sb.append("\n mAutoCorrectionThreshold = "); - sb.append("" + mAutoCorrectionThreshold); - sb.append("\n mAutoCorrectionEnabledPerUserSettings = "); - sb.append("" + mAutoCorrectionEnabledPerUserSettings); - sb.append("\n mSuggestionsEnabledPerUserSettings = "); - sb.append("" + mSuggestionsEnabledPerUserSettings); - sb.append("\n mDisplayOrientation = "); - sb.append("" + mDisplayOrientation); - sb.append("\n mAppWorkarounds = "); - final AppWorkaroundsUtils awu = mAppWorkarounds.get(null, 0); - sb.append("" + (null == awu ? "null" : awu.toString())); - sb.append("\n mIsInternal = "); - sb.append("" + mIsInternal); - sb.append("\n mKeyPreviewShowUpDuration = "); - sb.append("" + mKeyPreviewShowUpDuration); - sb.append("\n mKeyPreviewDismissDuration = "); - sb.append("" + mKeyPreviewDismissDuration); - sb.append("\n mKeyPreviewShowUpStartScaleX = "); - sb.append("" + mKeyPreviewShowUpStartXScale); - sb.append("\n mKeyPreviewShowUpStartScaleY = "); - sb.append("" + mKeyPreviewShowUpStartYScale); - sb.append("\n mKeyPreviewDismissEndScaleX = "); - sb.append("" + mKeyPreviewDismissEndXScale); - sb.append("\n mKeyPreviewDismissEndScaleY = "); - sb.append("" + mKeyPreviewDismissEndYScale); - return sb.toString(); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java b/java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java deleted file mode 100644 index 5e2e5a5d6..000000000 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -public class SettingsValuesForSuggestion { - public final boolean mBlockPotentiallyOffensive; - - public SettingsValuesForSuggestion(final boolean blockPotentiallyOffensive) { - mBlockPotentiallyOffensive = blockPotentiallyOffensive; - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java deleted file mode 100644 index 70d97a5ba..000000000 --- a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.content.res.Resources; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.keyboard.internal.MoreKeySpec; -import com.android.inputmethod.latin.PunctuationSuggestions; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; - -import java.util.Arrays; -import java.util.Locale; - -public final class SpacingAndPunctuations { - private final int[] mSortedSymbolsPrecededBySpace; - private final int[] mSortedSymbolsFollowedBySpace; - private final int[] mSortedSymbolsClusteringTogether; - private final int[] mSortedWordConnectors; - public final int[] mSortedWordSeparators; - public final PunctuationSuggestions mSuggestPuncList; - private final int mSentenceSeparator; - private final int mAbbreviationMarker; - private final int[] mSortedSentenceTerminators; - public final String mSentenceSeparatorAndSpace; - public final boolean mCurrentLanguageHasSpaces; - public final boolean mUsesAmericanTypography; - public final boolean mUsesGermanRules; - - public SpacingAndPunctuations(final Resources res) { - // To be able to binary search the code point. See {@link #isUsuallyPrecededBySpace(int)}. - mSortedSymbolsPrecededBySpace = StringUtils.toSortedCodePointArray( - res.getString(R.string.symbols_preceded_by_space)); - // To be able to binary search the code point. See {@link #isUsuallyFollowedBySpace(int)}. - mSortedSymbolsFollowedBySpace = StringUtils.toSortedCodePointArray( - res.getString(R.string.symbols_followed_by_space)); - mSortedSymbolsClusteringTogether = StringUtils.toSortedCodePointArray( - res.getString(R.string.symbols_clustering_together)); - // To be able to binary search the code point. See {@link #isWordConnector(int)}. - mSortedWordConnectors = StringUtils.toSortedCodePointArray( - res.getString(R.string.symbols_word_connectors)); - mSortedWordSeparators = StringUtils.toSortedCodePointArray( - res.getString(R.string.symbols_word_separators)); - mSortedSentenceTerminators = StringUtils.toSortedCodePointArray( - res.getString(R.string.symbols_sentence_terminators)); - mSentenceSeparator = res.getInteger(R.integer.sentence_separator); - mAbbreviationMarker = res.getInteger(R.integer.abbreviation_marker); - mSentenceSeparatorAndSpace = new String(new int[] { - mSentenceSeparator, Constants.CODE_SPACE }, 0, 2); - mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces); - final Locale locale = res.getConfiguration().locale; - // Heuristic: we use American Typography rules because it's the most common rules for all - // English variants. German rules (not "German typography") also have small gotchas. - mUsesAmericanTypography = Locale.ENGLISH.getLanguage().equals(locale.getLanguage()); - mUsesGermanRules = Locale.GERMAN.getLanguage().equals(locale.getLanguage()); - final String[] suggestPuncsSpec = MoreKeySpec.splitKeySpecs( - res.getString(R.string.suggested_punctuations)); - mSuggestPuncList = PunctuationSuggestions.newPunctuationSuggestions(suggestPuncsSpec); - } - - @UsedForTesting - public SpacingAndPunctuations(final SpacingAndPunctuations model, - final int[] overrideSortedWordSeparators) { - mSortedSymbolsPrecededBySpace = model.mSortedSymbolsPrecededBySpace; - mSortedSymbolsFollowedBySpace = model.mSortedSymbolsFollowedBySpace; - mSortedSymbolsClusteringTogether = model.mSortedSymbolsClusteringTogether; - mSortedWordConnectors = model.mSortedWordConnectors; - mSortedWordSeparators = overrideSortedWordSeparators; - mSortedSentenceTerminators = model.mSortedSentenceTerminators; - mSuggestPuncList = model.mSuggestPuncList; - mSentenceSeparator = model.mSentenceSeparator; - mAbbreviationMarker = model.mAbbreviationMarker; - mSentenceSeparatorAndSpace = model.mSentenceSeparatorAndSpace; - mCurrentLanguageHasSpaces = model.mCurrentLanguageHasSpaces; - mUsesAmericanTypography = model.mUsesAmericanTypography; - mUsesGermanRules = model.mUsesGermanRules; - } - - public boolean isWordSeparator(final int code) { - return Arrays.binarySearch(mSortedWordSeparators, code) >= 0; - } - - public boolean isWordConnector(final int code) { - return Arrays.binarySearch(mSortedWordConnectors, code) >= 0; - } - - public boolean isWordCodePoint(final int code) { - return Character.isLetter(code) || isWordConnector(code); - } - - public boolean isUsuallyPrecededBySpace(final int code) { - return Arrays.binarySearch(mSortedSymbolsPrecededBySpace, code) >= 0; - } - - public boolean isUsuallyFollowedBySpace(final int code) { - return Arrays.binarySearch(mSortedSymbolsFollowedBySpace, code) >= 0; - } - - public boolean isClusteringSymbol(final int code) { - return Arrays.binarySearch(mSortedSymbolsClusteringTogether, code) >= 0; - } - - public boolean isSentenceTerminator(final int code) { - return Arrays.binarySearch(mSortedSentenceTerminators, code) >= 0; - } - - public boolean isAbbreviationMarker(final int code) { - return code == mAbbreviationMarker; - } - - public boolean isSentenceSeparator(final int code) { - return code == mSentenceSeparator; - } - - public String dump() { - final StringBuilder sb = new StringBuilder(); - sb.append("mSortedSymbolsPrecededBySpace = "); - sb.append("" + Arrays.toString(mSortedSymbolsPrecededBySpace)); - sb.append("\n mSortedSymbolsFollowedBySpace = "); - sb.append("" + Arrays.toString(mSortedSymbolsFollowedBySpace)); - sb.append("\n mSortedWordConnectors = "); - sb.append("" + Arrays.toString(mSortedWordConnectors)); - sb.append("\n mSortedWordSeparators = "); - sb.append("" + Arrays.toString(mSortedWordSeparators)); - sb.append("\n mSuggestPuncList = "); - sb.append("" + mSuggestPuncList); - sb.append("\n mSentenceSeparator = "); - sb.append("" + mSentenceSeparator); - sb.append("\n mSentenceSeparatorAndSpace = "); - sb.append("" + mSentenceSeparatorAndSpace); - sb.append("\n mCurrentLanguageHasSpaces = "); - sb.append("" + mCurrentLanguageHasSpaces); - sb.append("\n mUsesAmericanTypography = "); - sb.append("" + mUsesAmericanTypography); - sb.append("\n mUsesGermanRules = "); - sb.append("" + mUsesGermanRules); - return sb.toString(); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java b/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java deleted file mode 100644 index 5994a76df..000000000 --- a/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.app.backup.BackupManager; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.content.res.Resources; -import android.os.Bundle; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.preference.PreferenceScreen; -import android.util.Log; - -/** - * A base abstract class for a {@link PreferenceFragment} that implements a nested - * {@link PreferenceScreen} of the main preference screen. - */ -public abstract class SubScreenFragment extends PreferenceFragment - implements OnSharedPreferenceChangeListener { - private OnSharedPreferenceChangeListener mSharedPreferenceChangeListener; - - static void setPreferenceEnabled(final String prefKey, final boolean enabled, - final PreferenceScreen screen) { - final Preference preference = screen.findPreference(prefKey); - if (preference != null) { - preference.setEnabled(enabled); - } - } - - static void removePreference(final String prefKey, final PreferenceScreen screen) { - final Preference preference = screen.findPreference(prefKey); - if (preference != null) { - screen.removePreference(preference); - } - } - - static void updateListPreferenceSummaryToCurrentValue(final String prefKey, - final PreferenceScreen screen) { - // Because the "%s" summary trick of {@link ListPreference} doesn't work properly before - // KitKat, we need to update the summary programmatically. - final ListPreference listPreference = (ListPreference)screen.findPreference(prefKey); - if (listPreference == null) { - return; - } - final CharSequence entries[] = listPreference.getEntries(); - final int entryIndex = listPreference.findIndexOfValue(listPreference.getValue()); - listPreference.setSummary(entryIndex < 0 ? null : entries[entryIndex]); - } - - final void setPreferenceEnabled(final String prefKey, final boolean enabled) { - setPreferenceEnabled(prefKey, enabled, getPreferenceScreen()); - } - - final void removePreference(final String prefKey) { - removePreference(prefKey, getPreferenceScreen()); - } - - final void updateListPreferenceSummaryToCurrentValue(final String prefKey) { - updateListPreferenceSummaryToCurrentValue(prefKey, getPreferenceScreen()); - } - - final SharedPreferences getSharedPreferences() { - return getPreferenceManager().getSharedPreferences(); - } - - /** - * Gets the application name to display on the UI. - */ - final String getApplicationName() { - final Context context = getActivity(); - final Resources res = getResources(); - final int applicationLabelRes = context.getApplicationInfo().labelRes; - return res.getString(applicationLabelRes); - } - - @Override - public void addPreferencesFromResource(final int preferencesResId) { - super.addPreferencesFromResource(preferencesResId); - TwoStatePreferenceHelper.replaceCheckBoxPreferencesBySwitchPreferences( - getPreferenceScreen()); - } - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mSharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { - @Override - public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - final SubScreenFragment fragment = SubScreenFragment.this; - final Context context = fragment.getActivity(); - if (context == null || fragment.getPreferenceScreen() == null) { - final String tag = fragment.getClass().getSimpleName(); - // TODO: Introduce a static function to register this class and ensure that - // onCreate must be called before "onSharedPreferenceChanged" is called. - Log.w(tag, "onSharedPreferenceChanged called before activity starts."); - return; - } - new BackupManager(context).dataChanged(); - fragment.onSharedPreferenceChanged(prefs, key); - } - }; - getSharedPreferences().registerOnSharedPreferenceChangeListener( - mSharedPreferenceChangeListener); - } - - @Override - public void onDestroy() { - getSharedPreferences().unregisterOnSharedPreferenceChangeListener( - mSharedPreferenceChangeListener); - super.onDestroy(); - } - - @Override - public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - // This method may be overridden by an extended class. - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java b/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java deleted file mode 100644 index 254bc6567..000000000 --- a/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.app.Activity; -import android.app.Fragment; -import android.app.FragmentManager; -import android.content.Intent; -import android.os.Bundle; - -/** - * Test activity to use when testing preference fragments. <br/> - * Usage: <br/> - * Create an ActivityInstrumentationTestCase2 for this activity - * and call setIntent() with an intent that specifies the fragment to load in the activity. - * The fragment can then be obtained from this activity and used for testing/verification. - */ -public final class TestFragmentActivity extends Activity { - /** - * The fragment name that should be loaded when starting this activity. - * This must be specified when starting this activity, as this activity is only - * meant to test fragments from instrumentation tests. - */ - public static final String EXTRA_SHOW_FRAGMENT = "show_fragment"; - - public Fragment mFragment; - - @Override - protected void onCreate(final Bundle savedState) { - super.onCreate(savedState); - final Intent intent = getIntent(); - final String fragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); - if (fragmentName == null) { - throw new IllegalArgumentException("No fragment name specified for testing"); - } - - mFragment = Fragment.instantiate(this, fragmentName); - FragmentManager fragmentManager = getFragmentManager(); - fragmentManager.beginTransaction().add(mFragment, fragmentName).commit(); - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java deleted file mode 100644 index 29289aed2..000000000 --- a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.content.Context; -import android.content.res.Resources; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceScreen; - -import com.android.inputmethod.keyboard.KeyboardTheme; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.settings.RadioButtonPreference.OnRadioButtonClickedListener; - -/** - * "Keyboard theme" settings sub screen. - */ -public final class ThemeSettingsFragment extends SubScreenFragment - implements OnRadioButtonClickedListener { - private int mSelectedThemeId; - - static class KeyboardThemePreference extends RadioButtonPreference { - final int mThemeId; - - KeyboardThemePreference(final Context context, final String name, final int id) { - super(context); - setTitle(name); - mThemeId = id; - } - } - - static void updateKeyboardThemeSummary(final Preference pref) { - final Context context = pref.getContext(); - final Resources res = context.getResources(); - final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context); - final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names); - final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids); - for (int index = 0; index < keyboardThemeNames.length; index++) { - if (keyboardTheme.mThemeId == keyboardThemeIds[index]) { - pref.setSummary(keyboardThemeNames[index]); - return; - } - } - } - - @Override - public void onCreate(final Bundle icicle) { - super.onCreate(icicle); - addPreferencesFromResource(R.xml.prefs_screen_theme); - final PreferenceScreen screen = getPreferenceScreen(); - final Context context = getActivity(); - final Resources res = getResources(); - final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names); - final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids); - for (int index = 0; index < keyboardThemeNames.length; index++) { - final KeyboardThemePreference pref = new KeyboardThemePreference( - context, keyboardThemeNames[index], keyboardThemeIds[index]); - screen.addPreference(pref); - pref.setOnRadioButtonClickedListener(this); - } - final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context); - mSelectedThemeId = keyboardTheme.mThemeId; - } - - @Override - public void onRadioButtonClicked(final RadioButtonPreference preference) { - if (preference instanceof KeyboardThemePreference) { - final KeyboardThemePreference pref = (KeyboardThemePreference)preference; - mSelectedThemeId = pref.mThemeId; - updateSelected(); - } - } - - @Override - public void onResume() { - super.onResume(); - updateSelected(); - } - - @Override - public void onPause() { - super.onPause(); - KeyboardTheme.saveKeyboardThemeId(mSelectedThemeId, getSharedPreferences()); - } - - private void updateSelected() { - final PreferenceScreen screen = getPreferenceScreen(); - final int count = screen.getPreferenceCount(); - for (int index = 0; index < count; index++) { - final Preference preference = screen.getPreference(index); - if (preference instanceof KeyboardThemePreference) { - final KeyboardThemePreference pref = (KeyboardThemePreference)preference; - final boolean selected = (mSelectedThemeId == pref.mThemeId); - pref.setSelected(selected); - } - } - } -} diff --git a/java/src/com/android/inputmethod/latin/settings/TwoStatePreferenceHelper.java b/java/src/com/android/inputmethod/latin/settings/TwoStatePreferenceHelper.java deleted file mode 100644 index 07a871ca0..000000000 --- a/java/src/com/android/inputmethod/latin/settings/TwoStatePreferenceHelper.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.settings; - -import android.os.Build; -import android.preference.CheckBoxPreference; -import android.preference.Preference; -import android.preference.PreferenceGroup; -import android.preference.SwitchPreference; - -import java.util.ArrayList; - -public class TwoStatePreferenceHelper { - private static final String EMPTY_TEXT = ""; - - private TwoStatePreferenceHelper() { - // This utility class is not publicly instantiable. - } - - public static void replaceCheckBoxPreferencesBySwitchPreferences(final PreferenceGroup group) { - // The keyboard settings keeps using a CheckBoxPreference on KitKat or previous. - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - return; - } - // The keyboard settings starts using a SwitchPreference without switch on/off text on - // API versions newer than KitKat. - replaceAllCheckBoxPreferencesBySwitchPreferences(group); - } - - private static void replaceAllCheckBoxPreferencesBySwitchPreferences( - final PreferenceGroup group) { - final ArrayList<Preference> preferences = new ArrayList<>(); - final int count = group.getPreferenceCount(); - for (int index = 0; index < count; index++) { - preferences.add(group.getPreference(index)); - } - group.removeAll(); - for (int index = 0; index < count; index++) { - final Preference preference = preferences.get(index); - if (preference instanceof CheckBoxPreference) { - addSwitchPreferenceBasedOnCheckBoxPreference((CheckBoxPreference)preference, group); - } else { - group.addPreference(preference); - if (preference instanceof PreferenceGroup) { - replaceAllCheckBoxPreferencesBySwitchPreferences((PreferenceGroup)preference); - } - } - } - } - - static void addSwitchPreferenceBasedOnCheckBoxPreference(final CheckBoxPreference checkBox, - final PreferenceGroup group) { - final SwitchPreference switchPref = new SwitchPreference(checkBox.getContext()); - switchPref.setTitle(checkBox.getTitle()); - switchPref.setKey(checkBox.getKey()); - switchPref.setOrder(checkBox.getOrder()); - switchPref.setPersistent(checkBox.isPersistent()); - switchPref.setEnabled(checkBox.isEnabled()); - switchPref.setChecked(checkBox.isChecked()); - switchPref.setSummary(checkBox.getSummary()); - switchPref.setSummaryOn(checkBox.getSummaryOn()); - switchPref.setSummaryOff(checkBox.getSummaryOff()); - switchPref.setSwitchTextOn(EMPTY_TEXT); - switchPref.setSwitchTextOff(EMPTY_TEXT); - group.addPreference(switchPref); - switchPref.setDependency(checkBox.getDependency()); - } -} diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java deleted file mode 100644 index 7607429f8..000000000 --- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.setup; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; - -public final class SetupActivity extends Activity { - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Intent intent = new Intent(); - intent.setClass(this, SetupWizardActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP - | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - if (!isFinishing()) { - finish(); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java deleted file mode 100644 index 789694f08..000000000 --- a/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.setup; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import androidx.core.view.ViewCompat; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.inputmethod.latin.R; - -public final class SetupStartIndicatorView extends LinearLayout { - public SetupStartIndicatorView(final Context context, final AttributeSet attrs) { - super(context, attrs); - setOrientation(HORIZONTAL); - LayoutInflater.from(context).inflate(R.layout.setup_start_indicator_label, this); - - final LabelView labelView = (LabelView)findViewById(R.id.setup_start_label); - labelView.setIndicatorView(findViewById(R.id.setup_start_indicator)); - } - - public static final class LabelView extends TextView { - private View mIndicatorView; - - public LabelView(final Context context, final AttributeSet attrs) { - super(context, attrs); - } - - public void setIndicatorView(final View indicatorView) { - mIndicatorView = indicatorView; - } - - // TODO: Once we stop supporting ICS, uncomment {@link #setPressed(boolean)} method and - // remove this method. - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - for (final int state : getDrawableState()) { - if (state == android.R.attr.state_pressed) { - updateIndicatorView(true /* pressed */); - return; - } - } - updateIndicatorView(false /* pressed */); - } - - // TODO: Once we stop supporting ICS, uncomment this method and remove - // {@link #drawableStateChanged()} method. -// @Override -// public void setPressed(final boolean pressed) { -// super.setPressed(pressed); -// updateIndicatorView(pressed); -// } - - private void updateIndicatorView(final boolean pressed) { - if (mIndicatorView != null) { - mIndicatorView.setPressed(pressed); - mIndicatorView.invalidate(); - } - } - } - - public static final class IndicatorView extends View { - private final Path mIndicatorPath = new Path(); - private final Paint mIndicatorPaint = new Paint(); - private final ColorStateList mIndicatorColor; - - public IndicatorView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mIndicatorColor = getResources().getColorStateList( - R.color.setup_step_action_background); - mIndicatorPaint.setStyle(Paint.Style.FILL); - } - - @Override - protected void onDraw(final Canvas canvas) { - super.onDraw(canvas); - final int layoutDirection = ViewCompat.getLayoutDirection(this); - final int width = getWidth(); - final int height = getHeight(); - final float halfHeight = height / 2.0f; - final Path path = mIndicatorPath; - path.rewind(); - if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) { - // Left arrow - path.moveTo(width, 0.0f); - path.lineTo(0.0f, halfHeight); - path.lineTo(width, height); - } else { // LAYOUT_DIRECTION_LTR - // Right arrow - path.moveTo(0.0f, 0.0f); - path.lineTo(width, halfHeight); - path.lineTo(0.0f, height); - } - path.close(); - final int[] stateSet = getDrawableState(); - final int color = mIndicatorColor.getColorForState(stateSet, 0); - mIndicatorPaint.setColor(color); - canvas.drawPath(path, mIndicatorPaint); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java deleted file mode 100644 index 9a39ceace..000000000 --- a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.setup; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import androidx.core.view.ViewCompat; -import android.util.AttributeSet; -import android.view.View; - -import com.android.inputmethod.latin.R; - -public final class SetupStepIndicatorView extends View { - private final Path mIndicatorPath = new Path(); - private final Paint mIndicatorPaint = new Paint(); - private float mXRatio; - - public SetupStepIndicatorView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mIndicatorPaint.setColor(getResources().getColor(R.color.setup_step_background)); - mIndicatorPaint.setStyle(Paint.Style.FILL); - } - - public void setIndicatorPosition(final int stepPos, final int totalStepNum) { - final int layoutDirection = ViewCompat.getLayoutDirection(this); - // The indicator position is the center of the partition that is equally divided into - // the total step number. - final float partionWidth = 1.0f / totalStepNum; - final float pos = stepPos * partionWidth + partionWidth / 2.0f; - mXRatio = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos; - invalidate(); - } - - @Override - protected void onDraw(final Canvas canvas) { - super.onDraw(canvas); - final int xPos = (int)(getWidth() * mXRatio); - final int height = getHeight(); - mIndicatorPath.rewind(); - mIndicatorPath.moveTo(xPos, 0); - mIndicatorPath.lineTo(xPos + height, height); - mIndicatorPath.lineTo(xPos - height, height); - mIndicatorPath.close(); - canvas.drawPath(mIndicatorPath, mIndicatorPaint); - } -} diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java deleted file mode 100644 index bee22afd5..000000000 --- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.setup; - -import android.app.Activity; -import android.content.ContentResolver; -import android.content.Intent; -import android.content.res.Resources; -import android.media.MediaPlayer; -import android.net.Uri; -import android.os.Bundle; -import android.os.Message; -import android.provider.Settings; -import android.util.Log; -import android.view.View; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.VideoView; - -import com.android.inputmethod.compat.TextViewCompatUtils; -import com.android.inputmethod.compat.ViewCompatUtils; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.settings.SettingsActivity; -import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; -import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils; - -import java.util.ArrayList; - -import javax.annotation.Nonnull; - -// TODO: Use Fragment to implement welcome screen and setup steps. -public final class SetupWizardActivity extends Activity implements View.OnClickListener { - static final String TAG = SetupWizardActivity.class.getSimpleName(); - - // For debugging purpose. - private static final boolean FORCE_TO_SHOW_WELCOME_SCREEN = false; - private static final boolean ENABLE_WELCOME_VIDEO = true; - - private InputMethodManager mImm; - - private View mSetupWizard; - private View mWelcomeScreen; - private View mSetupScreen; - private Uri mWelcomeVideoUri; - private VideoView mWelcomeVideoView; - private ImageView mWelcomeImageView; - private View mActionStart; - private View mActionNext; - private TextView mStep1Bullet; - private TextView mActionFinish; - private SetupStepGroup mSetupStepGroup; - private static final String STATE_STEP = "step"; - private int mStepNumber; - private boolean mNeedsToAdjustStepNumberToSystemState; - private static final int STEP_WELCOME = 0; - private static final int STEP_1 = 1; - private static final int STEP_2 = 2; - private static final int STEP_3 = 3; - private static final int STEP_LAUNCHING_IME_SETTINGS = 4; - private static final int STEP_BACK_FROM_IME_SETTINGS = 5; - - private SettingsPoolingHandler mHandler; - - private static final class SettingsPoolingHandler - extends LeakGuardHandlerWrapper<SetupWizardActivity> { - private static final int MSG_POLLING_IME_SETTINGS = 0; - private static final long IME_SETTINGS_POLLING_INTERVAL = 200; - - private final InputMethodManager mImmInHandler; - - public SettingsPoolingHandler(@Nonnull final SetupWizardActivity ownerInstance, - final InputMethodManager imm) { - super(ownerInstance); - mImmInHandler = imm; - } - - @Override - public void handleMessage(final Message msg) { - final SetupWizardActivity setupWizardActivity = getOwnerInstance(); - if (setupWizardActivity == null) { - return; - } - switch (msg.what) { - case MSG_POLLING_IME_SETTINGS: - if (UncachedInputMethodManagerUtils.isThisImeEnabled(setupWizardActivity, - mImmInHandler)) { - setupWizardActivity.invokeSetupWizardOfThisIme(); - return; - } - startPollingImeSettings(); - break; - } - } - - public void startPollingImeSettings() { - sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS), - IME_SETTINGS_POLLING_INTERVAL); - } - - public void cancelPollingImeSettings() { - removeMessages(MSG_POLLING_IME_SETTINGS); - } - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - setTheme(android.R.style.Theme_Translucent_NoTitleBar); - super.onCreate(savedInstanceState); - - mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); - mHandler = new SettingsPoolingHandler(this, mImm); - - setContentView(R.layout.setup_wizard); - mSetupWizard = findViewById(R.id.setup_wizard); - - if (savedInstanceState == null) { - mStepNumber = determineSetupStepNumberFromLauncher(); - } else { - mStepNumber = savedInstanceState.getInt(STATE_STEP); - } - - final String applicationName = getResources().getString(getApplicationInfo().labelRes); - mWelcomeScreen = findViewById(R.id.setup_welcome_screen); - final TextView welcomeTitle = (TextView)findViewById(R.id.setup_welcome_title); - welcomeTitle.setText(getString(R.string.setup_welcome_title, applicationName)); - - mSetupScreen = findViewById(R.id.setup_steps_screen); - final TextView stepsTitle = (TextView)findViewById(R.id.setup_title); - stepsTitle.setText(getString(R.string.setup_steps_title, applicationName)); - - final SetupStepIndicatorView indicatorView = - (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator); - mSetupStepGroup = new SetupStepGroup(indicatorView); - - mStep1Bullet = (TextView)findViewById(R.id.setup_step1_bullet); - mStep1Bullet.setOnClickListener(this); - final SetupStep step1 = new SetupStep(STEP_1, applicationName, - mStep1Bullet, findViewById(R.id.setup_step1), - R.string.setup_step1_title, R.string.setup_step1_instruction, - R.string.setup_step1_finished_instruction, R.drawable.ic_setup_step1, - R.string.setup_step1_action); - final SettingsPoolingHandler handler = mHandler; - step1.setAction(new Runnable() { - @Override - public void run() { - invokeLanguageAndInputSettings(); - handler.startPollingImeSettings(); - } - }); - mSetupStepGroup.addStep(step1); - - final SetupStep step2 = new SetupStep(STEP_2, applicationName, - (TextView)findViewById(R.id.setup_step2_bullet), findViewById(R.id.setup_step2), - R.string.setup_step2_title, R.string.setup_step2_instruction, - 0 /* finishedInstruction */, R.drawable.ic_setup_step2, - R.string.setup_step2_action); - step2.setAction(new Runnable() { - @Override - public void run() { - invokeInputMethodPicker(); - } - }); - mSetupStepGroup.addStep(step2); - - final SetupStep step3 = new SetupStep(STEP_3, applicationName, - (TextView)findViewById(R.id.setup_step3_bullet), findViewById(R.id.setup_step3), - R.string.setup_step3_title, R.string.setup_step3_instruction, - 0 /* finishedInstruction */, R.drawable.ic_setup_step3, - R.string.setup_step3_action); - step3.setAction(new Runnable() { - @Override - public void run() { - invokeSubtypeEnablerOfThisIme(); - } - }); - mSetupStepGroup.addStep(step3); - - mWelcomeVideoUri = new Uri.Builder() - .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(getPackageName()) - .path(Integer.toString(R.raw.setup_welcome_video)) - .build(); - final VideoView welcomeVideoView = (VideoView)findViewById(R.id.setup_welcome_video); - welcomeVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(final MediaPlayer mp) { - // Now VideoView has been laid-out and ready to play, remove background of it to - // reveal the video. - welcomeVideoView.setBackgroundResource(0); - mp.setLooping(true); - } - }); - welcomeVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { - @Override - public boolean onError(final MediaPlayer mp, final int what, final int extra) { - Log.e(TAG, "Playing welcome video causes error: what=" + what + " extra=" + extra); - hideWelcomeVideoAndShowWelcomeImage(); - return true; - } - }); - mWelcomeVideoView = welcomeVideoView; - mWelcomeImageView = (ImageView)findViewById(R.id.setup_welcome_image); - - mActionStart = findViewById(R.id.setup_start_label); - mActionStart.setOnClickListener(this); - mActionNext = findViewById(R.id.setup_next); - mActionNext.setOnClickListener(this); - mActionFinish = (TextView)findViewById(R.id.setup_finish); - TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(mActionFinish, - getResources().getDrawable(R.drawable.ic_setup_finish), null, null, null); - mActionFinish.setOnClickListener(this); - } - - @Override - public void onClick(final View v) { - if (v == mActionFinish) { - finish(); - return; - } - final int currentStep = determineSetupStepNumber(); - final int nextStep; - if (v == mActionStart) { - nextStep = STEP_1; - } else if (v == mActionNext) { - nextStep = mStepNumber + 1; - } else if (v == mStep1Bullet && currentStep == STEP_2) { - nextStep = STEP_1; - } else { - nextStep = mStepNumber; - } - if (mStepNumber != nextStep) { - mStepNumber = nextStep; - updateSetupStepView(); - } - } - - void invokeSetupWizardOfThisIme() { - final Intent intent = new Intent(); - intent.setClass(this, SetupWizardActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - | Intent.FLAG_ACTIVITY_SINGLE_TOP - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - mNeedsToAdjustStepNumberToSystemState = true; - } - - private void invokeSettingsOfThisIme() { - final Intent intent = new Intent(); - intent.setClass(this, SettingsActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY, - SettingsActivity.EXTRA_ENTRY_VALUE_APP_ICON); - startActivity(intent); - } - - void invokeLanguageAndInputSettings() { - final Intent intent = new Intent(); - intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS); - intent.addCategory(Intent.CATEGORY_DEFAULT); - startActivity(intent); - mNeedsToAdjustStepNumberToSystemState = true; - } - - void invokeInputMethodPicker() { - // Invoke input method picker. - mImm.showInputMethodPicker(); - mNeedsToAdjustStepNumberToSystemState = true; - } - - void invokeSubtypeEnablerOfThisIme() { - final InputMethodInfo imi = - UncachedInputMethodManagerUtils.getInputMethodInfoOf(getPackageName(), mImm); - if (imi == null) { - return; - } - final Intent intent = new Intent(); - intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId()); - startActivity(intent); - } - - private int determineSetupStepNumberFromLauncher() { - final int stepNumber = determineSetupStepNumber(); - if (stepNumber == STEP_1) { - return STEP_WELCOME; - } - if (stepNumber == STEP_3) { - return STEP_LAUNCHING_IME_SETTINGS; - } - return stepNumber; - } - - private int determineSetupStepNumber() { - mHandler.cancelPollingImeSettings(); - if (FORCE_TO_SHOW_WELCOME_SCREEN) { - return STEP_1; - } - if (!UncachedInputMethodManagerUtils.isThisImeEnabled(this, mImm)) { - return STEP_1; - } - if (!UncachedInputMethodManagerUtils.isThisImeCurrent(this, mImm)) { - return STEP_2; - } - return STEP_3; - } - - @Override - protected void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(STATE_STEP, mStepNumber); - } - - @Override - protected void onRestoreInstanceState(final Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mStepNumber = savedInstanceState.getInt(STATE_STEP); - } - - private static boolean isInSetupSteps(final int stepNumber) { - return stepNumber >= STEP_1 && stepNumber <= STEP_3; - } - - @Override - protected void onRestart() { - super.onRestart(); - // Probably the setup wizard has been invoked from "Recent" menu. The setup step number - // needs to be adjusted to system state, because the state (IME is enabled and/or current) - // may have been changed. - if (isInSetupSteps(mStepNumber)) { - mStepNumber = determineSetupStepNumber(); - } - } - - @Override - protected void onResume() { - super.onResume(); - if (mStepNumber == STEP_LAUNCHING_IME_SETTINGS) { - // Prevent white screen flashing while launching settings activity. - mSetupWizard.setVisibility(View.INVISIBLE); - invokeSettingsOfThisIme(); - mStepNumber = STEP_BACK_FROM_IME_SETTINGS; - return; - } - if (mStepNumber == STEP_BACK_FROM_IME_SETTINGS) { - finish(); - return; - } - updateSetupStepView(); - } - - @Override - public void onBackPressed() { - if (mStepNumber == STEP_1) { - mStepNumber = STEP_WELCOME; - updateSetupStepView(); - return; - } - super.onBackPressed(); - } - - void hideWelcomeVideoAndShowWelcomeImage() { - mWelcomeVideoView.setVisibility(View.GONE); - mWelcomeImageView.setImageResource(R.raw.setup_welcome_image); - mWelcomeImageView.setVisibility(View.VISIBLE); - } - - private void showAndStartWelcomeVideo() { - mWelcomeVideoView.setVisibility(View.VISIBLE); - mWelcomeVideoView.setVideoURI(mWelcomeVideoUri); - mWelcomeVideoView.start(); - } - - private void hideAndStopWelcomeVideo() { - mWelcomeVideoView.stopPlayback(); - mWelcomeVideoView.setVisibility(View.GONE); - } - - @Override - protected void onPause() { - hideAndStopWelcomeVideo(); - super.onPause(); - } - - @Override - public void onWindowFocusChanged(final boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus && mNeedsToAdjustStepNumberToSystemState) { - mNeedsToAdjustStepNumberToSystemState = false; - mStepNumber = determineSetupStepNumber(); - updateSetupStepView(); - } - } - - private void updateSetupStepView() { - mSetupWizard.setVisibility(View.VISIBLE); - final boolean welcomeScreen = (mStepNumber == STEP_WELCOME); - mWelcomeScreen.setVisibility(welcomeScreen ? View.VISIBLE : View.GONE); - mSetupScreen.setVisibility(welcomeScreen ? View.GONE : View.VISIBLE); - if (welcomeScreen) { - if (ENABLE_WELCOME_VIDEO) { - showAndStartWelcomeVideo(); - } else { - hideWelcomeVideoAndShowWelcomeImage(); - } - return; - } - hideAndStopWelcomeVideo(); - final boolean isStepActionAlreadyDone = mStepNumber < determineSetupStepNumber(); - mSetupStepGroup.enableStep(mStepNumber, isStepActionAlreadyDone); - mActionNext.setVisibility(isStepActionAlreadyDone ? View.VISIBLE : View.GONE); - mActionFinish.setVisibility((mStepNumber == STEP_3) ? View.VISIBLE : View.GONE); - } - - static final class SetupStep implements View.OnClickListener { - public final int mStepNo; - private final View mStepView; - private final TextView mBulletView; - private final int mActivatedColor; - private final int mDeactivatedColor; - private final String mInstruction; - private final String mFinishedInstruction; - private final TextView mActionLabel; - private Runnable mAction; - - public SetupStep(final int stepNo, final String applicationName, final TextView bulletView, - final View stepView, final int title, final int instruction, - final int finishedInstruction, final int actionIcon, final int actionLabel) { - mStepNo = stepNo; - mStepView = stepView; - mBulletView = bulletView; - final Resources res = stepView.getResources(); - mActivatedColor = res.getColor(R.color.setup_text_action); - mDeactivatedColor = res.getColor(R.color.setup_text_dark); - - final TextView titleView = (TextView)mStepView.findViewById(R.id.setup_step_title); - titleView.setText(res.getString(title, applicationName)); - mInstruction = (instruction == 0) ? null - : res.getString(instruction, applicationName); - mFinishedInstruction = (finishedInstruction == 0) ? null - : res.getString(finishedInstruction, applicationName); - - mActionLabel = (TextView)mStepView.findViewById(R.id.setup_step_action_label); - mActionLabel.setText(res.getString(actionLabel)); - if (actionIcon == 0) { - final int paddingEnd = ViewCompatUtils.getPaddingEnd(mActionLabel); - ViewCompatUtils.setPaddingRelative(mActionLabel, paddingEnd, 0, paddingEnd, 0); - } else { - TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds( - mActionLabel, res.getDrawable(actionIcon), null, null, null); - } - } - - public void setEnabled(final boolean enabled, final boolean isStepActionAlreadyDone) { - mStepView.setVisibility(enabled ? View.VISIBLE : View.GONE); - mBulletView.setTextColor(enabled ? mActivatedColor : mDeactivatedColor); - final TextView instructionView = (TextView)mStepView.findViewById( - R.id.setup_step_instruction); - instructionView.setText(isStepActionAlreadyDone ? mFinishedInstruction : mInstruction); - mActionLabel.setVisibility(isStepActionAlreadyDone ? View.GONE : View.VISIBLE); - } - - public void setAction(final Runnable action) { - mActionLabel.setOnClickListener(this); - mAction = action; - } - - @Override - public void onClick(final View v) { - if (v == mActionLabel && mAction != null) { - mAction.run(); - return; - } - } - } - - static final class SetupStepGroup { - private final SetupStepIndicatorView mIndicatorView; - private final ArrayList<SetupStep> mGroup = new ArrayList<>(); - - public SetupStepGroup(final SetupStepIndicatorView indicatorView) { - mIndicatorView = indicatorView; - } - - public void addStep(final SetupStep step) { - mGroup.add(step); - } - - public void enableStep(final int enableStepNo, final boolean isStepActionAlreadyDone) { - for (final SetupStep step : mGroup) { - step.setEnabled(step.mStepNo == enableStepNo, isStepActionAlreadyDone); - } - mIndicatorView.setIndicatorPosition(enableStepNo - STEP_1, mGroup.size()); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java deleted file mode 100644 index 4625e8e8b..000000000 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.spellcheck; - -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.service.textservice.SpellCheckerService; -import android.text.InputType; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodSubtype; -import android.view.textservice.SuggestionsInfo; - -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.keyboard.KeyboardLayoutSet; -import com.android.inputmethod.latin.DictionaryFacilitator; -import com.android.inputmethod.latin.DictionaryFacilitatorLruCache; -import com.android.inputmethod.latin.NgramContext; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodSubtype; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.common.ComposedData; -import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; -import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; -import com.android.inputmethod.latin.utils.ScriptUtils; -import com.android.inputmethod.latin.utils.SuggestionResults; - -import java.util.Locale; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Semaphore; - -import javax.annotation.Nonnull; - -/** - * Service for spell checking, using LatinIME's dictionaries and mechanisms. - */ -public final class AndroidSpellCheckerService extends SpellCheckerService - implements SharedPreferences.OnSharedPreferenceChangeListener { - private static final String TAG = AndroidSpellCheckerService.class.getSimpleName(); - private static final boolean DEBUG = false; - - public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts"; - - private static final int SPELLCHECKER_DUMMY_KEYBOARD_WIDTH = 480; - private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 301; - - private static final String DICTIONARY_NAME_PREFIX = "spellcheck_"; - - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - - private final int MAX_NUM_OF_THREADS_READ_DICTIONARY = 2; - private final Semaphore mSemaphore = new Semaphore(MAX_NUM_OF_THREADS_READ_DICTIONARY, - true /* fair */); - // TODO: Make each spell checker session has its own session id. - private final ConcurrentLinkedQueue<Integer> mSessionIdPool = new ConcurrentLinkedQueue<>(); - - private final DictionaryFacilitatorLruCache mDictionaryFacilitatorCache = - new DictionaryFacilitatorLruCache(this /* context */, DICTIONARY_NAME_PREFIX); - private final ConcurrentHashMap<Locale, Keyboard> mKeyboardCache = new ConcurrentHashMap<>(); - - // The threshold for a suggestion to be considered "recommended". - private float mRecommendedThreshold; - // TODO: make a spell checker option to block offensive words or not - private final SettingsValuesForSuggestion mSettingsValuesForSuggestion = - new SettingsValuesForSuggestion(true /* blockPotentiallyOffensive */); - - public static final String SINGLE_QUOTE = "\u0027"; - public static final String APOSTROPHE = "\u2019"; - - public AndroidSpellCheckerService() { - super(); - for (int i = 0; i < MAX_NUM_OF_THREADS_READ_DICTIONARY; i++) { - mSessionIdPool.add(i); - } - } - - @Override - public void onCreate() { - super.onCreate(); - mRecommendedThreshold = Float.parseFloat( - getString(R.string.spellchecker_recommended_threshold_value)); - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - prefs.registerOnSharedPreferenceChangeListener(this); - onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY); - } - - public float getRecommendedThreshold() { - return mRecommendedThreshold; - } - - private static String getKeyboardLayoutNameForLocale(final Locale locale) { - // See b/19963288. - if (locale.getLanguage().equals("sr")) { - return "south_slavic"; - } - final int script = ScriptUtils.getScriptFromSpellCheckerLocale(locale); - switch (script) { - case ScriptUtils.SCRIPT_LATIN: - return "qwerty"; - case ScriptUtils.SCRIPT_CYRILLIC: - return "east_slavic"; - case ScriptUtils.SCRIPT_GREEK: - return "greek"; - case ScriptUtils.SCRIPT_HEBREW: - return "hebrew"; - default: - throw new RuntimeException("Wrong script supplied: " + script); - } - } - - @Override - public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - if (!PREF_USE_CONTACTS_KEY.equals(key)) return; - final boolean useContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true); - mDictionaryFacilitatorCache.setUseContactsDictionary(useContactsDictionary); - } - - @Override - public Session createSession() { - // Should not refer to AndroidSpellCheckerSession directly considering - // that AndroidSpellCheckerSession may be overlaid. - return AndroidSpellCheckerSessionFactory.newInstance(this); - } - - /** - * Returns an empty SuggestionsInfo with flags signaling the word is not in the dictionary. - * @param reportAsTypo whether this should include the flag LOOKS_LIKE_TYPO, for red underline. - * @return the empty SuggestionsInfo with the appropriate flags set. - */ - public static SuggestionsInfo getNotInDictEmptySuggestions(final boolean reportAsTypo) { - return new SuggestionsInfo(reportAsTypo ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0, - EMPTY_STRING_ARRAY); - } - - /** - * Returns an empty suggestionInfo with flags signaling the word is in the dictionary. - * @return the empty SuggestionsInfo with the appropriate flags set. - */ - public static SuggestionsInfo getInDictEmptySuggestions() { - return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY, - EMPTY_STRING_ARRAY); - } - - public boolean isValidWord(final Locale locale, final String word) { - mSemaphore.acquireUninterruptibly(); - try { - DictionaryFacilitator dictionaryFacilitatorForLocale = - mDictionaryFacilitatorCache.get(locale); - return dictionaryFacilitatorForLocale.isValidSpellingWord(word); - } finally { - mSemaphore.release(); - } - } - - public SuggestionResults getSuggestionResults(final Locale locale, - final ComposedData composedData, final NgramContext ngramContext, - @Nonnull final Keyboard keyboard) { - Integer sessionId = null; - mSemaphore.acquireUninterruptibly(); - try { - sessionId = mSessionIdPool.poll(); - DictionaryFacilitator dictionaryFacilitatorForLocale = - mDictionaryFacilitatorCache.get(locale); - return dictionaryFacilitatorForLocale.getSuggestionResults(composedData, ngramContext, - keyboard, mSettingsValuesForSuggestion, - sessionId, SuggestedWords.INPUT_STYLE_TYPING); - } finally { - if (sessionId != null) { - mSessionIdPool.add(sessionId); - } - mSemaphore.release(); - } - } - - public boolean hasMainDictionaryForLocale(final Locale locale) { - mSemaphore.acquireUninterruptibly(); - try { - final DictionaryFacilitator dictionaryFacilitator = - mDictionaryFacilitatorCache.get(locale); - return dictionaryFacilitator.hasAtLeastOneInitializedMainDictionary(); - } finally { - mSemaphore.release(); - } - } - - @Override - public boolean onUnbind(final Intent intent) { - mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY); - try { - mDictionaryFacilitatorCache.closeDictionaries(); - } finally { - mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY); - } - mKeyboardCache.clear(); - return false; - } - - public Keyboard getKeyboardForLocale(final Locale locale) { - Keyboard keyboard = mKeyboardCache.get(locale); - if (keyboard == null) { - keyboard = createKeyboardForLocale(locale); - if (keyboard != null) { - mKeyboardCache.put(locale, keyboard); - } - } - return keyboard; - } - - private Keyboard createKeyboardForLocale(final Locale locale) { - final String keyboardLayoutName = getKeyboardLayoutNameForLocale(locale); - final InputMethodSubtype subtype = AdditionalSubtypeUtils.createDummyAdditionalSubtype( - locale.toString(), keyboardLayoutName); - final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype); - return keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET); - } - - private KeyboardLayoutSet createKeyboardSetForSpellChecker(final InputMethodSubtype subtype) { - final EditorInfo editorInfo = new EditorInfo(); - editorInfo.inputType = InputType.TYPE_CLASS_TEXT; - final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(this, editorInfo); - builder.setKeyboardGeometry( - SPELLCHECKER_DUMMY_KEYBOARD_WIDTH, SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT); - builder.setSubtype(RichInputMethodSubtype.getRichInputMethodSubtype(subtype)); - builder.setIsSpellChecker(true /* isSpellChecker */); - builder.disableTouchPositionCorrectionData(); - return builder.build(); - } -} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java deleted file mode 100644 index c7622e7a1..000000000 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.spellcheck; - -import android.annotation.TargetApi; -import android.content.res.Resources; -import android.os.Binder; -import android.os.Build; -import android.text.TextUtils; -import android.util.Log; -import android.view.textservice.SentenceSuggestionsInfo; -import android.view.textservice.SuggestionsInfo; -import android.view.textservice.TextInfo; - -import com.android.inputmethod.compat.TextInfoCompatUtils; -import com.android.inputmethod.latin.NgramContext; -import com.android.inputmethod.latin.utils.SpannableStringUtils; - -import java.util.ArrayList; -import java.util.Locale; - -public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession { - private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName(); - private static final boolean DBG = false; - private final Resources mResources; - private SentenceLevelAdapter mSentenceLevelAdapter; - - public AndroidSpellCheckerSession(AndroidSpellCheckerService service) { - super(service); - mResources = service.getResources(); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote(TextInfo ti, - SentenceSuggestionsInfo ssi) { - final CharSequence typedText = TextInfoCompatUtils.getCharSequenceOrString(ti); - if (!typedText.toString().contains(AndroidSpellCheckerService.SINGLE_QUOTE)) { - return null; - } - final int N = ssi.getSuggestionsCount(); - final ArrayList<Integer> additionalOffsets = new ArrayList<>(); - final ArrayList<Integer> additionalLengths = new ArrayList<>(); - final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = new ArrayList<>(); - CharSequence currentWord = null; - for (int i = 0; i < N; ++i) { - final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i); - final int flags = si.getSuggestionsAttributes(); - if ((flags & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) == 0) { - continue; - } - final int offset = ssi.getOffsetAt(i); - final int length = ssi.getLengthAt(i); - final CharSequence subText = typedText.subSequence(offset, offset + length); - final NgramContext ngramContext = - new NgramContext(new NgramContext.WordInfo(currentWord)); - currentWord = subText; - if (!subText.toString().contains(AndroidSpellCheckerService.SINGLE_QUOTE)) { - continue; - } - // Split preserving spans. - final CharSequence[] splitTexts = SpannableStringUtils.split(subText, - AndroidSpellCheckerService.SINGLE_QUOTE, - true /* preserveTrailingEmptySegments */); - if (splitTexts == null || splitTexts.length <= 1) { - continue; - } - final int splitNum = splitTexts.length; - for (int j = 0; j < splitNum; ++j) { - final CharSequence splitText = splitTexts[j]; - if (TextUtils.isEmpty(splitText)) { - continue; - } - if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString()) == null) { - continue; - } - final int newLength = splitText.length(); - // Neither RESULT_ATTR_IN_THE_DICTIONARY nor RESULT_ATTR_LOOKS_LIKE_TYPO - final int newFlags = 0; - final SuggestionsInfo newSi = new SuggestionsInfo(newFlags, EMPTY_STRING_ARRAY); - newSi.setCookieAndSequence(si.getCookie(), si.getSequence()); - if (DBG) { - Log.d(TAG, "Override and remove old span over: " + splitText + ", " - + offset + "," + newLength); - } - additionalOffsets.add(offset); - additionalLengths.add(newLength); - additionalSuggestionsInfos.add(newSi); - } - } - final int additionalSize = additionalOffsets.size(); - if (additionalSize <= 0) { - return null; - } - final int suggestionsSize = N + additionalSize; - final int[] newOffsets = new int[suggestionsSize]; - final int[] newLengths = new int[suggestionsSize]; - final SuggestionsInfo[] newSuggestionsInfos = new SuggestionsInfo[suggestionsSize]; - int i; - for (i = 0; i < N; ++i) { - newOffsets[i] = ssi.getOffsetAt(i); - newLengths[i] = ssi.getLengthAt(i); - newSuggestionsInfos[i] = ssi.getSuggestionsInfoAt(i); - } - for (; i < suggestionsSize; ++i) { - newOffsets[i] = additionalOffsets.get(i - N); - newLengths[i] = additionalLengths.get(i - N); - newSuggestionsInfos[i] = additionalSuggestionsInfos.get(i - N); - } - return new SentenceSuggestionsInfo(newSuggestionsInfos, newOffsets, newLengths); - } - - @Override - public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, - int suggestionsLimit) { - final SentenceSuggestionsInfo[] retval = splitAndSuggest(textInfos, suggestionsLimit); - if (retval == null || retval.length != textInfos.length) { - return retval; - } - for (int i = 0; i < retval.length; ++i) { - final SentenceSuggestionsInfo tempSsi = - fixWronglyInvalidatedWordWithSingleQuote(textInfos[i], retval[i]); - if (tempSsi != null) { - retval[i] = tempSsi; - } - } - return retval; - } - - /** - * Get sentence suggestions for specified texts in an array of TextInfo. This is taken from - * SpellCheckerService#onGetSentenceSuggestionsMultiple that we can't use because it's - * using private variables. - * The default implementation splits the input text to words and returns - * {@link SentenceSuggestionsInfo} which contains suggestions for each word. - * This function will run on the incoming IPC thread. - * So, this is not called on the main thread, - * but will be called in series on another thread. - * @param textInfos an array of the text metadata - * @param suggestionsLimit the maximum number of suggestions to be returned - * @return an array of {@link SentenceSuggestionsInfo} returned by - * {@link android.service.textservice.SpellCheckerService.Session#onGetSuggestions(TextInfo, int)} - */ - private SentenceSuggestionsInfo[] splitAndSuggest(TextInfo[] textInfos, int suggestionsLimit) { - if (textInfos == null || textInfos.length == 0) { - return SentenceLevelAdapter.getEmptySentenceSuggestionsInfo(); - } - SentenceLevelAdapter sentenceLevelAdapter; - synchronized(this) { - sentenceLevelAdapter = mSentenceLevelAdapter; - if (sentenceLevelAdapter == null) { - final String localeStr = getLocale(); - if (!TextUtils.isEmpty(localeStr)) { - sentenceLevelAdapter = new SentenceLevelAdapter(mResources, - new Locale(localeStr)); - mSentenceLevelAdapter = sentenceLevelAdapter; - } - } - } - if (sentenceLevelAdapter == null) { - return SentenceLevelAdapter.getEmptySentenceSuggestionsInfo(); - } - final int infosSize = textInfos.length; - final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[infosSize]; - for (int i = 0; i < infosSize; ++i) { - final SentenceLevelAdapter.SentenceTextInfoParams textInfoParams = - sentenceLevelAdapter.getSplitWords(textInfos[i]); - final ArrayList<SentenceLevelAdapter.SentenceWordItem> mItems = - textInfoParams.mItems; - final int itemsSize = mItems.size(); - final TextInfo[] splitTextInfos = new TextInfo[itemsSize]; - for (int j = 0; j < itemsSize; ++j) { - splitTextInfos[j] = mItems.get(j).mTextInfo; - } - retval[i] = SentenceLevelAdapter.reconstructSuggestions( - textInfoParams, onGetSuggestionsMultiple( - splitTextInfos, suggestionsLimit, true)); - } - return retval; - } - - @Override - public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos, - int suggestionsLimit, boolean sequentialWords) { - long ident = Binder.clearCallingIdentity(); - try { - final int length = textInfos.length; - final SuggestionsInfo[] retval = new SuggestionsInfo[length]; - for (int i = 0; i < length; ++i) { - final CharSequence prevWord; - if (sequentialWords && i > 0) { - final TextInfo prevTextInfo = textInfos[i - 1]; - final CharSequence prevWordCandidate = - TextInfoCompatUtils.getCharSequenceOrString(prevTextInfo); - // Note that an empty string would be used to indicate the initial word - // in the future. - prevWord = TextUtils.isEmpty(prevWordCandidate) ? null : prevWordCandidate; - } else { - prevWord = null; - } - final NgramContext ngramContext = - new NgramContext(new NgramContext.WordInfo(prevWord)); - final TextInfo textInfo = textInfos[i]; - retval[i] = onGetSuggestionsInternal(textInfo, ngramContext, suggestionsLimit); - retval[i].setCookieAndSequence(textInfo.getCookie(), textInfo.getSequence()); - } - return retval; - } finally { - Binder.restoreCallingIdentity(ident); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java deleted file mode 100644 index e0418d404..000000000 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.spellcheck; - -import android.service.textservice.SpellCheckerService.Session; - -public abstract class AndroidSpellCheckerSessionFactory { - public static Session newInstance(AndroidSpellCheckerService service) { - return new AndroidSpellCheckerSession(service); - } -} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java deleted file mode 100644 index 9223923a7..000000000 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ /dev/null @@ -1,390 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.spellcheck; - -import android.content.ContentResolver; -import android.database.ContentObserver; -import android.os.Binder; -import android.provider.UserDictionary.Words; -import android.service.textservice.SpellCheckerService.Session; -import android.text.TextUtils; -import android.util.Log; -import android.util.LruCache; -import android.view.textservice.SuggestionsInfo; -import android.view.textservice.TextInfo; - -import com.android.inputmethod.compat.SuggestionsInfoCompatUtils; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.latin.NgramContext; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.WordComposer; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.LocaleUtils; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.ScriptUtils; -import com.android.inputmethod.latin.utils.StatsUtils; -import com.android.inputmethod.latin.utils.SuggestionResults; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -public abstract class AndroidWordLevelSpellCheckerSession extends Session { - private static final String TAG = AndroidWordLevelSpellCheckerSession.class.getSimpleName(); - - public final static String[] EMPTY_STRING_ARRAY = new String[0]; - - // Immutable, but not available in the constructor. - private Locale mLocale; - // Cache this for performance - private int mScript; // One of SCRIPT_LATIN or SCRIPT_CYRILLIC for now. - private final AndroidSpellCheckerService mService; - protected final SuggestionsCache mSuggestionsCache = new SuggestionsCache(); - private final ContentObserver mObserver; - - private static final String quotesRegexp = - "(\\u0022|\\u0027|\\u0060|\\u00B4|\\u2018|\\u2018|\\u201C|\\u201D)"; - - private static final class SuggestionsParams { - public final String[] mSuggestions; - public final int mFlags; - public SuggestionsParams(String[] suggestions, int flags) { - mSuggestions = suggestions; - mFlags = flags; - } - } - - protected static final class SuggestionsCache { - private static final int MAX_CACHE_SIZE = 50; - private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache = - new LruCache<>(MAX_CACHE_SIZE); - - private static String generateKey(final String query) { - return query + ""; - } - - public SuggestionsParams getSuggestionsFromCache(final String query) { - return mUnigramSuggestionsInfoCache.get(query); - } - - public void putSuggestionsToCache( - final String query, final String[] suggestions, final int flags) { - if (suggestions == null || TextUtils.isEmpty(query)) { - return; - } - mUnigramSuggestionsInfoCache.put( - generateKey(query), - new SuggestionsParams(suggestions, flags)); - } - - public void clearCache() { - mUnigramSuggestionsInfoCache.evictAll(); - } - } - - AndroidWordLevelSpellCheckerSession(final AndroidSpellCheckerService service) { - mService = service; - final ContentResolver cres = service.getContentResolver(); - - mObserver = new ContentObserver(null) { - @Override - public void onChange(boolean self) { - mSuggestionsCache.clearCache(); - } - }; - cres.registerContentObserver(Words.CONTENT_URI, true, mObserver); - } - - @Override - public void onCreate() { - final String localeString = getLocale(); - mLocale = (null == localeString) ? null - : LocaleUtils.constructLocaleFromString(localeString); - mScript = ScriptUtils.getScriptFromSpellCheckerLocale(mLocale); - } - - @Override - public void onClose() { - final ContentResolver cres = mService.getContentResolver(); - cres.unregisterContentObserver(mObserver); - } - - private static final int CHECKABILITY_CHECKABLE = 0; - private static final int CHECKABILITY_TOO_MANY_NON_LETTERS = 1; - private static final int CHECKABILITY_CONTAINS_PERIOD = 2; - private static final int CHECKABILITY_EMAIL_OR_URL = 3; - private static final int CHECKABILITY_FIRST_LETTER_UNCHECKABLE = 4; - private static final int CHECKABILITY_TOO_SHORT = 5; - /** - * Finds out whether a particular string should be filtered out of spell checking. - * - * This will loosely match URLs, numbers, symbols. To avoid always underlining words that - * we know we will never recognize, this accepts a script identifier that should be one - * of the SCRIPT_* constants defined above, to rule out quickly characters from very - * different languages. - * - * @param text the string to evaluate. - * @param script the identifier for the script this spell checker recognizes - * @return one of the FILTER_OUT_* constants above. - */ - private static int getCheckabilityInScript(final String text, final int script) { - if (TextUtils.isEmpty(text) || text.length() <= 1) return CHECKABILITY_TOO_SHORT; - - // TODO: check if an equivalent processing can't be done more quickly with a - // compiled regexp. - // Filter by first letter - final int firstCodePoint = text.codePointAt(0); - // Filter out words that don't start with a letter or an apostrophe - if (!ScriptUtils.isLetterPartOfScript(firstCodePoint, script) - && '\'' != firstCodePoint) return CHECKABILITY_FIRST_LETTER_UNCHECKABLE; - - // Filter contents - final int length = text.length(); - int letterCount = 0; - for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) { - final int codePoint = text.codePointAt(i); - // Any word containing a COMMERCIAL_AT is probably an e-mail address - // Any word containing a SLASH is probably either an ad-hoc combination of two - // words or a URI - in either case we don't want to spell check that - if (Constants.CODE_COMMERCIAL_AT == codePoint || Constants.CODE_SLASH == codePoint) { - return CHECKABILITY_EMAIL_OR_URL; - } - // If the string contains a period, native returns strange suggestions (it seems - // to return suggestions for everything up to the period only and to ignore the - // rest), so we suppress lookup if there is a period. - // TODO: investigate why native returns these suggestions and remove this code. - if (Constants.CODE_PERIOD == codePoint) { - return CHECKABILITY_CONTAINS_PERIOD; - } - if (ScriptUtils.isLetterPartOfScript(codePoint, script)) ++letterCount; - } - // Guestimate heuristic: perform spell checking if at least 3/4 of the characters - // in this word are letters - return (letterCount * 4 < length * 3) - ? CHECKABILITY_TOO_MANY_NON_LETTERS : CHECKABILITY_CHECKABLE; - } - - /** - * Helper method to test valid capitalizations of a word. - * - * If the "text" is lower-case, we test only the exact string. - * If the "Text" is capitalized, we test the exact string "Text" and the lower-cased - * version of it "text". - * If the "TEXT" is fully upper case, we test the exact string "TEXT", the lower-cased - * version of it "text" and the capitalized version of it "Text". - */ - private boolean isInDictForAnyCapitalization(final String text, final int capitalizeType) { - // If the word is in there as is, then it's in the dictionary. If not, we'll test lower - // case versions, but only if the word is not already all-lower case or mixed case. - if (mService.isValidWord(mLocale, text)) return true; - if (StringUtils.CAPITALIZE_NONE == capitalizeType) return false; - - // If we come here, we have a capitalized word (either First- or All-). - // Downcase the word and look it up again. If the word is only capitalized, we - // tested all possibilities, so if it's still negative we can return false. - final String lowerCaseText = text.toLowerCase(mLocale); - if (mService.isValidWord(mLocale, lowerCaseText)) return true; - if (StringUtils.CAPITALIZE_FIRST == capitalizeType) return false; - - // If the lower case version is not in the dictionary, it's still possible - // that we have an all-caps version of a word that needs to be capitalized - // according to the dictionary. E.g. "GERMANS" only exists in the dictionary as "Germans". - return mService.isValidWord(mLocale, - StringUtils.capitalizeFirstAndDowncaseRest(lowerCaseText, mLocale)); - } - - // Note : this must be reentrant - /** - * Gets a list of suggestions for a specific string. This returns a list of possible - * corrections for the text passed as an argument. It may split or group words, and - * even perform grammatical analysis. - */ - private SuggestionsInfo onGetSuggestionsInternal(final TextInfo textInfo, - final int suggestionsLimit) { - return onGetSuggestionsInternal(textInfo, null, suggestionsLimit); - } - - protected SuggestionsInfo onGetSuggestionsInternal( - final TextInfo textInfo, final NgramContext ngramContext, final int suggestionsLimit) { - try { - final String text = textInfo.getText(). - replaceAll(AndroidSpellCheckerService.APOSTROPHE, - AndroidSpellCheckerService.SINGLE_QUOTE). - replaceAll("^" + quotesRegexp, ""). - replaceAll(quotesRegexp + "$", ""); - - if (!mService.hasMainDictionaryForLocale(mLocale)) { - return AndroidSpellCheckerService.getNotInDictEmptySuggestions( - false /* reportAsTypo */); - } - - // Handle special patterns like email, URI, telephone number. - final int checkability = getCheckabilityInScript(text, mScript); - if (CHECKABILITY_CHECKABLE != checkability) { - if (CHECKABILITY_CONTAINS_PERIOD == checkability) { - final String[] splitText = text.split(Constants.REGEXP_PERIOD); - boolean allWordsAreValid = true; - for (final String word : splitText) { - if (!mService.isValidWord(mLocale, word)) { - allWordsAreValid = false; - break; - } - } - if (allWordsAreValid) { - return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO - | SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS, - new String[] { - TextUtils.join(Constants.STRING_SPACE, splitText) }); - } - } - return mService.isValidWord(mLocale, text) ? - AndroidSpellCheckerService.getInDictEmptySuggestions() : - AndroidSpellCheckerService.getNotInDictEmptySuggestions( - CHECKABILITY_CONTAINS_PERIOD == checkability /* reportAsTypo */); - } - - // Handle normal words. - final int capitalizeType = StringUtils.getCapitalizationType(text); - - if (isInDictForAnyCapitalization(text, capitalizeType)) { - if (DebugFlags.DEBUG_ENABLED) { - Log.i(TAG, "onGetSuggestionsInternal() : [" + text + "] is a valid word"); - } - return AndroidSpellCheckerService.getInDictEmptySuggestions(); - } - if (DebugFlags.DEBUG_ENABLED) { - Log.i(TAG, "onGetSuggestionsInternal() : [" + text + "] is NOT a valid word"); - } - - final Keyboard keyboard = mService.getKeyboardForLocale(mLocale); - if (null == keyboard) { - Log.w(TAG, "onGetSuggestionsInternal() : No keyboard for locale: " + mLocale); - // If there is no keyboard for this locale, don't do any spell-checking. - return AndroidSpellCheckerService.getNotInDictEmptySuggestions( - false /* reportAsTypo */); - } - - final WordComposer composer = new WordComposer(); - final int[] codePoints = StringUtils.toCodePointArray(text); - final int[] coordinates; - coordinates = keyboard.getCoordinates(codePoints); - composer.setComposingWord(codePoints, coordinates); - // TODO: Don't gather suggestions if the limit is <= 0 unless necessary - final SuggestionResults suggestionResults = mService.getSuggestionResults( - mLocale, composer.getComposedDataSnapshot(), ngramContext, keyboard); - final Result result = getResult(capitalizeType, mLocale, suggestionsLimit, - mService.getRecommendedThreshold(), text, suggestionResults); - if (DebugFlags.DEBUG_ENABLED) { - if (result.mSuggestions != null && result.mSuggestions.length > 0) { - final StringBuilder builder = new StringBuilder(); - for (String suggestion : result.mSuggestions) { - builder.append(" ["); - builder.append(suggestion); - builder.append("]"); - } - Log.i(TAG, "onGetSuggestionsInternal() : Suggestions =" + builder); - } - } - // Handle word not in dictionary. - // This is called only once per unique word, so entering multiple - // instances of the same word does not result in more than one call - // to this method. - // Also, upon changing the orientation of the device, this is called - // again for every unique invalid word in the text box. - StatsUtils.onInvalidWordIdentification(text); - - final int flags = - SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO - | (result.mHasRecommendedSuggestions - ? SuggestionsInfoCompatUtils - .getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS() - : 0); - final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions); - mSuggestionsCache.putSuggestionsToCache(text, result.mSuggestions, flags); - return retval; - } catch (RuntimeException e) { - // Don't kill the keyboard if there is a bug in the spell checker - Log.e(TAG, "Exception while spellchecking", e); - return AndroidSpellCheckerService.getNotInDictEmptySuggestions( - false /* reportAsTypo */); - } - } - - private static final class Result { - public final String[] mSuggestions; - public final boolean mHasRecommendedSuggestions; - public Result(final String[] gatheredSuggestions, final boolean hasRecommendedSuggestions) { - mSuggestions = gatheredSuggestions; - mHasRecommendedSuggestions = hasRecommendedSuggestions; - } - } - - private static Result getResult(final int capitalizeType, final Locale locale, - final int suggestionsLimit, final float recommendedThreshold, final String originalText, - final SuggestionResults suggestionResults) { - if (suggestionResults.isEmpty() || suggestionsLimit <= 0) { - return new Result(null /* gatheredSuggestions */, - false /* hasRecommendedSuggestions */); - } - final ArrayList<String> suggestions = new ArrayList<>(); - for (final SuggestedWordInfo suggestedWordInfo : suggestionResults) { - final String suggestion; - if (StringUtils.CAPITALIZE_ALL == capitalizeType) { - suggestion = suggestedWordInfo.mWord.toUpperCase(locale); - } else if (StringUtils.CAPITALIZE_FIRST == capitalizeType) { - suggestion = StringUtils.capitalizeFirstCodePoint( - suggestedWordInfo.mWord, locale); - } else { - suggestion = suggestedWordInfo.mWord; - } - suggestions.add(suggestion); - } - StringUtils.removeDupes(suggestions); - // This returns a String[], while toArray() returns an Object[] which cannot be cast - // into a String[]. - final List<String> gatheredSuggestionsList = - suggestions.subList(0, Math.min(suggestions.size(), suggestionsLimit)); - final String[] gatheredSuggestions = - gatheredSuggestionsList.toArray(new String[gatheredSuggestionsList.size()]); - - final int bestScore = suggestionResults.first().mScore; - final String bestSuggestion = suggestions.get(0); - final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore( - originalText, bestSuggestion, bestScore); - final boolean hasRecommendedSuggestions = (normalizedScore > recommendedThreshold); - return new Result(gatheredSuggestions, hasRecommendedSuggestions); - } - - /* - * The spell checker acts on its own behalf. That is needed, in particular, to be able to - * access the dictionary files, which the provider restricts to the identity of Latin IME. - * Since it's called externally by the application, the spell checker is using the identity - * of the application by default unless we clearCallingIdentity. - * That's what the following method does. - */ - @Override - public SuggestionsInfo onGetSuggestions(final TextInfo textInfo, final int suggestionsLimit) { - long ident = Binder.clearCallingIdentity(); - try { - return onGetSuggestionsInternal(textInfo, suggestionsLimit); - } finally { - Binder.restoreCallingIdentity(ident); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java deleted file mode 100644 index 10c458c7d..000000000 --- a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.spellcheck; - -import android.annotation.TargetApi; -import android.content.res.Resources; -import android.os.Build; -import android.view.textservice.SentenceSuggestionsInfo; -import android.view.textservice.SuggestionsInfo; -import android.view.textservice.TextInfo; - -import com.android.inputmethod.compat.TextInfoCompatUtils; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.settings.SpacingAndPunctuations; -import com.android.inputmethod.latin.utils.RunInLocale; - -import java.util.ArrayList; -import java.util.Locale; - -/** - * This code is mostly lifted directly from android.service.textservice.SpellCheckerService in - * the framework; maybe that should be protected instead, so that implementers don't have to - * rewrite everything for any small change. - */ -public class SentenceLevelAdapter { - private static class EmptySentenceSuggestionsInfosInitializationHolder { - public static final SentenceSuggestionsInfo[] EMPTY_SENTENCE_SUGGESTIONS_INFOS = - new SentenceSuggestionsInfo[]{}; - } - private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, null); - - public static SentenceSuggestionsInfo[] getEmptySentenceSuggestionsInfo() { - return EmptySentenceSuggestionsInfosInitializationHolder.EMPTY_SENTENCE_SUGGESTIONS_INFOS; - } - - /** - * Container for split TextInfo parameters - */ - public static class SentenceWordItem { - public final TextInfo mTextInfo; - public final int mStart; - public final int mLength; - public SentenceWordItem(TextInfo ti, int start, int end) { - mTextInfo = ti; - mStart = start; - mLength = end - start; - } - } - - /** - * Container for originally queried TextInfo and parameters - */ - public static class SentenceTextInfoParams { - final TextInfo mOriginalTextInfo; - final ArrayList<SentenceWordItem> mItems; - final int mSize; - public SentenceTextInfoParams(TextInfo ti, ArrayList<SentenceWordItem> items) { - mOriginalTextInfo = ti; - mItems = items; - mSize = items.size(); - } - } - - private static class WordIterator { - private final SpacingAndPunctuations mSpacingAndPunctuations; - public WordIterator(final Resources res, final Locale locale) { - final RunInLocale<SpacingAndPunctuations> job = - new RunInLocale<SpacingAndPunctuations>() { - @Override - protected SpacingAndPunctuations job(final Resources r) { - return new SpacingAndPunctuations(r); - } - }; - mSpacingAndPunctuations = job.runInLocale(res, locale); - } - - public int getEndOfWord(final CharSequence sequence, final int fromIndex) { - final int length = sequence.length(); - int index = fromIndex < 0 ? 0 : Character.offsetByCodePoints(sequence, fromIndex, 1); - while (index < length) { - final int codePoint = Character.codePointAt(sequence, index); - if (mSpacingAndPunctuations.isWordSeparator(codePoint)) { - // If it's a period, we want to stop here only if it's followed by another - // word separator. In all other cases we stop here. - if (Constants.CODE_PERIOD == codePoint) { - final int indexOfNextCodePoint = - index + Character.charCount(Constants.CODE_PERIOD); - if (indexOfNextCodePoint < length - && mSpacingAndPunctuations.isWordSeparator( - Character.codePointAt(sequence, indexOfNextCodePoint))) { - return index; - } - } else { - return index; - } - } - index += Character.charCount(codePoint); - } - return index; - } - - public int getBeginningOfNextWord(final CharSequence sequence, final int fromIndex) { - final int length = sequence.length(); - if (fromIndex >= length) { - return -1; - } - int index = fromIndex < 0 ? 0 : Character.offsetByCodePoints(sequence, fromIndex, 1); - while (index < length) { - final int codePoint = Character.codePointAt(sequence, index); - if (!mSpacingAndPunctuations.isWordSeparator(codePoint)) { - return index; - } - index += Character.charCount(codePoint); - } - return -1; - } - } - - private final WordIterator mWordIterator; - public SentenceLevelAdapter(final Resources res, final Locale locale) { - mWordIterator = new WordIterator(res, locale); - } - - public SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) { - final WordIterator wordIterator = mWordIterator; - final CharSequence originalText = - TextInfoCompatUtils.getCharSequenceOrString(originalTextInfo); - final int cookie = originalTextInfo.getCookie(); - final int start = -1; - final int end = originalText.length(); - final ArrayList<SentenceWordItem> wordItems = new ArrayList<>(); - int wordStart = wordIterator.getBeginningOfNextWord(originalText, start); - int wordEnd = wordIterator.getEndOfWord(originalText, wordStart); - while (wordStart <= end && wordEnd != -1 && wordStart != -1) { - if (wordEnd >= start && wordEnd > wordStart) { - final TextInfo ti = TextInfoCompatUtils.newInstance(originalText, wordStart, - wordEnd, cookie, originalText.subSequence(wordStart, wordEnd).hashCode()); - wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd)); - } - wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd); - if (wordStart == -1) { - break; - } - wordEnd = wordIterator.getEndOfWord(originalText, wordStart); - } - return new SentenceTextInfoParams(originalTextInfo, wordItems); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - public static SentenceSuggestionsInfo reconstructSuggestions( - SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) { - if (results == null || results.length == 0) { - return null; - } - if (originalTextInfoParams == null) { - return null; - } - final int originalCookie = originalTextInfoParams.mOriginalTextInfo.getCookie(); - final int originalSequence = - originalTextInfoParams.mOriginalTextInfo.getSequence(); - - final int querySize = originalTextInfoParams.mSize; - final int[] offsets = new int[querySize]; - final int[] lengths = new int[querySize]; - final SuggestionsInfo[] reconstructedSuggestions = new SuggestionsInfo[querySize]; - for (int i = 0; i < querySize; ++i) { - final SentenceWordItem item = originalTextInfoParams.mItems.get(i); - SuggestionsInfo result = null; - for (int j = 0; j < results.length; ++j) { - final SuggestionsInfo cur = results[j]; - if (cur != null && cur.getSequence() == item.mTextInfo.getSequence()) { - result = cur; - result.setCookieAndSequence(originalCookie, originalSequence); - break; - } - } - offsets[i] = item.mStart; - lengths[i] = item.mLength; - reconstructedSuggestions[i] = result != null ? result : EMPTY_SUGGESTIONS_INFO; - } - return new SentenceSuggestionsInfo(reconstructedSuggestions, offsets, lengths); - } -} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java deleted file mode 100644 index 5f99f9004..000000000 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.spellcheck; - -import com.android.inputmethod.latin.permissions.PermissionsManager; -import com.android.inputmethod.latin.utils.FragmentUtils; - -import android.annotation.TargetApi; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceActivity; -import androidx.core.app.ActivityCompat; - -/** - * Spell checker preference screen. - */ -public final class SpellCheckerSettingsActivity extends PreferenceActivity - implements ActivityCompat.OnRequestPermissionsResultCallback { - private static final String DEFAULT_FRAGMENT = SpellCheckerSettingsFragment.class.getName(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public Intent getIntent() { - final Intent modIntent = new Intent(super.getIntent()); - modIntent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT); - modIntent.putExtra(EXTRA_NO_HEADERS, true); - return modIntent; - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - @Override - public boolean isValidFragment(String fragmentName) { - return FragmentUtils.isValidFragment(fragmentName); - } - - @Override - public void onRequestPermissionsResult( - int requestCode, String[] permissions, int[] grantResults) { - PermissionsManager.get(this).onRequestPermissionsResult( - requestCode, permissions, grantResults); - } -} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java deleted file mode 100644 index 12005c25e..000000000 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.spellcheck; - -import android.Manifest; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.PreferenceScreen; -import android.preference.SwitchPreference; -import android.text.TextUtils; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.permissions.PermissionsManager; -import com.android.inputmethod.latin.permissions.PermissionsUtil; -import com.android.inputmethod.latin.settings.SubScreenFragment; -import com.android.inputmethod.latin.settings.TwoStatePreferenceHelper; -import com.android.inputmethod.latin.utils.ApplicationUtils; - -import static com.android.inputmethod.latin.permissions.PermissionsManager.get; - -/** - * Preference screen. - */ -public final class SpellCheckerSettingsFragment extends SubScreenFragment - implements SharedPreferences.OnSharedPreferenceChangeListener, - PermissionsManager.PermissionsResultCallback { - - private SwitchPreference mLookupContactsPreference; - - @Override - public void onActivityCreated(final Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - addPreferencesFromResource(R.xml.spell_checker_settings); - final PreferenceScreen preferenceScreen = getPreferenceScreen(); - preferenceScreen.setTitle(ApplicationUtils.getActivityTitleResId( - getActivity(), SpellCheckerSettingsActivity.class)); - TwoStatePreferenceHelper.replaceCheckBoxPreferencesBySwitchPreferences(preferenceScreen); - - mLookupContactsPreference = (SwitchPreference) findPreference( - AndroidSpellCheckerService.PREF_USE_CONTACTS_KEY); - turnOffLookupContactsIfNoPermission(); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (!TextUtils.equals(key, AndroidSpellCheckerService.PREF_USE_CONTACTS_KEY)) { - return; - } - - if (!sharedPreferences.getBoolean(key, false)) { - // don't care if the preference is turned off. - return; - } - - // Check for permissions. - if (PermissionsUtil.checkAllPermissionsGranted( - getActivity() /* context */, Manifest.permission.READ_CONTACTS)) { - return; // all permissions granted, no need to request permissions. - } - - get(getActivity() /* context */).requestPermissions(this /* PermissionsResultCallback */, - getActivity() /* activity */, Manifest.permission.READ_CONTACTS); - } - - @Override - public void onRequestPermissionsResult(boolean allGranted) { - turnOffLookupContactsIfNoPermission(); - } - - private void turnOffLookupContactsIfNoPermission() { - if (!PermissionsUtil.checkAllPermissionsGranted( - getActivity(), Manifest.permission.READ_CONTACTS)) { - mLookupContactsPreference.setChecked(false); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java deleted file mode 100644 index 37ab2669b..000000000 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.suggestions; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.internal.KeyboardBuilder; -import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; -import com.android.inputmethod.keyboard.internal.KeyboardParams; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.utils.TypefaceUtils; - -public final class MoreSuggestions extends Keyboard { - public final SuggestedWords mSuggestedWords; - - MoreSuggestions(final MoreSuggestionsParam params, final SuggestedWords suggestedWords) { - super(params); - mSuggestedWords = suggestedWords; - } - - private static final class MoreSuggestionsParam extends KeyboardParams { - private final int[] mWidths = new int[SuggestedWords.MAX_SUGGESTIONS]; - private final int[] mRowNumbers = new int[SuggestedWords.MAX_SUGGESTIONS]; - private final int[] mColumnOrders = new int[SuggestedWords.MAX_SUGGESTIONS]; - private final int[] mNumColumnsInRow = new int[SuggestedWords.MAX_SUGGESTIONS]; - private static final int MAX_COLUMNS_IN_ROW = 3; - private int mNumRows; - public Drawable mDivider; - public int mDividerWidth; - - public MoreSuggestionsParam() { - super(); - } - - public int layout(final SuggestedWords suggestedWords, final int fromIndex, - final int maxWidth, final int minWidth, final int maxRow, final Paint paint, - final Resources res) { - clearKeys(); - mDivider = res.getDrawable(R.drawable.more_suggestions_divider); - mDividerWidth = mDivider.getIntrinsicWidth(); - final float padding = res.getDimension( - R.dimen.config_more_suggestions_key_horizontal_padding); - - int row = 0; - int index = fromIndex; - int rowStartIndex = fromIndex; - final int size = Math.min(suggestedWords.size(), SuggestedWords.MAX_SUGGESTIONS); - while (index < size) { - final String word; - if (isIndexSubjectToAutoCorrection(suggestedWords, index)) { - // INDEX_OF_AUTO_CORRECTION and INDEX_OF_TYPED_WORD got swapped. - word = suggestedWords.getLabel(SuggestedWords.INDEX_OF_TYPED_WORD); - } else { - word = suggestedWords.getLabel(index); - } - // TODO: Should take care of text x-scaling. - mWidths[index] = (int)(TypefaceUtils.getStringWidth(word, paint) + padding); - final int numColumn = index - rowStartIndex + 1; - final int columnWidth = - (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn; - if (numColumn > MAX_COLUMNS_IN_ROW - || !fitInWidth(rowStartIndex, index + 1, columnWidth)) { - if ((row + 1) >= maxRow) { - break; - } - mNumColumnsInRow[row] = index - rowStartIndex; - rowStartIndex = index; - row++; - } - mColumnOrders[index] = index - rowStartIndex; - mRowNumbers[index] = row; - index++; - } - mNumColumnsInRow[row] = index - rowStartIndex; - mNumRows = row + 1; - mBaseWidth = mOccupiedWidth = Math.max( - minWidth, calcurateMaxRowWidth(fromIndex, index)); - mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; - return index - fromIndex; - } - - private boolean fitInWidth(final int startIndex, final int endIndex, final int width) { - for (int index = startIndex; index < endIndex; index++) { - if (mWidths[index] > width) - return false; - } - return true; - } - - private int calcurateMaxRowWidth(final int startIndex, final int endIndex) { - int maxRowWidth = 0; - int index = startIndex; - for (int row = 0; row < mNumRows; row++) { - final int numColumnInRow = mNumColumnsInRow[row]; - int maxKeyWidth = 0; - while (index < endIndex && mRowNumbers[index] == row) { - maxKeyWidth = Math.max(maxKeyWidth, mWidths[index]); - index++; - } - maxRowWidth = Math.max(maxRowWidth, - maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1)); - } - return maxRowWidth; - } - - private static final int[][] COLUMN_ORDER_TO_NUMBER = { - { 0 }, // center - { 1, 0 }, // right-left - { 1, 0, 2 }, // center-left-right - }; - - public int getNumColumnInRow(final int index) { - return mNumColumnsInRow[mRowNumbers[index]]; - } - - public int getColumnNumber(final int index) { - final int columnOrder = mColumnOrders[index]; - final int numColumn = getNumColumnInRow(index); - return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder]; - } - - public int getX(final int index) { - final int columnNumber = getColumnNumber(index); - return columnNumber * (getWidth(index) + mDividerWidth); - } - - public int getY(final int index) { - final int row = mRowNumbers[index]; - return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; - } - - public int getWidth(final int index) { - final int numColumnInRow = getNumColumnInRow(index); - return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; - } - - public void markAsEdgeKey(final Key key, final int index) { - final int row = mRowNumbers[index]; - if (row == 0) - key.markAsBottomEdge(this); - if (row == mNumRows - 1) - key.markAsTopEdge(this); - - final int numColumnInRow = mNumColumnsInRow[row]; - final int column = getColumnNumber(index); - if (column == 0) - key.markAsLeftEdge(this); - if (column == numColumnInRow - 1) - key.markAsRightEdge(this); - } - } - - static boolean isIndexSubjectToAutoCorrection(final SuggestedWords suggestedWords, - final int index) { - return suggestedWords.mWillAutoCorrect && index == SuggestedWords.INDEX_OF_AUTO_CORRECTION; - } - - public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> { - private final MoreSuggestionsView mPaneView; - private SuggestedWords mSuggestedWords; - private int mFromIndex; - private int mToIndex; - - public Builder(final Context context, final MoreSuggestionsView paneView) { - super(context, new MoreSuggestionsParam()); - mPaneView = paneView; - } - - public Builder layout(final SuggestedWords suggestedWords, final int fromIndex, - final int maxWidth, final int minWidth, final int maxRow, - final Keyboard parentKeyboard) { - final int xmlId = R.xml.kbd_suggestions_pane_template; - load(xmlId, parentKeyboard.mId); - mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2; - mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight); - final int count = mParams.layout(suggestedWords, fromIndex, maxWidth, minWidth, maxRow, - mPaneView.newLabelPaint(null /* key */), mResources); - mFromIndex = fromIndex; - mToIndex = fromIndex + count; - mSuggestedWords = suggestedWords; - return this; - } - - @Override - public MoreSuggestions build() { - final MoreSuggestionsParam params = mParams; - for (int index = mFromIndex; index < mToIndex; index++) { - final int x = params.getX(index); - final int y = params.getY(index); - final int width = params.getWidth(index); - final String word; - final String info; - if (isIndexSubjectToAutoCorrection(mSuggestedWords, index)) { - // INDEX_OF_AUTO_CORRECTION and INDEX_OF_TYPED_WORD got swapped. - word = mSuggestedWords.getLabel(SuggestedWords.INDEX_OF_TYPED_WORD); - info = mSuggestedWords.getDebugString(SuggestedWords.INDEX_OF_TYPED_WORD); - } else { - word = mSuggestedWords.getLabel(index); - info = mSuggestedWords.getDebugString(index); - } - final Key key = new MoreSuggestionKey(word, info, index, params); - params.markAsEdgeKey(key, index); - params.onAddKey(key); - final int columnNumber = params.getColumnNumber(index); - final int numColumnInRow = params.getNumColumnInRow(index); - if (columnNumber < numColumnInRow - 1) { - final Divider divider = new Divider(params, params.mDivider, x + width, y, - params.mDividerWidth, params.mDefaultRowHeight); - params.onAddKey(divider); - } - } - return new MoreSuggestions(params, mSuggestedWords); - } - } - - static final class MoreSuggestionKey extends Key { - public final int mSuggestedWordIndex; - - public MoreSuggestionKey(final String word, final String info, final int index, - final MoreSuggestionsParam params) { - super(word /* label */, KeyboardIconsSet.ICON_UNDEFINED, Constants.CODE_OUTPUT_TEXT, - word /* outputText */, info, 0 /* labelFlags */, Key.BACKGROUND_TYPE_NORMAL, - params.getX(index), params.getY(index), params.getWidth(index), - params.mDefaultRowHeight, params.mHorizontalGap, params.mVerticalGap); - mSuggestedWordIndex = index; - } - } - - private static final class Divider extends Key.Spacer { - private final Drawable mIcon; - - public Divider(final KeyboardParams params, final Drawable icon, final int x, - final int y, final int width, final int height) { - super(params, x, y, width, height); - mIcon = icon; - } - - @Override - public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { - // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the - // constructor. - // TODO: Drawable itself should have an alpha value. - mIcon.setAlpha(128); - return mIcon; - } - } -} diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java deleted file mode 100644 index 907e3fa42..000000000 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.suggestions; - -import android.content.Context; -import android.util.AttributeSet; -import android.util.Log; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.keyboard.MoreKeysKeyboardView; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionKey; - -/** - * A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting - * key presses and touch movements. - */ -public final class MoreSuggestionsView extends MoreKeysKeyboardView { - private static final String TAG = MoreSuggestionsView.class.getSimpleName(); - - public static abstract class MoreSuggestionsListener extends KeyboardActionListener.Adapter { - public abstract void onSuggestionSelected(final SuggestedWordInfo info); - } - - private boolean mIsInModalMode; - - public MoreSuggestionsView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.moreKeysKeyboardViewStyle); - } - - public MoreSuggestionsView(final Context context, final AttributeSet attrs, - final int defStyle) { - super(context, attrs, defStyle); - } - - // TODO: Remove redundant override method. - @Override - public void setKeyboard(final Keyboard keyboard) { - super.setKeyboard(keyboard); - mIsInModalMode = false; - // With accessibility mode off, {@link #mAccessibilityDelegate} is set to null at the - // above {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call. - // With accessibility mode on, {@link #mAccessibilityDelegate} is set to a - // {@link MoreKeysKeyboardAccessibilityDelegate} object at the above - // {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call. - if (mAccessibilityDelegate != null) { - mAccessibilityDelegate.setOpenAnnounce(R.string.spoken_open_more_suggestions); - mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_suggestions); - } - } - - @Override - protected int getDefaultCoordX() { - final MoreSuggestions pane = (MoreSuggestions)getKeyboard(); - return pane.mOccupiedWidth / 2; - } - - public void updateKeyboardGeometry(final int keyHeight) { - updateKeyDrawParams(keyHeight); - } - - public void setModalMode() { - mIsInModalMode = true; - // Set vertical correction to zero (Reset more keys keyboard sliding allowance - // {@link R#dimen.config_more_keys_keyboard_slide_allowance}). - mKeyDetector.setKeyboard(getKeyboard(), -getPaddingLeft(), -getPaddingTop()); - } - - public boolean isInModalMode() { - return mIsInModalMode; - } - - @Override - protected void onKeyInput(final Key key, final int x, final int y) { - if (!(key instanceof MoreSuggestionKey)) { - Log.e(TAG, "Expected key is MoreSuggestionKey, but found " - + key.getClass().getName()); - return; - } - final Keyboard keyboard = getKeyboard(); - if (!(keyboard instanceof MoreSuggestions)) { - Log.e(TAG, "Expected keyboard is MoreSuggestions, but found " - + keyboard.getClass().getName()); - return; - } - final SuggestedWords suggestedWords = ((MoreSuggestions)keyboard).mSuggestedWords; - final int index = ((MoreSuggestionKey)key).mSuggestedWordIndex; - if (index < 0 || index >= suggestedWords.size()) { - Log.e(TAG, "Selected suggestion has an illegal index: " + index); - return; - } - if (!(mListener instanceof MoreSuggestionsListener)) { - Log.e(TAG, "Expected mListener is MoreSuggestionsListener, but found " - + mListener.getClass().getName()); - return; - } - ((MoreSuggestionsListener)mListener).onSuggestionSelected(suggestedWords.getInfo(index)); - } -} diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java deleted file mode 100644 index 9577d0913..000000000 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java +++ /dev/null @@ -1,650 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.suggestions; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.CharacterStyle; -import android.text.style.StyleSpan; -import android.text.style.UnderlineSpan; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.inputmethod.accessibility.AccessibilityUtils; -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.PunctuationSuggestions; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.settings.SettingsValues; -import com.android.inputmethod.latin.utils.ResourceUtils; -import com.android.inputmethod.latin.utils.ViewLayoutUtils; - -import java.util.ArrayList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -final class SuggestionStripLayoutHelper { - private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; - private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f; - private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2; - private static final int PUNCTUATIONS_IN_STRIP = 5; - private static final float MIN_TEXT_XSCALE = 0.70f; - - public final int mPadding; - public final int mDividerWidth; - public final int mSuggestionsStripHeight; - private final int mSuggestionsCountInStrip; - public final int mMoreSuggestionsRowHeight; - private int mMaxMoreSuggestionsRow; - public final float mMinMoreSuggestionsWidth; - public final int mMoreSuggestionsBottomGap; - private boolean mMoreSuggestionsAvailable; - - // The index of these {@link ArrayList} is the position in the suggestion strip. The indices - // increase towards the right for LTR scripts and the left for RTL scripts, starting with 0. - // The position of the most important suggestion is in {@link #mCenterPositionInStrip} - private final ArrayList<TextView> mWordViews; - private final ArrayList<View> mDividerViews; - private final ArrayList<TextView> mDebugInfoViews; - - private final int mColorValidTypedWord; - private final int mColorTypedWord; - private final int mColorAutoCorrect; - private final int mColorSuggested; - private final float mAlphaObsoleted; - private final float mCenterSuggestionWeight; - private final int mCenterPositionInStrip; - private final int mTypedWordPositionWhenAutocorrect; - private final Drawable mMoreSuggestionsHint; - private static final String MORE_SUGGESTIONS_HINT = "\u2026"; - - private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); - private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); - - private final int mSuggestionStripOptions; - // These constants are the flag values of - // {@link R.styleable#SuggestionStripView_suggestionStripOptions} attribute. - private static final int AUTO_CORRECT_BOLD = 0x01; - private static final int AUTO_CORRECT_UNDERLINE = 0x02; - private static final int VALID_TYPED_WORD_BOLD = 0x04; - - public SuggestionStripLayoutHelper(final Context context, final AttributeSet attrs, - final int defStyle, final ArrayList<TextView> wordViews, - final ArrayList<View> dividerViews, final ArrayList<TextView> debugInfoViews) { - mWordViews = wordViews; - mDividerViews = dividerViews; - mDebugInfoViews = debugInfoViews; - - final TextView wordView = wordViews.get(0); - final View dividerView = dividerViews.get(0); - mPadding = wordView.getCompoundPaddingLeft() + wordView.getCompoundPaddingRight(); - dividerView.measure( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - mDividerWidth = dividerView.getMeasuredWidth(); - - final Resources res = wordView.getResources(); - mSuggestionsStripHeight = res.getDimensionPixelSize( - R.dimen.config_suggestions_strip_height); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripView); - mSuggestionStripOptions = a.getInt( - R.styleable.SuggestionStripView_suggestionStripOptions, 0); - mAlphaObsoleted = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_alphaObsoleted, 1.0f); - mColorValidTypedWord = a.getColor(R.styleable.SuggestionStripView_colorValidTypedWord, 0); - mColorTypedWord = a.getColor(R.styleable.SuggestionStripView_colorTypedWord, 0); - mColorAutoCorrect = a.getColor(R.styleable.SuggestionStripView_colorAutoCorrect, 0); - mColorSuggested = a.getColor(R.styleable.SuggestionStripView_colorSuggested, 0); - mSuggestionsCountInStrip = a.getInt( - R.styleable.SuggestionStripView_suggestionsCountInStrip, - DEFAULT_SUGGESTIONS_COUNT_IN_STRIP); - mCenterSuggestionWeight = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_centerSuggestionPercentile, - DEFAULT_CENTER_SUGGESTION_PERCENTILE); - mMaxMoreSuggestionsRow = a.getInt( - R.styleable.SuggestionStripView_maxMoreSuggestionsRow, - DEFAULT_MAX_MORE_SUGGESTIONS_ROW); - mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f); - a.recycle(); - - mMoreSuggestionsHint = getMoreSuggestionsHint(res, - res.getDimension(R.dimen.config_more_suggestions_hint_text_size), - mColorAutoCorrect); - mCenterPositionInStrip = mSuggestionsCountInStrip / 2; - // Assuming there are at least three suggestions. Also, note that the suggestions are - // laid out according to script direction, so this is left of the center for LTR scripts - // and right of the center for RTL scripts. - mTypedWordPositionWhenAutocorrect = mCenterPositionInStrip - 1; - mMoreSuggestionsBottomGap = res.getDimensionPixelOffset( - R.dimen.config_more_suggestions_bottom_gap); - mMoreSuggestionsRowHeight = res.getDimensionPixelSize( - R.dimen.config_more_suggestions_row_height); - } - - public int getMaxMoreSuggestionsRow() { - return mMaxMoreSuggestionsRow; - } - - private int getMoreSuggestionsHeight() { - return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap; - } - - public void setMoreSuggestionsHeight(final int remainingHeight) { - final int currentHeight = getMoreSuggestionsHeight(); - if (currentHeight <= remainingHeight) { - return; - } - - mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap) - / mMoreSuggestionsRowHeight; - } - - private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize, - final int color) { - final Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setTextAlign(Align.CENTER); - paint.setTextSize(textSize); - paint.setColor(color); - final Rect bounds = new Rect(); - paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds); - final int width = Math.round(bounds.width() + 0.5f); - final int height = Math.round(bounds.height() + 0.5f); - final Bitmap buffer = Bitmap.createBitmap(width, (height * 3 / 2), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(buffer); - canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint); - BitmapDrawable bitmapDrawable = new BitmapDrawable(res, buffer); - bitmapDrawable.setTargetDensity(canvas); - return bitmapDrawable; - } - - private CharSequence getStyledSuggestedWord(final SuggestedWords suggestedWords, - final int indexInSuggestedWords) { - if (indexInSuggestedWords >= suggestedWords.size()) { - return null; - } - final String word = suggestedWords.getLabel(indexInSuggestedWords); - // TODO: don't use the index to decide whether this is the auto-correction/typed word, as - // this is brittle - final boolean isAutoCorrection = suggestedWords.mWillAutoCorrect - && indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION; - final boolean isTypedWordValid = suggestedWords.mTypedWordValid - && indexInSuggestedWords == SuggestedWords.INDEX_OF_TYPED_WORD; - if (!isAutoCorrection && !isTypedWordValid) { - return word; - } - - final Spannable spannedWord = new SpannableString(word); - final int options = mSuggestionStripOptions; - if ((isAutoCorrection && (options & AUTO_CORRECT_BOLD) != 0) - || (isTypedWordValid && (options & VALID_TYPED_WORD_BOLD) != 0)) { - addStyleSpan(spannedWord, BOLD_SPAN); - } - if (isAutoCorrection && (options & AUTO_CORRECT_UNDERLINE) != 0) { - addStyleSpan(spannedWord, UNDERLINE_SPAN); - } - return spannedWord; - } - - /** - * Convert an index of {@link SuggestedWords} to position in the suggestion strip. - * @param indexInSuggestedWords the index of {@link SuggestedWords}. - * @param suggestedWords the suggested words list - * @return Non-negative integer of the position in the suggestion strip. - * Negative integer if the word of the index shouldn't be shown on the suggestion strip. - */ - private int getPositionInSuggestionStrip(final int indexInSuggestedWords, - final SuggestedWords suggestedWords) { - final SettingsValues settingsValues = Settings.getInstance().getCurrent(); - final boolean shouldOmitTypedWord = shouldOmitTypedWord(suggestedWords.mInputStyle, - settingsValues.mGestureFloatingPreviewTextEnabled, - settingsValues.mShouldShowLxxSuggestionUi); - return getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords.mWillAutoCorrect, - settingsValues.mShouldShowLxxSuggestionUi && shouldOmitTypedWord, - mCenterPositionInStrip, mTypedWordPositionWhenAutocorrect); - } - - @UsedForTesting - static boolean shouldOmitTypedWord(final int inputStyle, - final boolean gestureFloatingPreviewTextEnabled, - final boolean shouldShowUiToAcceptTypedWord) { - final boolean omitTypedWord = (inputStyle == SuggestedWords.INPUT_STYLE_TYPING) - || (inputStyle == SuggestedWords.INPUT_STYLE_TAIL_BATCH) - || (inputStyle == SuggestedWords.INPUT_STYLE_UPDATE_BATCH - && gestureFloatingPreviewTextEnabled); - return shouldShowUiToAcceptTypedWord && omitTypedWord; - } - - @UsedForTesting - static int getPositionInSuggestionStrip(final int indexInSuggestedWords, - final boolean willAutoCorrect, final boolean omitTypedWord, - final int centerPositionInStrip, final int typedWordPositionWhenAutoCorrect) { - if (omitTypedWord) { - if (indexInSuggestedWords == SuggestedWords.INDEX_OF_TYPED_WORD) { - // Ignore. - return -1; - } - if (indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION) { - // Center in the suggestion strip. - return centerPositionInStrip; - } - // If neither of those, the order in the suggestion strip is left of the center first - // then right of the center, to both edges of the suggestion strip. - // For example, center-1, center+1, center-2, center+2, and so on. - final int n = indexInSuggestedWords; - final int offsetFromCenter = (n % 2) == 0 ? -(n / 2) : (n / 2); - final int positionInSuggestionStrip = centerPositionInStrip + offsetFromCenter; - return positionInSuggestionStrip; - } - final int indexToDisplayMostImportantSuggestion; - final int indexToDisplaySecondMostImportantSuggestion; - if (willAutoCorrect) { - indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION; - indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD; - } else { - indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD; - indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION; - } - if (indexInSuggestedWords == indexToDisplayMostImportantSuggestion) { - // Center in the suggestion strip. - return centerPositionInStrip; - } - if (indexInSuggestedWords == indexToDisplaySecondMostImportantSuggestion) { - // Center-1. - return typedWordPositionWhenAutoCorrect; - } - // If neither of those, the order in the suggestion strip is right of the center first - // then left of the center, to both edges of the suggestion strip. - // For example, Center+1, center-2, center+2, center-3, and so on. - final int n = indexInSuggestedWords + 1; - final int offsetFromCenter = (n % 2) == 0 ? -(n / 2) : (n / 2); - final int positionInSuggestionStrip = centerPositionInStrip + offsetFromCenter; - return positionInSuggestionStrip; - } - - private int getSuggestionTextColor(final SuggestedWords suggestedWords, - final int indexInSuggestedWords) { - // Use identity for strings, not #equals : it's the typed word if it's the same object - final boolean isTypedWord = suggestedWords.getInfo(indexInSuggestedWords).isKindOf( - SuggestedWordInfo.KIND_TYPED); - - final int color; - if (indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION - && suggestedWords.mWillAutoCorrect) { - color = mColorAutoCorrect; - } else if (isTypedWord && suggestedWords.mTypedWordValid) { - color = mColorValidTypedWord; - } else if (isTypedWord) { - color = mColorTypedWord; - } else { - color = mColorSuggested; - } - if (suggestedWords.mIsObsoleteSuggestions && !isTypedWord) { - return applyAlpha(color, mAlphaObsoleted); - } - return color; - } - - private static int applyAlpha(final int color, final float alpha) { - final int newAlpha = (int)(Color.alpha(color) * alpha); - return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color)); - } - - private static void addDivider(final ViewGroup stripView, final View dividerView) { - stripView.addView(dividerView); - final LinearLayout.LayoutParams params = - (LinearLayout.LayoutParams)dividerView.getLayoutParams(); - params.gravity = Gravity.CENTER; - } - - /** - * Layout suggestions to the suggestions strip. And returns the start index of more - * suggestions. - * - * @param suggestedWords suggestions to be shown in the suggestions strip. - * @param stripView the suggestions strip view. - * @param placerView the view where the debug info will be placed. - * @return the start index of more suggestions. - */ - public int layoutAndReturnStartIndexOfMoreSuggestions( - final Context context, - final SuggestedWords suggestedWords, - final ViewGroup stripView, - final ViewGroup placerView) { - if (suggestedWords.isPunctuationSuggestions()) { - return layoutPunctuationsAndReturnStartIndexOfMoreSuggestions( - (PunctuationSuggestions)suggestedWords, stripView); - } - - final int wordCountToShow = suggestedWords.getWordCountToShow( - Settings.getInstance().getCurrent().mShouldShowLxxSuggestionUi); - final int startIndexOfMoreSuggestions = setupWordViewsAndReturnStartIndexOfMoreSuggestions( - suggestedWords, mSuggestionsCountInStrip); - final TextView centerWordView = mWordViews.get(mCenterPositionInStrip); - final int stripWidth = stripView.getWidth(); - final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth); - if (wordCountToShow == 1 || getTextScaleX(centerWordView.getText(), centerWidth, - centerWordView.getPaint()) < MIN_TEXT_XSCALE) { - // Layout only the most relevant suggested word at the center of the suggestion strip - // by consolidating all slots in the strip. - final int countInStrip = 1; - mMoreSuggestionsAvailable = (wordCountToShow > countInStrip); - layoutWord(context, mCenterPositionInStrip, stripWidth - mPadding); - stripView.addView(centerWordView); - setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT); - if (SuggestionStripView.DBG) { - layoutDebugInfo(mCenterPositionInStrip, placerView, stripWidth); - } - final Integer lastIndex = (Integer)centerWordView.getTag(); - return (lastIndex == null ? 0 : lastIndex) + 1; - } - - final int countInStrip = mSuggestionsCountInStrip; - mMoreSuggestionsAvailable = (wordCountToShow > countInStrip); - @SuppressWarnings("unused") - int x = 0; - for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) { - if (positionInStrip != 0) { - final View divider = mDividerViews.get(positionInStrip); - // Add divider if this isn't the left most suggestion in suggestions strip. - addDivider(stripView, divider); - x += divider.getMeasuredWidth(); - } - - final int width = getSuggestionWidth(positionInStrip, stripWidth); - final TextView wordView = layoutWord(context, positionInStrip, width); - stripView.addView(wordView); - setLayoutWeight(wordView, getSuggestionWeight(positionInStrip), - ViewGroup.LayoutParams.MATCH_PARENT); - x += wordView.getMeasuredWidth(); - - if (SuggestionStripView.DBG) { - layoutDebugInfo(positionInStrip, placerView, x); - } - } - return startIndexOfMoreSuggestions; - } - - /** - * Format appropriately the suggested word in {@link #mWordViews} specified by - * <code>positionInStrip</code>. When the suggested word doesn't exist, the corresponding - * {@link TextView} will be disabled and never respond to user interaction. The suggested word - * may be shrunk or ellipsized to fit in the specified width. - * - * The <code>positionInStrip</code> argument is the index in the suggestion strip. The indices - * increase towards the right for LTR scripts and the left for RTL scripts, starting with 0. - * The position of the most important suggestion is in {@link #mCenterPositionInStrip}. This - * usually doesn't match the index in <code>suggedtedWords</code> -- see - * {@link #getPositionInSuggestionStrip(int,SuggestedWords)}. - * - * @param positionInStrip the position in the suggestion strip. - * @param width the maximum width for layout in pixels. - * @return the {@link TextView} containing the suggested word appropriately formatted. - */ - private TextView layoutWord(final Context context, final int positionInStrip, final int width) { - final TextView wordView = mWordViews.get(positionInStrip); - final CharSequence word = wordView.getText(); - if (positionInStrip == mCenterPositionInStrip && mMoreSuggestionsAvailable) { - // TODO: This "more suggestions hint" should have a nicely designed icon. - wordView.setCompoundDrawablesWithIntrinsicBounds( - null, null, null, mMoreSuggestionsHint); - // HACK: Align with other TextViews that have no compound drawables. - wordView.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight()); - } else { - wordView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); - } - // {@link StyleSpan} in a content description may cause an issue of TTS/TalkBack. - // Use a simple {@link String} to avoid the issue. - wordView.setContentDescription( - TextUtils.isEmpty(word) - ? context.getResources().getString(R.string.spoken_empty_suggestion) - : word.toString()); - final CharSequence text = getEllipsizedTextWithSettingScaleX( - word, width, wordView.getPaint()); - final float scaleX = wordView.getTextScaleX(); - wordView.setText(text); // TextView.setText() resets text scale x to 1.0. - wordView.setTextScaleX(scaleX); - // A <code>wordView</code> should be disabled when <code>word</code> is empty in order to - // make it unclickable. - // With accessibility touch exploration on, <code>wordView</code> should be enabled even - // when it is empty to avoid announcing as "disabled". - wordView.setEnabled(!TextUtils.isEmpty(word) - || AccessibilityUtils.getInstance().isTouchExplorationEnabled()); - return wordView; - } - - private void layoutDebugInfo(final int positionInStrip, final ViewGroup placerView, - final int x) { - final TextView debugInfoView = mDebugInfoViews.get(positionInStrip); - final CharSequence debugInfo = debugInfoView.getText(); - if (debugInfo == null) { - return; - } - placerView.addView(debugInfoView); - debugInfoView.measure( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - final int infoWidth = debugInfoView.getMeasuredWidth(); - final int y = debugInfoView.getMeasuredHeight(); - ViewLayoutUtils.placeViewAt( - debugInfoView, x - infoWidth, y, infoWidth, debugInfoView.getMeasuredHeight()); - } - - private int getSuggestionWidth(final int positionInStrip, final int maxWidth) { - final int paddings = mPadding * mSuggestionsCountInStrip; - final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1); - final int availableWidth = maxWidth - paddings - dividers; - return (int)(availableWidth * getSuggestionWeight(positionInStrip)); - } - - private float getSuggestionWeight(final int positionInStrip) { - if (positionInStrip == mCenterPositionInStrip) { - return mCenterSuggestionWeight; - } - // TODO: Revisit this for cases of 5 or more suggestions - return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1); - } - - private int setupWordViewsAndReturnStartIndexOfMoreSuggestions( - final SuggestedWords suggestedWords, final int maxSuggestionInStrip) { - // Clear all suggestions first - for (int positionInStrip = 0; positionInStrip < maxSuggestionInStrip; ++positionInStrip) { - final TextView wordView = mWordViews.get(positionInStrip); - wordView.setText(null); - wordView.setTag(null); - // Make this inactive for touches in {@link #layoutWord(int,int)}. - if (SuggestionStripView.DBG) { - mDebugInfoViews.get(positionInStrip).setText(null); - } - } - int count = 0; - int indexInSuggestedWords; - for (indexInSuggestedWords = 0; indexInSuggestedWords < suggestedWords.size() - && count < maxSuggestionInStrip; indexInSuggestedWords++) { - final int positionInStrip = - getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords); - if (positionInStrip < 0) { - continue; - } - final TextView wordView = mWordViews.get(positionInStrip); - // {@link TextView#getTag()} is used to get the index in suggestedWords at - // {@link SuggestionStripView#onClick(View)}. - wordView.setTag(indexInSuggestedWords); - wordView.setText(getStyledSuggestedWord(suggestedWords, indexInSuggestedWords)); - wordView.setTextColor(getSuggestionTextColor(suggestedWords, indexInSuggestedWords)); - if (SuggestionStripView.DBG) { - mDebugInfoViews.get(positionInStrip).setText( - suggestedWords.getDebugString(indexInSuggestedWords)); - } - count++; - } - return indexInSuggestedWords; - } - - private int layoutPunctuationsAndReturnStartIndexOfMoreSuggestions( - final PunctuationSuggestions punctuationSuggestions, final ViewGroup stripView) { - final int countInStrip = Math.min(punctuationSuggestions.size(), PUNCTUATIONS_IN_STRIP); - for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) { - if (positionInStrip != 0) { - // Add divider if this isn't the left most suggestion in suggestions strip. - addDivider(stripView, mDividerViews.get(positionInStrip)); - } - - final TextView wordView = mWordViews.get(positionInStrip); - final String punctuation = punctuationSuggestions.getLabel(positionInStrip); - // {@link TextView#getTag()} is used to get the index in suggestedWords at - // {@link SuggestionStripView#onClick(View)}. - wordView.setTag(positionInStrip); - wordView.setText(punctuation); - wordView.setContentDescription(punctuation); - wordView.setTextScaleX(1.0f); - wordView.setCompoundDrawables(null, null, null, null); - wordView.setTextColor(mColorAutoCorrect); - stripView.addView(wordView); - setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight); - } - mMoreSuggestionsAvailable = (punctuationSuggestions.size() > countInStrip); - return countInStrip; - } - - public void layoutImportantNotice(final View importantNoticeStrip, - final String importantNoticeTitle) { - final TextView titleView = (TextView)importantNoticeStrip.findViewById( - R.id.important_notice_title); - final int width = titleView.getWidth() - titleView.getPaddingLeft() - - titleView.getPaddingRight(); - titleView.setTextColor(mColorAutoCorrect); - titleView.setText(importantNoticeTitle); // TextView.setText() resets text scale x to 1.0. - final float titleScaleX = getTextScaleX(importantNoticeTitle, width, titleView.getPaint()); - titleView.setTextScaleX(titleScaleX); - } - - static void setLayoutWeight(final View v, final float weight, final int height) { - final ViewGroup.LayoutParams lp = v.getLayoutParams(); - if (lp instanceof LinearLayout.LayoutParams) { - final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp; - llp.weight = weight; - llp.width = 0; - llp.height = height; - } - } - - private static float getTextScaleX(@Nullable final CharSequence text, final int maxWidth, - final TextPaint paint) { - paint.setTextScaleX(1.0f); - final int width = getTextWidth(text, paint); - if (width <= maxWidth || maxWidth <= 0) { - return 1.0f; - } - return maxWidth / (float) width; - } - - @Nullable - private static CharSequence getEllipsizedTextWithSettingScaleX( - @Nullable final CharSequence text, final int maxWidth, @Nonnull final TextPaint paint) { - if (text == null) { - return null; - } - final float scaleX = getTextScaleX(text, maxWidth, paint); - if (scaleX >= MIN_TEXT_XSCALE) { - paint.setTextScaleX(scaleX); - return text; - } - - // <code>text</code> must be ellipsized with minimum text scale x. - paint.setTextScaleX(MIN_TEXT_XSCALE); - final boolean hasBoldStyle = hasStyleSpan(text, BOLD_SPAN); - final boolean hasUnderlineStyle = hasStyleSpan(text, UNDERLINE_SPAN); - // TextUtils.ellipsize erases any span object existed after ellipsized point. - // We have to restore these spans afterward. - final CharSequence ellipsizedText = TextUtils.ellipsize( - text, paint, maxWidth, TextUtils.TruncateAt.MIDDLE); - if (!hasBoldStyle && !hasUnderlineStyle) { - return ellipsizedText; - } - final Spannable spannableText = (ellipsizedText instanceof Spannable) - ? (Spannable)ellipsizedText : new SpannableString(ellipsizedText); - if (hasBoldStyle) { - addStyleSpan(spannableText, BOLD_SPAN); - } - if (hasUnderlineStyle) { - addStyleSpan(spannableText, UNDERLINE_SPAN); - } - return spannableText; - } - - private static boolean hasStyleSpan(@Nullable final CharSequence text, - final CharacterStyle style) { - if (text instanceof Spanned) { - return ((Spanned)text).getSpanStart(style) >= 0; - } - return false; - } - - private static void addStyleSpan(@Nonnull final Spannable text, final CharacterStyle style) { - text.removeSpan(style); - text.setSpan(style, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - } - - private static int getTextWidth(@Nullable final CharSequence text, final TextPaint paint) { - if (TextUtils.isEmpty(text)) { - return 0; - } - final int length = text.length(); - final float[] widths = new float[length]; - final int count; - final Typeface savedTypeface = paint.getTypeface(); - try { - paint.setTypeface(getTextTypeface(text)); - count = paint.getTextWidths(text, 0, length, widths); - } finally { - paint.setTypeface(savedTypeface); - } - int width = 0; - for (int i = 0; i < count; i++) { - width += Math.round(widths[i] + 0.5f); - } - return width; - } - - private static Typeface getTextTypeface(@Nullable final CharSequence text) { - return hasStyleSpan(text, BOLD_SPAN) ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT; - } -} diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java deleted file mode 100644 index 5dba4928b..000000000 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ /dev/null @@ -1,491 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.suggestions; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.drawable.Drawable; -import androidx.core.view.ViewCompat; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.GestureDetector; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.widget.ImageButton; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.android.inputmethod.accessibility.AccessibilityUtils; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.MainKeyboardView; -import com.android.inputmethod.keyboard.MoreKeysPanel; -import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.settings.SettingsValues; -import com.android.inputmethod.latin.suggestions.MoreSuggestionsView.MoreSuggestionsListener; -import com.android.inputmethod.latin.utils.ImportantNoticeUtils; - -import java.util.ArrayList; - -public final class SuggestionStripView extends RelativeLayout implements OnClickListener, - OnLongClickListener { - public interface Listener { - public void showImportantNoticeContents(); - public void pickSuggestionManually(SuggestedWordInfo word); - public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat); - } - - static final boolean DBG = DebugFlags.DEBUG_ENABLED; - private static final float DEBUG_INFO_TEXT_SIZE_IN_DIP = 6.0f; - - private final ViewGroup mSuggestionsStrip; - private final ImageButton mVoiceKey; - private final View mImportantNoticeStrip; - MainKeyboardView mMainKeyboardView; - - private final View mMoreSuggestionsContainer; - private final MoreSuggestionsView mMoreSuggestionsView; - private final MoreSuggestions.Builder mMoreSuggestionsBuilder; - - private final ArrayList<TextView> mWordViews = new ArrayList<>(); - private final ArrayList<TextView> mDebugInfoViews = new ArrayList<>(); - private final ArrayList<View> mDividerViews = new ArrayList<>(); - - Listener mListener; - private SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance(); - private int mStartIndexOfMoreSuggestions; - - private final SuggestionStripLayoutHelper mLayoutHelper; - private final StripVisibilityGroup mStripVisibilityGroup; - - private static class StripVisibilityGroup { - private final View mSuggestionStripView; - private final View mSuggestionsStrip; - private final View mImportantNoticeStrip; - - public StripVisibilityGroup(final View suggestionStripView, - final ViewGroup suggestionsStrip, final View importantNoticeStrip) { - mSuggestionStripView = suggestionStripView; - mSuggestionsStrip = suggestionsStrip; - mImportantNoticeStrip = importantNoticeStrip; - showSuggestionsStrip(); - } - - public void setLayoutDirection(final boolean isRtlLanguage) { - final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL - : ViewCompat.LAYOUT_DIRECTION_LTR; - ViewCompat.setLayoutDirection(mSuggestionStripView, layoutDirection); - ViewCompat.setLayoutDirection(mSuggestionsStrip, layoutDirection); - ViewCompat.setLayoutDirection(mImportantNoticeStrip, layoutDirection); - } - - public void showSuggestionsStrip() { - mSuggestionsStrip.setVisibility(VISIBLE); - mImportantNoticeStrip.setVisibility(INVISIBLE); - } - - public void showImportantNoticeStrip() { - mSuggestionsStrip.setVisibility(INVISIBLE); - mImportantNoticeStrip.setVisibility(VISIBLE); - } - - public boolean isShowingImportantNoticeStrip() { - return mImportantNoticeStrip.getVisibility() == VISIBLE; - } - } - - /** - * Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user. - * @param context - * @param attrs - */ - public SuggestionStripView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.suggestionStripViewStyle); - } - - public SuggestionStripView(final Context context, final AttributeSet attrs, - final int defStyle) { - super(context, attrs, defStyle); - - final LayoutInflater inflater = LayoutInflater.from(context); - inflater.inflate(R.layout.suggestions_strip, this); - - mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip); - mVoiceKey = (ImageButton)findViewById(R.id.suggestions_strip_voice_key); - mImportantNoticeStrip = findViewById(R.id.important_notice_strip); - mStripVisibilityGroup = new StripVisibilityGroup(this, mSuggestionsStrip, - mImportantNoticeStrip); - - for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) { - final TextView word = new TextView(context, null, R.attr.suggestionWordStyle); - word.setContentDescription(getResources().getString(R.string.spoken_empty_suggestion)); - word.setOnClickListener(this); - word.setOnLongClickListener(this); - mWordViews.add(word); - final View divider = inflater.inflate(R.layout.suggestion_divider, null); - mDividerViews.add(divider); - final TextView info = new TextView(context, null, R.attr.suggestionWordStyle); - info.setTextColor(Color.WHITE); - info.setTextSize(TypedValue.COMPLEX_UNIT_DIP, DEBUG_INFO_TEXT_SIZE_IN_DIP); - mDebugInfoViews.add(info); - } - - mLayoutHelper = new SuggestionStripLayoutHelper( - context, attrs, defStyle, mWordViews, mDividerViews, mDebugInfoViews); - - mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null); - mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer - .findViewById(R.id.more_suggestions_view); - mMoreSuggestionsBuilder = new MoreSuggestions.Builder(context, mMoreSuggestionsView); - - final Resources res = context.getResources(); - mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset( - R.dimen.config_more_suggestions_modal_tolerance); - mMoreSuggestionsSlidingDetector = new GestureDetector( - context, mMoreSuggestionsSlidingListener); - - final TypedArray keyboardAttr = context.obtainStyledAttributes(attrs, - R.styleable.Keyboard, defStyle, R.style.SuggestionStripView); - final Drawable iconVoice = keyboardAttr.getDrawable(R.styleable.Keyboard_iconShortcutKey); - keyboardAttr.recycle(); - mVoiceKey.setImageDrawable(iconVoice); - mVoiceKey.setOnClickListener(this); - } - - /** - * A connection back to the input method. - * @param listener - */ - public void setListener(final Listener listener, final View inputView) { - mListener = listener; - mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view); - } - - public void updateVisibility(final boolean shouldBeVisible, final boolean isFullscreenMode) { - final int visibility = shouldBeVisible ? VISIBLE : (isFullscreenMode ? GONE : INVISIBLE); - setVisibility(visibility); - final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent(); - mVoiceKey.setVisibility(currentSettingsValues.mShowsVoiceInputKey ? VISIBLE : INVISIBLE); - } - - public void setSuggestions(final SuggestedWords suggestedWords, final boolean isRtlLanguage) { - clear(); - mStripVisibilityGroup.setLayoutDirection(isRtlLanguage); - mSuggestedWords = suggestedWords; - mStartIndexOfMoreSuggestions = mLayoutHelper.layoutAndReturnStartIndexOfMoreSuggestions( - getContext(), mSuggestedWords, mSuggestionsStrip, this); - mStripVisibilityGroup.showSuggestionsStrip(); - } - - public void setMoreSuggestionsHeight(final int remainingHeight) { - mLayoutHelper.setMoreSuggestionsHeight(remainingHeight); - } - - // This method checks if we should show the important notice (checks on permanent storage if - // it has been shown once already or not, and if in the setup wizard). If applicable, it shows - // the notice. In all cases, it returns true if it was shown, false otherwise. - public boolean maybeShowImportantNoticeTitle() { - final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent(); - if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext(), currentSettingsValues)) { - return false; - } - if (getWidth() <= 0) { - return false; - } - final String importantNoticeTitle = ImportantNoticeUtils.getSuggestContactsNoticeTitle( - getContext()); - if (TextUtils.isEmpty(importantNoticeTitle)) { - return false; - } - if (isShowingMoreSuggestionPanel()) { - dismissMoreSuggestionsPanel(); - } - mLayoutHelper.layoutImportantNotice(mImportantNoticeStrip, importantNoticeTitle); - mStripVisibilityGroup.showImportantNoticeStrip(); - mImportantNoticeStrip.setOnClickListener(this); - return true; - } - - public void clear() { - mSuggestionsStrip.removeAllViews(); - removeAllDebugInfoViews(); - mStripVisibilityGroup.showSuggestionsStrip(); - dismissMoreSuggestionsPanel(); - } - - private void removeAllDebugInfoViews() { - // The debug info views may be placed as children views of this {@link SuggestionStripView}. - for (final View debugInfoView : mDebugInfoViews) { - final ViewParent parent = debugInfoView.getParent(); - if (parent instanceof ViewGroup) { - ((ViewGroup)parent).removeView(debugInfoView); - } - } - } - - private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener() { - @Override - public void onSuggestionSelected(final SuggestedWordInfo wordInfo) { - mListener.pickSuggestionManually(wordInfo); - dismissMoreSuggestionsPanel(); - } - - @Override - public void onCancelInput() { - dismissMoreSuggestionsPanel(); - } - }; - - private final MoreKeysPanel.Controller mMoreSuggestionsController = - new MoreKeysPanel.Controller() { - @Override - public void onDismissMoreKeysPanel() { - mMainKeyboardView.onDismissMoreKeysPanel(); - } - - @Override - public void onShowMoreKeysPanel(final MoreKeysPanel panel) { - mMainKeyboardView.onShowMoreKeysPanel(panel); - } - - @Override - public void onCancelMoreKeysPanel() { - dismissMoreSuggestionsPanel(); - } - }; - - public boolean isShowingMoreSuggestionPanel() { - return mMoreSuggestionsView.isShowingInParent(); - } - - public void dismissMoreSuggestionsPanel() { - mMoreSuggestionsView.dismissMoreKeysPanel(); - } - - @Override - public boolean onLongClick(final View view) { - AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback( - Constants.NOT_A_CODE, this); - return showMoreSuggestions(); - } - - boolean showMoreSuggestions() { - final Keyboard parentKeyboard = mMainKeyboardView.getKeyboard(); - if (parentKeyboard == null) { - return false; - } - final SuggestionStripLayoutHelper layoutHelper = mLayoutHelper; - if (mSuggestedWords.size() <= mStartIndexOfMoreSuggestions) { - return false; - } - final int stripWidth = getWidth(); - final View container = mMoreSuggestionsContainer; - final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight(); - final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; - builder.layout(mSuggestedWords, mStartIndexOfMoreSuggestions, maxWidth, - (int)(maxWidth * layoutHelper.mMinMoreSuggestionsWidth), - layoutHelper.getMaxMoreSuggestionsRow(), parentKeyboard); - mMoreSuggestionsView.setKeyboard(builder.build()); - container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; - final int pointX = stripWidth / 2; - final int pointY = -layoutHelper.mMoreSuggestionsBottomGap; - moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY, - mMoreSuggestionsListener); - mOriginX = mLastX; - mOriginY = mLastY; - for (int i = 0; i < mStartIndexOfMoreSuggestions; i++) { - mWordViews.get(i).setPressed(false); - } - return true; - } - - // Working variables for {@link onInterceptTouchEvent(MotionEvent)} and - // {@link onTouchEvent(MotionEvent)}. - private int mLastX; - private int mLastY; - private int mOriginX; - private int mOriginY; - private final int mMoreSuggestionsModalTolerance; - private boolean mNeedsToTransformTouchEventToHoverEvent; - private boolean mIsDispatchingHoverEventToMoreSuggestions; - private final GestureDetector mMoreSuggestionsSlidingDetector; - private final GestureDetector.OnGestureListener mMoreSuggestionsSlidingListener = - new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onScroll(MotionEvent down, MotionEvent me, float deltaX, float deltaY) { - if (down == null) { - return false; - } - final float dy = me.getY() - down.getY(); - if (deltaY > 0 && dy < 0) { - return showMoreSuggestions(); - } - return false; - } - }; - - @Override - public boolean onInterceptTouchEvent(final MotionEvent me) { - if (mStripVisibilityGroup.isShowingImportantNoticeStrip()) { - return false; - } - // Detecting sliding up finger to show {@link MoreSuggestionsView}. - if (!mMoreSuggestionsView.isShowingInParent()) { - mLastX = (int)me.getX(); - mLastY = (int)me.getY(); - return mMoreSuggestionsSlidingDetector.onTouchEvent(me); - } - if (mMoreSuggestionsView.isInModalMode()) { - return false; - } - - final int action = me.getAction(); - final int index = me.getActionIndex(); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance - || mOriginY - y >= mMoreSuggestionsModalTolerance) { - // Decided to be in the sliding suggestion mode only when the touch point has been moved - // upward. Further {@link MotionEvent}s will be delivered to - // {@link #onTouchEvent(MotionEvent)}. - mNeedsToTransformTouchEventToHoverEvent = - AccessibilityUtils.getInstance().isTouchExplorationEnabled(); - mIsDispatchingHoverEventToMoreSuggestions = false; - return true; - } - - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) { - // Decided to be in the modal input mode. - mMoreSuggestionsView.setModalMode(); - } - return false; - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) { - // Don't populate accessibility event with suggested words and voice key. - return true; - } - - @Override - public boolean onTouchEvent(final MotionEvent me) { - if (!mMoreSuggestionsView.isShowingInParent()) { - // Ignore any touch event while more suggestions panel hasn't been shown. - // Detecting sliding up is done at {@link #onInterceptTouchEvent}. - return true; - } - // In the sliding input mode. {@link MotionEvent} should be forwarded to - // {@link MoreSuggestionsView}. - final int index = me.getActionIndex(); - final int x = mMoreSuggestionsView.translateX((int)me.getX(index)); - final int y = mMoreSuggestionsView.translateY((int)me.getY(index)); - me.setLocation(x, y); - if (!mNeedsToTransformTouchEventToHoverEvent) { - mMoreSuggestionsView.onTouchEvent(me); - return true; - } - // In sliding suggestion mode with accessibility mode on, a touch event should be - // transformed to a hover event. - final int width = mMoreSuggestionsView.getWidth(); - final int height = mMoreSuggestionsView.getHeight(); - final boolean onMoreSuggestions = (x >= 0 && x < width && y >= 0 && y < height); - if (!onMoreSuggestions && !mIsDispatchingHoverEventToMoreSuggestions) { - // Just drop this touch event because dispatching hover event isn't started yet and - // the touch event isn't on {@link MoreSuggestionsView}. - return true; - } - final int hoverAction; - if (onMoreSuggestions && !mIsDispatchingHoverEventToMoreSuggestions) { - // Transform this touch event to a hover enter event and start dispatching a hover - // event to {@link MoreSuggestionsView}. - mIsDispatchingHoverEventToMoreSuggestions = true; - hoverAction = MotionEvent.ACTION_HOVER_ENTER; - } else if (me.getActionMasked() == MotionEvent.ACTION_UP) { - // Transform this touch event to a hover exit event and stop dispatching a hover event - // after this. - mIsDispatchingHoverEventToMoreSuggestions = false; - mNeedsToTransformTouchEventToHoverEvent = false; - hoverAction = MotionEvent.ACTION_HOVER_EXIT; - } else { - // Transform this touch event to a hover move event. - hoverAction = MotionEvent.ACTION_HOVER_MOVE; - } - me.setAction(hoverAction); - mMoreSuggestionsView.onHoverEvent(me); - return true; - } - - @Override - public void onClick(final View view) { - AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback( - Constants.CODE_UNSPECIFIED, this); - if (view == mImportantNoticeStrip) { - mListener.showImportantNoticeContents(); - return; - } - if (view == mVoiceKey) { - mListener.onCodeInput(Constants.CODE_SHORTCUT, - Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE, - false /* isKeyRepeat */); - return; - } - - final Object tag = view.getTag(); - // {@link Integer} tag is set at - // {@link SuggestionStripLayoutHelper#setupWordViewsTextAndColor(SuggestedWords,int)} and - // {@link SuggestionStripLayoutHelper#layoutPunctuationSuggestions(SuggestedWords,ViewGroup} - if (tag instanceof Integer) { - final int index = (Integer) tag; - if (index >= mSuggestedWords.size()) { - return; - } - final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index); - mListener.pickSuggestionManually(wordInfo); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - dismissMoreSuggestionsPanel(); - } - - @Override - protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) { - // Called by the framework when the size is known. Show the important notice if applicable. - // This may be overriden by showing suggestions later, if applicable. - if (oldw <= 0 && w > 0) { - maybeShowImportantNoticeTitle(); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java deleted file mode 100644 index 68f417e84..000000000 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.suggestions; - -import com.android.inputmethod.latin.SuggestedWords; - -/** - * An object that gives basic control of a suggestion strip and some info on it. - */ -public interface SuggestionStripViewAccessor { - public void setNeutralSuggestionStrip(); - public void showSuggestionStrip(final SuggestedWords suggestedWords); -} diff --git a/java/src/com/android/inputmethod/latin/touchinputconsumer/GestureConsumer.java b/java/src/com/android/inputmethod/latin/touchinputconsumer/GestureConsumer.java deleted file mode 100644 index 8291b4f72..000000000 --- a/java/src/com/android/inputmethod/latin/touchinputconsumer/GestureConsumer.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.touchinputconsumer; - -import android.view.inputmethod.EditorInfo; - -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.latin.DictionaryFacilitator; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.common.InputPointers; -import com.android.inputmethod.latin.inputlogic.PrivateCommandPerformer; - -import java.util.Locale; - -/** - * Stub for GestureConsumer. - * <br> - * The methods of this class should only be called from a single thread, e.g., - * the UI Thread. - */ -@SuppressWarnings("unused") -public class GestureConsumer { - public static final GestureConsumer NULL_GESTURE_CONSUMER = - new GestureConsumer(); - - public static GestureConsumer newInstance( - final EditorInfo editorInfo, final PrivateCommandPerformer commandPerformer, - final Locale locale, final Keyboard keyboard) { - return GestureConsumer.NULL_GESTURE_CONSUMER; - } - - private GestureConsumer() { - } - - public boolean willConsume() { - return false; - } - - public void onInit(final Locale locale, final Keyboard keyboard) { - } - - public void onGestureStarted(final Locale locale, final Keyboard keyboard) { - } - - public void onGestureCanceled() { - } - - public void onGestureCompleted(final InputPointers inputPointers) { - } - - public void onImeSuggestionsProcessed(final SuggestedWords suggestedWords, - final int composingStart, final int composingLength, - final DictionaryFacilitator dictionaryFacilitator) { - } -} diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java deleted file mode 100644 index 58e6a25b8..000000000 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.userdictionary; - -import android.app.Activity; -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.os.Bundle; -import android.provider.UserDictionary; -import android.text.TextUtils; -import android.view.View; -import android.widget.EditText; - -import com.android.inputmethod.compat.UserDictionaryCompatUtils; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.LocaleUtils; - -import java.util.ArrayList; -import java.util.Locale; -import java.util.TreeSet; - -import javax.annotation.Nullable; - -// Caveat: This class is basically taken from -// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java -// in order to deal with some devices that have issues with the user dictionary handling - -/** - * A container class to factor common code to UserDictionaryAddWordFragment - * and UserDictionaryAddWordActivity. - */ -public class UserDictionaryAddWordContents { - public static final String EXTRA_MODE = "mode"; - public static final String EXTRA_WORD = "word"; - public static final String EXTRA_SHORTCUT = "shortcut"; - public static final String EXTRA_LOCALE = "locale"; - public static final String EXTRA_ORIGINAL_WORD = "originalWord"; - public static final String EXTRA_ORIGINAL_SHORTCUT = "originalShortcut"; - - public static final int MODE_EDIT = 0; - public static final int MODE_INSERT = 1; - - /* package */ static final int CODE_WORD_ADDED = 0; - /* package */ static final int CODE_CANCEL = 1; - /* package */ static final int CODE_ALREADY_PRESENT = 2; - - private static final int FREQUENCY_FOR_USER_DICTIONARY_ADDS = 250; - - private final int mMode; // Either MODE_EDIT or MODE_INSERT - private final EditText mWordEditText; - private final EditText mShortcutEditText; - private String mLocale; - private final String mOldWord; - private final String mOldShortcut; - private String mSavedWord; - private String mSavedShortcut; - - /* package */ UserDictionaryAddWordContents(final View view, final Bundle args) { - mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text); - mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut); - if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) { - mShortcutEditText.setVisibility(View.GONE); - view.findViewById(R.id.user_dictionary_add_shortcut_label).setVisibility(View.GONE); - } - final String word = args.getString(EXTRA_WORD); - if (null != word) { - mWordEditText.setText(word); - // Use getText in case the edit text modified the text we set. This happens when - // it's too long to be edited. - mWordEditText.setSelection(mWordEditText.getText().length()); - } - final String shortcut; - if (UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) { - shortcut = args.getString(EXTRA_SHORTCUT); - if (null != shortcut && null != mShortcutEditText) { - mShortcutEditText.setText(shortcut); - } - mOldShortcut = args.getString(EXTRA_SHORTCUT); - } else { - shortcut = null; - mOldShortcut = null; - } - mMode = args.getInt(EXTRA_MODE); // default return value for #getInt() is 0 = MODE_EDIT - mOldWord = args.getString(EXTRA_WORD); - updateLocale(args.getString(EXTRA_LOCALE)); - } - - /* package */ UserDictionaryAddWordContents(final View view, - final UserDictionaryAddWordContents oldInstanceToBeEdited) { - mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text); - mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut); - mMode = MODE_EDIT; - mOldWord = oldInstanceToBeEdited.mSavedWord; - mOldShortcut = oldInstanceToBeEdited.mSavedShortcut; - updateLocale(mLocale); - } - - // locale may be null, this means default locale - // It may also be the empty string, which means "all locales" - /* package */ void updateLocale(final String locale) { - mLocale = null == locale ? Locale.getDefault().toString() : locale; - } - - /* package */ void saveStateIntoBundle(final Bundle outState) { - outState.putString(EXTRA_WORD, mWordEditText.getText().toString()); - outState.putString(EXTRA_ORIGINAL_WORD, mOldWord); - if (null != mShortcutEditText) { - outState.putString(EXTRA_SHORTCUT, mShortcutEditText.getText().toString()); - } - if (null != mOldShortcut) { - outState.putString(EXTRA_ORIGINAL_SHORTCUT, mOldShortcut); - } - outState.putString(EXTRA_LOCALE, mLocale); - } - - /* package */ void delete(final Context context) { - if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) { - // Mode edit: remove the old entry. - final ContentResolver resolver = context.getContentResolver(); - UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver); - } - // If we are in add mode, nothing was added, so we don't need to do anything. - } - - /* package */ - int apply(final Context context, final Bundle outParameters) { - if (null != outParameters) saveStateIntoBundle(outParameters); - final ContentResolver resolver = context.getContentResolver(); - if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) { - // Mode edit: remove the old entry. - UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver); - } - final String newWord = mWordEditText.getText().toString(); - final String newShortcut; - if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) { - newShortcut = null; - } else if (null == mShortcutEditText) { - newShortcut = null; - } else { - final String tmpShortcut = mShortcutEditText.getText().toString(); - if (TextUtils.isEmpty(tmpShortcut)) { - newShortcut = null; - } else { - newShortcut = tmpShortcut; - } - } - if (TextUtils.isEmpty(newWord)) { - // If the word is somehow empty, don't insert it. - return CODE_CANCEL; - } - mSavedWord = newWord; - mSavedShortcut = newShortcut; - // If there is no shortcut, and the word already exists in the database, then we - // should not insert, because either A. the word exists with no shortcut, in which - // case the exact same thing we want to insert is already there, or B. the word - // exists with at least one shortcut, in which case it has priority on our word. - if (TextUtils.isEmpty(newShortcut) && hasWord(newWord, context)) { - return CODE_ALREADY_PRESENT; - } - - // Disallow duplicates. If the same word with no shortcut is defined, remove it; if - // the same word with the same shortcut is defined, remove it; but we don't mind if - // there is the same word with a different, non-empty shortcut. - UserDictionarySettings.deleteWord(newWord, null, resolver); - if (!TextUtils.isEmpty(newShortcut)) { - // If newShortcut is empty we just deleted this, no need to do it again - UserDictionarySettings.deleteWord(newWord, newShortcut, resolver); - } - - // In this class we use the empty string to represent 'all locales' and mLocale cannot - // be null. However the addWord method takes null to mean 'all locales'. - UserDictionaryCompatUtils.addWord(context, newWord.toString(), - FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut, TextUtils.isEmpty(mLocale) ? - null : LocaleUtils.constructLocaleFromString(mLocale)); - - return CODE_WORD_ADDED; - } - - private static final String[] HAS_WORD_PROJECTION = { UserDictionary.Words.WORD }; - private static final String HAS_WORD_SELECTION_ONE_LOCALE = UserDictionary.Words.WORD - + "=? AND " + UserDictionary.Words.LOCALE + "=?"; - private static final String HAS_WORD_SELECTION_ALL_LOCALES = UserDictionary.Words.WORD - + "=? AND " + UserDictionary.Words.LOCALE + " is null"; - private boolean hasWord(final String word, final Context context) { - final Cursor cursor; - // mLocale == "" indicates this is an entry for all languages. Here, mLocale can't - // be null at all (it's ensured by the updateLocale method). - if ("".equals(mLocale)) { - cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, - HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ALL_LOCALES, - new String[] { word }, null /* sort order */); - } else { - cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, - HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ONE_LOCALE, - new String[] { word, mLocale }, null /* sort order */); - } - try { - if (null == cursor) return false; - return cursor.getCount() > 0; - } finally { - if (null != cursor) cursor.close(); - } - } - - public static class LocaleRenderer { - private final String mLocaleString; - private final String mDescription; - - public LocaleRenderer(final Context context, @Nullable final String localeString) { - mLocaleString = localeString; - if (null == localeString) { - mDescription = context.getString(R.string.user_dict_settings_more_languages); - } else if ("".equals(localeString)) { - mDescription = context.getString(R.string.user_dict_settings_all_languages); - } else { - mDescription = LocaleUtils.constructLocaleFromString(localeString).getDisplayName(); - } - } - @Override - public String toString() { - return mDescription; - } - public String getLocaleString() { - return mLocaleString; - } - // "More languages..." is null ; "All languages" is the empty string. - public boolean isMoreLanguages() { - return null == mLocaleString; - } - } - - private static void addLocaleDisplayNameToList(final Context context, - final ArrayList<LocaleRenderer> list, final String locale) { - if (null != locale) { - list.add(new LocaleRenderer(context, locale)); - } - } - - // Helper method to get the list of locales to display for this word - public ArrayList<LocaleRenderer> getLocalesList(final Activity activity) { - final TreeSet<String> locales = UserDictionaryList.getUserDictionaryLocalesSet(activity); - // Remove our locale if it's in, because we're always gonna put it at the top - locales.remove(mLocale); // mLocale may not be null - final String systemLocale = Locale.getDefault().toString(); - // The system locale should be inside. We want it at the 2nd spot. - locales.remove(systemLocale); // system locale may not be null - locales.remove(""); // Remove the empty string if it's there - final ArrayList<LocaleRenderer> localesList = new ArrayList<>(); - // Add the passed locale, then the system locale at the top of the list. Add an - // "all languages" entry at the bottom of the list. - addLocaleDisplayNameToList(activity, localesList, mLocale); - if (!systemLocale.equals(mLocale)) { - addLocaleDisplayNameToList(activity, localesList, systemLocale); - } - for (final String l : locales) { - // TODO: sort in unicode order - addLocaleDisplayNameToList(activity, localesList, l); - } - if (!"".equals(mLocale)) { - // If mLocale is "", then we already inserted the "all languages" item, so don't do it - addLocaleDisplayNameToList(activity, localesList, ""); // meaning: all languages - } - localesList.add(new LocaleRenderer(activity, null)); // meaning: select another locale - return localesList; - } - - public String getCurrentUserDictionaryLocale() { - return mLocale; - } -} - diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java deleted file mode 100644 index a8781d786..000000000 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.userdictionary; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.LocaleRenderer; -import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker.LocationChangedListener; - -import android.app.Fragment; -import android.os.Bundle; -import android.preference.PreferenceActivity; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Spinner; - -import java.util.ArrayList; -import java.util.Locale; - -// Caveat: This class is basically taken from -// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java -// in order to deal with some devices that have issues with the user dictionary handling - -/** - * Fragment to add a word/shortcut to the user dictionary. - * - * As opposed to the UserDictionaryActivity, this is only invoked within Settings - * from the UserDictionarySettings. - */ -public class UserDictionaryAddWordFragment extends Fragment - implements AdapterView.OnItemSelectedListener, LocationChangedListener { - - private static final int OPTIONS_MENU_ADD = Menu.FIRST; - private static final int OPTIONS_MENU_DELETE = Menu.FIRST + 1; - - private UserDictionaryAddWordContents mContents; - private View mRootView; - private boolean mIsDeleting = false; - - @Override - public void onActivityCreated(final Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - setHasOptionsMenu(true); - getActivity().getActionBar().setTitle(R.string.edit_personal_dictionary); - // Keep the instance so that we remember mContents when configuration changes (eg rotation) - setRetainInstance(true); - } - - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedState) { - mRootView = inflater.inflate(R.layout.user_dictionary_add_word_fullscreen, null); - mIsDeleting = false; - // If we have a non-null mContents object, it's the old value before a configuration - // change (eg rotation) so we need to use its values. Otherwise, read from the arguments. - if (null == mContents) { - mContents = new UserDictionaryAddWordContents(mRootView, getArguments()); - } else { - // We create a new mContents object to account for the new situation : a word has - // been added to the user dictionary when we started rotating, and we are now editing - // it. That means in particular if the word undergoes any change, the old version should - // be updated, so the mContents object needs to switch to EDIT mode if it was in - // INSERT mode. - mContents = new UserDictionaryAddWordContents(mRootView, - mContents /* oldInstanceToBeEdited */); - } - getActivity().getActionBar().setSubtitle(UserDictionarySettingsUtils.getLocaleDisplayName( - getActivity(), mContents.getCurrentUserDictionaryLocale())); - return mRootView; - } - - @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0, - R.string.user_dict_settings_add_menu_title).setIcon(R.drawable.ic_menu_add); - actionItemAdd.setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - final MenuItem actionItemDelete = menu.add(0, OPTIONS_MENU_DELETE, 0, - R.string.user_dict_settings_delete).setIcon(android.R.drawable.ic_menu_delete); - actionItemDelete.setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - - /** - * Callback for the framework when a menu option is pressed. - * - * @param item the item that was pressed - * @return false to allow normal menu processing to proceed, true to consume it here - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == OPTIONS_MENU_ADD) { - // added the entry in "onPause" - getActivity().onBackPressed(); - return true; - } - if (item.getItemId() == OPTIONS_MENU_DELETE) { - mContents.delete(getActivity()); - mIsDeleting = true; - getActivity().onBackPressed(); - return true; - } - return false; - } - - @Override - public void onResume() { - super.onResume(); - // We are being shown: display the word - updateSpinner(); - } - - private void updateSpinner() { - final ArrayList<LocaleRenderer> localesList = mContents.getLocalesList(getActivity()); - - final Spinner localeSpinner = - (Spinner)mRootView.findViewById(R.id.user_dictionary_add_locale); - final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<>( - getActivity(), android.R.layout.simple_spinner_item, localesList); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - localeSpinner.setAdapter(adapter); - localeSpinner.setOnItemSelectedListener(this); - } - - @Override - public void onPause() { - super.onPause(); - // We are being hidden: commit changes to the user dictionary, unless we were deleting it - if (!mIsDeleting) { - mContents.apply(getActivity(), null); - } - } - - @Override - public void onItemSelected(final AdapterView<?> parent, final View view, final int pos, - final long id) { - final LocaleRenderer locale = (LocaleRenderer)parent.getItemAtPosition(pos); - if (locale.isMoreLanguages()) { - PreferenceActivity preferenceActivity = (PreferenceActivity)getActivity(); - preferenceActivity.startPreferenceFragment(new UserDictionaryLocalePicker(), true); - } else { - mContents.updateLocale(locale.getLocaleString()); - } - } - - @Override - public void onNothingSelected(final AdapterView<?> parent) { - // I'm not sure we can come here, but if we do, that's the right thing to do. - final Bundle args = getArguments(); - mContents.updateLocale(args.getString(UserDictionaryAddWordContents.EXTRA_LOCALE)); - } - - // Called by the locale picker - @Override - public void onLocaleSelected(final Locale locale) { - mContents.updateLocale(locale.toString()); - getActivity().onBackPressed(); - } -} - diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java deleted file mode 100644 index f7940e3de..000000000 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.userdictionary; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.preference.PreferenceGroup; -import android.provider.UserDictionary; -import android.text.TextUtils; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.LocaleUtils; - -import java.util.List; -import java.util.Locale; -import java.util.TreeSet; - -import javax.annotation.Nullable; - -// Caveat: This class is basically taken from -// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryList.java -// in order to deal with some devices that have issues with the user dictionary handling - -public class UserDictionaryList extends PreferenceFragment { - - public static final String USER_DICTIONARY_SETTINGS_INTENT_ACTION = - "android.settings.USER_DICTIONARY_SETTINGS"; - - @Override - public void onCreate(final Bundle icicle) { - super.onCreate(icicle); - setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity())); - } - - public static TreeSet<String> getUserDictionaryLocalesSet(final Activity activity) { - final Cursor cursor = activity.getContentResolver().query(UserDictionary.Words.CONTENT_URI, - new String[] { UserDictionary.Words.LOCALE }, - null, null, null); - final TreeSet<String> localeSet = new TreeSet<>(); - if (null == cursor) { - // The user dictionary service is not present or disabled. Return null. - return null; - } - try { - if (cursor.moveToFirst()) { - final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE); - do { - final String locale = cursor.getString(columnIndex); - localeSet.add(null != locale ? locale : ""); - } while (cursor.moveToNext()); - } - } finally { - cursor.close(); - } - if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) { - // For ICS, we need to show "For all languages" in case that the keyboard locale - // is different from the system locale - localeSet.add(""); - } - - final InputMethodManager imm = - (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE); - final List<InputMethodInfo> imis = imm.getEnabledInputMethodList(); - for (final InputMethodInfo imi : imis) { - final List<InputMethodSubtype> subtypes = - imm.getEnabledInputMethodSubtypeList( - imi, true /* allowsImplicitlySelectedSubtypes */); - for (InputMethodSubtype subtype : subtypes) { - final String locale = subtype.getLocale(); - if (!TextUtils.isEmpty(locale)) { - localeSet.add(locale); - } - } - } - - // We come here after we have collected locales from existing user dictionary entries and - // enabled subtypes. If we already have the locale-without-country version of the system - // locale, we don't add the system locale to avoid confusion even though it's technically - // correct to add it. - if (!localeSet.contains(Locale.getDefault().getLanguage().toString())) { - localeSet.add(Locale.getDefault().toString()); - } - - return localeSet; - } - - /** - * Creates the entries that allow the user to go into the user dictionary for each locale. - * @param userDictGroup The group to put the settings in. - */ - protected void createUserDictSettings(final PreferenceGroup userDictGroup) { - final Activity activity = getActivity(); - userDictGroup.removeAll(); - final TreeSet<String> localeSet = - UserDictionaryList.getUserDictionaryLocalesSet(activity); - - if (localeSet.size() > 1) { - // Have an "All languages" entry in the languages list if there are two or more active - // languages - localeSet.add(""); - } - - if (localeSet.isEmpty()) { - userDictGroup.addPreference(createUserDictionaryPreference(null)); - } else { - for (String locale : localeSet) { - userDictGroup.addPreference(createUserDictionaryPreference(locale)); - } - } - } - - /** - * Create a single User Dictionary Preference object, with its parameters set. - * @param localeString The locale for which this user dictionary is for. - * @return The corresponding preference. - */ - protected Preference createUserDictionaryPreference(@Nullable final String localeString) { - final Preference newPref = new Preference(getActivity()); - final Intent intent = new Intent(USER_DICTIONARY_SETTINGS_INTENT_ACTION); - if (null == localeString) { - newPref.setTitle(Locale.getDefault().getDisplayName()); - } else { - if (localeString.isEmpty()) { - newPref.setTitle(getString(R.string.user_dict_settings_all_languages)); - } else { - newPref.setTitle( - LocaleUtils.constructLocaleFromString(localeString).getDisplayName()); - } - intent.putExtra("locale", localeString); - newPref.getExtras().putString("locale", localeString); - } - newPref.setIntent(intent); - newPref.setFragment(UserDictionarySettings.class.getName()); - return newPref; - } - - @Override - public void onResume() { - super.onResume(); - createUserDictSettings(getPreferenceScreen()); - } -} - diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryLocalePicker.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryLocalePicker.java deleted file mode 100644 index 58d3fb91c..000000000 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryLocalePicker.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.userdictionary; - -import android.app.Fragment; - -import java.util.Locale; - -// Caveat: This class is basically taken from -// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryLocalePicker.java -// in order to deal with some devices that have issues with the user dictionary handling - -public class UserDictionaryLocalePicker extends Fragment { - public UserDictionaryLocalePicker() { - super(); - // TODO: implement - } - - public interface LocationChangedListener { - public void onLocaleSelected(Locale locale); - } -} diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java deleted file mode 100644 index d9339d965..000000000 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.userdictionary; - -import com.android.inputmethod.latin.R; - -import android.app.ListFragment; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.os.Build; -import android.os.Bundle; -import android.provider.UserDictionary; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AlphabetIndexer; -import android.widget.ListAdapter; -import android.widget.ListView; -import android.widget.SectionIndexer; -import android.widget.SimpleCursorAdapter; -import android.widget.TextView; - -import java.util.Locale; - -// Caveat: This class is basically taken from -// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionarySettings.java -// in order to deal with some devices that have issues with the user dictionary handling - -public class UserDictionarySettings extends ListFragment { - - public static final boolean IS_SHORTCUT_API_SUPPORTED = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; - - private static final String[] QUERY_PROJECTION_SHORTCUT_UNSUPPORTED = - { UserDictionary.Words._ID, UserDictionary.Words.WORD}; - private static final String[] QUERY_PROJECTION_SHORTCUT_SUPPORTED = - { UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT}; - private static final String[] QUERY_PROJECTION = - IS_SHORTCUT_API_SUPPORTED ? - QUERY_PROJECTION_SHORTCUT_SUPPORTED : QUERY_PROJECTION_SHORTCUT_UNSUPPORTED; - - // The index of the shortcut in the above array. - private static final int INDEX_SHORTCUT = 2; - - private static final String[] ADAPTER_FROM_SHORTCUT_UNSUPPORTED = { - UserDictionary.Words.WORD, - }; - - private static final String[] ADAPTER_FROM_SHORTCUT_SUPPORTED = { - UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT - }; - - private static final String[] ADAPTER_FROM = IS_SHORTCUT_API_SUPPORTED ? - ADAPTER_FROM_SHORTCUT_SUPPORTED : ADAPTER_FROM_SHORTCUT_UNSUPPORTED; - - private static final int[] ADAPTER_TO_SHORTCUT_UNSUPPORTED = { - android.R.id.text1, - }; - - private static final int[] ADAPTER_TO_SHORTCUT_SUPPORTED = { - android.R.id.text1, android.R.id.text2 - }; - - private static final int[] ADAPTER_TO = IS_SHORTCUT_API_SUPPORTED ? - ADAPTER_TO_SHORTCUT_SUPPORTED : ADAPTER_TO_SHORTCUT_UNSUPPORTED; - - // Either the locale is empty (means the word is applicable to all locales) - // or the word equals our current locale - private static final String QUERY_SELECTION = - UserDictionary.Words.LOCALE + "=?"; - private static final String QUERY_SELECTION_ALL_LOCALES = - UserDictionary.Words.LOCALE + " is null"; - - private static final String DELETE_SELECTION_WITH_SHORTCUT = UserDictionary.Words.WORD - + "=? AND " + UserDictionary.Words.SHORTCUT + "=?"; - private static final String DELETE_SELECTION_WITHOUT_SHORTCUT = UserDictionary.Words.WORD - + "=? AND " + UserDictionary.Words.SHORTCUT + " is null OR " - + UserDictionary.Words.SHORTCUT + "=''"; - private static final String DELETE_SELECTION_SHORTCUT_UNSUPPORTED = - UserDictionary.Words.WORD + "=?"; - - private static final int OPTIONS_MENU_ADD = Menu.FIRST; - - private Cursor mCursor; - - protected String mLocale; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getActivity().getActionBar().setTitle(R.string.edit_personal_dictionary); - } - - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate( - R.layout.user_dictionary_preference_list_fragment, container, false); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - final Intent intent = getActivity().getIntent(); - final String localeFromIntent = - null == intent ? null : intent.getStringExtra("locale"); - - final Bundle arguments = getArguments(); - final String localeFromArguments = - null == arguments ? null : arguments.getString("locale"); - - final String locale; - if (null != localeFromArguments) { - locale = localeFromArguments; - } else if (null != localeFromIntent) { - locale = localeFromIntent; - } else { - locale = null; - } - - mLocale = locale; - // WARNING: The following cursor is never closed! TODO: don't put that in a member, and - // make sure all cursors are correctly closed. Also, this comes from a call to - // Activity#managedQuery, which has been deprecated for a long time (and which FORBIDS - // closing the cursor, so take care when resolving this TODO). We should either use a - // regular query and close the cursor, or switch to a LoaderManager and a CursorLoader. - mCursor = createCursor(locale); - TextView emptyView = (TextView) getView().findViewById(android.R.id.empty); - emptyView.setText(R.string.user_dict_settings_empty_text); - - final ListView listView = getListView(); - listView.setAdapter(createAdapter()); - listView.setFastScrollEnabled(true); - listView.setEmptyView(emptyView); - - setHasOptionsMenu(true); - // Show the language as a subtitle of the action bar - getActivity().getActionBar().setSubtitle( - UserDictionarySettingsUtils.getLocaleDisplayName(getActivity(), mLocale)); - } - - @Override - public void onResume() { - super.onResume(); - ListAdapter adapter = getListView().getAdapter(); - if (adapter != null && adapter instanceof MyAdapter) { - // The list view is forced refreshed here. This allows the changes done - // in UserDictionaryAddWordFragment (update/delete/insert) to be seen when - // user goes back to this view. - MyAdapter listAdapter = (MyAdapter) adapter; - listAdapter.notifyDataSetChanged(); - } - } - - @SuppressWarnings("deprecation") - private Cursor createCursor(final String locale) { - // Locale can be any of: - // - The string representation of a locale, as returned by Locale#toString() - // - The empty string. This means we want a cursor returning words valid for all locales. - // - null. This means we want a cursor for the current locale, whatever this is. - // Note that this contrasts with the data inside the database, where NULL means "all - // locales" and there should never be an empty string. The confusion is called by the - // historical use of null for "all locales". - // TODO: it should be easy to make this more readable by making the special values - // human-readable, like "all_locales" and "current_locales" strings, provided they - // can be guaranteed not to match locales that may exist. - if ("".equals(locale)) { - // Case-insensitive sort - return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, - QUERY_SELECTION_ALL_LOCALES, null, - "UPPER(" + UserDictionary.Words.WORD + ")"); - } - final String queryLocale = null != locale ? locale : Locale.getDefault().toString(); - return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, - QUERY_SELECTION, new String[] { queryLocale }, - "UPPER(" + UserDictionary.Words.WORD + ")"); - } - - private ListAdapter createAdapter() { - return new MyAdapter(getActivity(), R.layout.user_dictionary_item, mCursor, - ADAPTER_FROM, ADAPTER_TO); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - final String word = getWord(position); - final String shortcut = getShortcut(position); - if (word != null) { - showAddOrEditDialog(word, shortcut); - } - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) { - final Locale systemLocale = getResources().getConfiguration().locale; - if (!TextUtils.isEmpty(mLocale) && !mLocale.equals(systemLocale.toString())) { - // Hide the add button for ICS because it doesn't support specifying a locale - // for an entry. This new "locale"-aware API has been added in conjunction - // with the shortcut API. - return; - } - } - MenuItem actionItem = - menu.add(0, OPTIONS_MENU_ADD, 0, R.string.user_dict_settings_add_menu_title) - .setIcon(R.drawable.ic_menu_add); - actionItem.setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == OPTIONS_MENU_ADD) { - showAddOrEditDialog(null, null); - return true; - } - return false; - } - - /** - * Add or edit a word. If editingWord is null, it's an add; otherwise, it's an edit. - * @param editingWord the word to edit, or null if it's an add. - * @param editingShortcut the shortcut for this entry, or null if none. - */ - private void showAddOrEditDialog(final String editingWord, final String editingShortcut) { - final Bundle args = new Bundle(); - args.putInt(UserDictionaryAddWordContents.EXTRA_MODE, null == editingWord - ? UserDictionaryAddWordContents.MODE_INSERT - : UserDictionaryAddWordContents.MODE_EDIT); - args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord); - args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut); - args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale); - android.preference.PreferenceActivity pa = - (android.preference.PreferenceActivity)getActivity(); - pa.startPreferencePanel(UserDictionaryAddWordFragment.class.getName(), - args, R.string.user_dict_settings_add_dialog_title, null, null, 0); - } - - private String getWord(final int position) { - if (null == mCursor) return null; - mCursor.moveToPosition(position); - // Handle a possible race-condition - if (mCursor.isAfterLast()) return null; - - return mCursor.getString( - mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD)); - } - - private String getShortcut(final int position) { - if (!IS_SHORTCUT_API_SUPPORTED) return null; - if (null == mCursor) return null; - mCursor.moveToPosition(position); - // Handle a possible race-condition - if (mCursor.isAfterLast()) return null; - - return mCursor.getString( - mCursor.getColumnIndexOrThrow(UserDictionary.Words.SHORTCUT)); - } - - public static void deleteWord(final String word, final String shortcut, - final ContentResolver resolver) { - if (!IS_SHORTCUT_API_SUPPORTED) { - resolver.delete(UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_SHORTCUT_UNSUPPORTED, - new String[] { word }); - } else if (TextUtils.isEmpty(shortcut)) { - resolver.delete( - UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT, - new String[] { word }); - } else { - resolver.delete( - UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT, - new String[] { word, shortcut }); - } - } - - private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer { - private AlphabetIndexer mIndexer; - - private ViewBinder mViewBinder = new ViewBinder() { - - @Override - public boolean setViewValue(final View v, final Cursor c, final int columnIndex) { - if (!IS_SHORTCUT_API_SUPPORTED) { - // just let SimpleCursorAdapter set the view values - return false; - } - if (columnIndex == INDEX_SHORTCUT) { - final String shortcut = c.getString(INDEX_SHORTCUT); - if (TextUtils.isEmpty(shortcut)) { - v.setVisibility(View.GONE); - } else { - ((TextView)v).setText(shortcut); - v.setVisibility(View.VISIBLE); - } - v.invalidate(); - return true; - } - - return false; - } - }; - - public MyAdapter(final Context context, final int layout, final Cursor c, - final String[] from, final int[] to) { - super(context, layout, c, from, to, 0 /* flags */); - - if (null != c) { - final String alphabet = context.getString(R.string.user_dict_fast_scroll_alphabet); - final int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD); - mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet); - } - setViewBinder(mViewBinder); - } - - @Override - public int getPositionForSection(final int section) { - return null == mIndexer ? 0 : mIndexer.getPositionForSection(section); - } - - @Override - public int getSectionForPosition(final int position) { - return null == mIndexer ? 0 : mIndexer.getSectionForPosition(position); - } - - @Override - public Object[] getSections() { - return null == mIndexer ? null : mIndexer.getSections(); - } - } -} - diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java deleted file mode 100644 index c0a946e42..000000000 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.userdictionary; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.LocaleUtils; - -import android.content.Context; -import android.text.TextUtils; - -import java.util.Locale; - -/** - * Utilities of the user dictionary settings - * TODO: We really want to move these utilities to a static library. - */ -public class UserDictionarySettingsUtils { - public static String getLocaleDisplayName(Context context, String localeStr) { - if (TextUtils.isEmpty(localeStr)) { - // CAVEAT: localeStr should not be null because a null locale stands for the system - // locale in UserDictionary.Words.addWord. - return context.getResources().getString(R.string.user_dict_settings_all_languages); - } - final Locale locale = LocaleUtils.constructLocaleFromString(localeStr); - final Locale systemLocale = context.getResources().getConfiguration().locale; - return locale.getDisplayName(systemLocale); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java deleted file mode 100644 index 2aac7c57a..000000000 --- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.utils; - -import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE; -import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.ASCII_CAPABLE; -import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.EMOJI_CAPABLE; -import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE; -import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; -import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; - -import android.os.Build; -import android.text.TextUtils; -import android.util.Log; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.StringUtils; - -import java.util.ArrayList; -import java.util.Arrays; - -public final class AdditionalSubtypeUtils { - private static final String TAG = AdditionalSubtypeUtils.class.getSimpleName(); - - private static final InputMethodSubtype[] EMPTY_SUBTYPE_ARRAY = new InputMethodSubtype[0]; - - private AdditionalSubtypeUtils() { - // This utility class is not publicly instantiable. - } - - @UsedForTesting - public static boolean isAdditionalSubtype(final InputMethodSubtype subtype) { - return subtype.containsExtraValueKey(IS_ADDITIONAL_SUBTYPE); - } - - private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":"; - private static final int INDEX_OF_LOCALE = 0; - private static final int INDEX_OF_KEYBOARD_LAYOUT = 1; - private static final int INDEX_OF_EXTRA_VALUE = 2; - private static final int LENGTH_WITHOUT_EXTRA_VALUE = (INDEX_OF_KEYBOARD_LAYOUT + 1); - private static final int LENGTH_WITH_EXTRA_VALUE = (INDEX_OF_EXTRA_VALUE + 1); - private static final String PREF_SUBTYPE_SEPARATOR = ";"; - - private static InputMethodSubtype createAdditionalSubtypeInternal( - final String localeString, final String keyboardLayoutSetName, - final boolean isAsciiCapable, final boolean isEmojiCapable) { - final int nameId = SubtypeLocaleUtils.getSubtypeNameId(localeString, keyboardLayoutSetName); - final String platformVersionDependentExtraValues = getPlatformVersionDependentExtraValue( - localeString, keyboardLayoutSetName, isAsciiCapable, isEmojiCapable); - final int platformVersionIndependentSubtypeId = - getPlatformVersionIndependentSubtypeId(localeString, keyboardLayoutSetName); - // NOTE: In KitKat and later, InputMethodSubtypeBuilder#setIsAsciiCapable is also available. - // TODO: Use InputMethodSubtypeBuilder#setIsAsciiCapable when appropriate. - return InputMethodSubtypeCompatUtils.newInputMethodSubtype(nameId, - R.drawable.ic_ime_switcher_dark, localeString, KEYBOARD_MODE, - platformVersionDependentExtraValues, - false /* isAuxiliary */, false /* overrideImplicitlyEnabledSubtype */, - platformVersionIndependentSubtypeId); - } - - public static InputMethodSubtype createDummyAdditionalSubtype( - final String localeString, final String keyboardLayoutSetName) { - return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName, - false /* isAsciiCapable */, false /* isEmojiCapable */); - } - - public static InputMethodSubtype createAsciiEmojiCapableAdditionalSubtype( - final String localeString, final String keyboardLayoutSetName) { - return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName, - true /* isAsciiCapable */, true /* isEmojiCapable */); - } - - public static String getPrefSubtype(final InputMethodSubtype subtype) { - final String localeString = subtype.getLocale(); - final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); - final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName; - final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists( - layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists( - IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue())); - final String basePrefSubtype = localeString + LOCALE_AND_LAYOUT_SEPARATOR - + keyboardLayoutSetName; - return extraValue.isEmpty() ? basePrefSubtype - : basePrefSubtype + LOCALE_AND_LAYOUT_SEPARATOR + extraValue; - } - - public static InputMethodSubtype[] createAdditionalSubtypesArray(final String prefSubtypes) { - if (TextUtils.isEmpty(prefSubtypes)) { - return EMPTY_SUBTYPE_ARRAY; - } - final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR); - final ArrayList<InputMethodSubtype> subtypesList = new ArrayList<>(prefSubtypeArray.length); - for (final String prefSubtype : prefSubtypeArray) { - final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR); - if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE - && elems.length != LENGTH_WITH_EXTRA_VALUE) { - Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype + " in " - + prefSubtypes); - continue; - } - final String localeString = elems[INDEX_OF_LOCALE]; - final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT]; - // Here we assume that all the additional subtypes have AsciiCapable and EmojiCapable. - // This is actually what the setting dialog for additional subtype is doing. - final InputMethodSubtype subtype = createAsciiEmojiCapableAdditionalSubtype( - localeString, keyboardLayoutSetName); - if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) { - // Skip unknown keyboard layout subtype. This may happen when predefined keyboard - // layout has been removed. - continue; - } - subtypesList.add(subtype); - } - return subtypesList.toArray(new InputMethodSubtype[subtypesList.size()]); - } - - public static String createPrefSubtypes(final InputMethodSubtype[] subtypes) { - if (subtypes == null || subtypes.length == 0) { - return ""; - } - final StringBuilder sb = new StringBuilder(); - for (final InputMethodSubtype subtype : subtypes) { - if (sb.length() > 0) { - sb.append(PREF_SUBTYPE_SEPARATOR); - } - sb.append(getPrefSubtype(subtype)); - } - return sb.toString(); - } - - public static String createPrefSubtypes(final String[] prefSubtypes) { - if (prefSubtypes == null || prefSubtypes.length == 0) { - return ""; - } - final StringBuilder sb = new StringBuilder(); - for (final String prefSubtype : prefSubtypes) { - if (sb.length() > 0) { - sb.append(PREF_SUBTYPE_SEPARATOR); - } - sb.append(prefSubtype); - } - return sb.toString(); - } - - /** - * Returns the extra value that is optimized for the running OS. - * <p> - * Historically the extra value has been used as the last resort to annotate various kinds of - * attributes. Some of these attributes are valid only on some platform versions. Thus we cannot - * assume that the extra values stored in a persistent storage are always valid. We need to - * regenerate the extra value on the fly instead. - * </p> - * @param localeString the locale string (e.g., "en_US"). - * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). - * @param isAsciiCapable true when ASCII characters are supported with this layout. - * @param isEmojiCapable true when Unicode Emoji characters are supported with this layout. - * @return extra value that is optimized for the running OS. - * @see #getPlatformVersionIndependentSubtypeId(String, String) - */ - private static String getPlatformVersionDependentExtraValue(final String localeString, - final String keyboardLayoutSetName, final boolean isAsciiCapable, - final boolean isEmojiCapable) { - final ArrayList<String> extraValueItems = new ArrayList<>(); - extraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName); - if (isAsciiCapable) { - extraValueItems.add(ASCII_CAPABLE); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && - SubtypeLocaleUtils.isExceptionalLocale(localeString)) { - extraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + - SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName)); - } - if (isEmojiCapable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - extraValueItems.add(EMOJI_CAPABLE); - } - extraValueItems.add(IS_ADDITIONAL_SUBTYPE); - return TextUtils.join(",", extraValueItems); - } - - /** - * Returns the subtype ID that is supposed to be compatible between different version of OSes. - * <p> - * From the compatibility point of view, it is important to keep subtype id predictable and - * stable between different OSes. For this purpose, the calculation code in this method is - * carefully chosen and then fixed. Treat the following code as no more or less than a - * hash function. Each component to be hashed can be different from the corresponding value - * that is used to instantiate {@link InputMethodSubtype} actually. - * For example, you don't need to update <code>compatibilityExtraValueItems</code> in this - * method even when we need to add some new extra values for the actual instance of - * {@link InputMethodSubtype}. - * </p> - * @param localeString the locale string (e.g., "en_US"). - * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). - * @return a platform-version independent subtype ID. - * @see #getPlatformVersionDependentExtraValue(String, String, boolean, boolean) - */ - private static int getPlatformVersionIndependentSubtypeId(final String localeString, - final String keyboardLayoutSetName) { - // For compatibility reasons, we concatenate the extra values in the following order. - // - KeyboardLayoutSet - // - AsciiCapable - // - UntranslatableReplacementStringInSubtypeName - // - EmojiCapable - // - isAdditionalSubtype - final ArrayList<String> compatibilityExtraValueItems = new ArrayList<>(); - compatibilityExtraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName); - compatibilityExtraValueItems.add(ASCII_CAPABLE); - if (SubtypeLocaleUtils.isExceptionalLocale(localeString)) { - compatibilityExtraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + - SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName)); - } - compatibilityExtraValueItems.add(EMOJI_CAPABLE); - compatibilityExtraValueItems.add(IS_ADDITIONAL_SUBTYPE); - final String compatibilityExtraValues = TextUtils.join(",", compatibilityExtraValueItems); - return Arrays.hashCode(new Object[] { - localeString, - KEYBOARD_MODE, - compatibilityExtraValues, - false /* isAuxiliary */, - false /* overrideImplicitlyEnabledSubtype */ }); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java deleted file mode 100644 index 7a4150def..000000000 --- a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.util.Log; - -public final class ApplicationUtils { - private static final String TAG = ApplicationUtils.class.getSimpleName(); - - private ApplicationUtils() { - // This utility class is not publicly instantiable. - } - - public static int getActivityTitleResId(final Context context, - final Class<? extends Activity> cls) { - final ComponentName cn = new ComponentName(context, cls); - try { - final ActivityInfo ai = context.getPackageManager().getActivityInfo(cn, 0); - if (ai != null) { - return ai.labelRes; - } - } catch (final NameNotFoundException e) { - Log.e(TAG, "Failed to get settings activity title res id.", e); - } - return 0; - } - - /** - * A utility method to get the application's PackageInfo.versionName - * @return the application's PackageInfo.versionName - */ - public static String getVersionName(final Context context) { - try { - if (context == null) { - return ""; - } - final String packageName = context.getPackageName(); - final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); - return info.versionName; - } catch (final NameNotFoundException e) { - Log.e(TAG, "Could not find version info.", e); - } - return ""; - } - - /** - * A utility method to get the application's PackageInfo.versionCode - * @return the application's PackageInfo.versionCode - */ - public static int getVersionCode(final Context context) { - try { - if (context == null) { - return 0; - } - final String packageName = context.getPackageName(); - final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); - return info.versionCode; - } catch (final NameNotFoundException e) { - Log.e(TAG, "Could not find version info.", e); - } - return 0; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java deleted file mode 100644 index 1525f2d56..000000000 --- a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.util.Log; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * This class is a holder of the result of an asynchronous computation. - * - * @param <E> the type of the result. - */ -public class AsyncResultHolder<E> { - - private final Object mLock = new Object(); - - private E mResult; - private final String mTag; - private final CountDownLatch mLatch; - - public AsyncResultHolder(final String tag) { - mTag = tag; - mLatch = new CountDownLatch(1); - } - - /** - * Sets the result value of this holder. - * - * @param result the value to set. - */ - public void set(final E result) { - synchronized(mLock) { - if (mLatch.getCount() > 0) { - mResult = result; - mLatch.countDown(); - } - } - } - - /** - * Gets the result value held in this holder. - * Causes the current thread to wait unless the value is set or the specified time is elapsed. - * - * @param defaultValue the default value. - * @param timeOut the maximum time to wait. - * @return if the result is set before the time limit then the result, otherwise defaultValue. - */ - public E get(final E defaultValue, final long timeOut) { - try { - return mLatch.await(timeOut, TimeUnit.MILLISECONDS) ? mResult : defaultValue; - } catch (InterruptedException e) { - Log.w(mTag, "get() : Interrupted after " + timeOut + " ms"); - return defaultValue; - } - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java deleted file mode 100644 index c9ecade91..000000000 --- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.utils; - -import android.util.Log; - -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.define.DebugFlags; - -public final class AutoCorrectionUtils { - private static final boolean DBG = DebugFlags.DEBUG_ENABLED; - private static final String TAG = AutoCorrectionUtils.class.getSimpleName(); - - private AutoCorrectionUtils() { - // Purely static class: can't instantiate. - } - - public static boolean suggestionExceedsThreshold(final SuggestedWordInfo suggestion, - final String consideredWord, final float threshold) { - if (null != suggestion) { - // Shortlist a whitelisted word - if (suggestion.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) { - return true; - } - // TODO: return suggestion.isAprapreateForAutoCorrection(); - if (!suggestion.isAprapreateForAutoCorrection()) { - return false; - } - final int autoCorrectionSuggestionScore = suggestion.mScore; - // TODO: when the normalized score of the first suggestion is nearly equals to - // the normalized score of the second suggestion, behave less aggressive. - final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore( - consideredWord, suggestion.mWord, autoCorrectionSuggestionScore); - if (DBG) { - Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + "," - + autoCorrectionSuggestionScore + ", " + normalizedScore - + "(" + threshold + ")"); - } - if (normalizedScore >= threshold) { - if (DBG) { - Log.d(TAG, "Exceeds threshold."); - } - return true; - } - } - return false; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java deleted file mode 100644 index 3bf9c6200..000000000 --- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.BinaryDictionary; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.makedict.DictionaryHeader; -import com.android.inputmethod.latin.makedict.UnsupportedFormatException; - -import java.io.File; -import java.io.IOException; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class BinaryDictionaryUtils { - private static final String TAG = BinaryDictionaryUtils.class.getSimpleName(); - - private BinaryDictionaryUtils() { - // This utility class is not publicly instantiable. - } - - static { - JniUtils.loadNativeLibrary(); - } - - @UsedForTesting - private static native boolean createEmptyDictFileNative(String filePath, long dictVersion, - String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray); - private static native float calcNormalizedScoreNative(int[] before, int[] after, int score); - private static native int setCurrentTimeForTestNative(int currentTime); - - public static DictionaryHeader getHeader(final File dictFile) - throws IOException, UnsupportedFormatException { - return getHeaderWithOffsetAndLength(dictFile, 0 /* offset */, dictFile.length()); - } - - public static DictionaryHeader getHeaderWithOffsetAndLength(final File dictFile, - final long offset, final long length) throws IOException, UnsupportedFormatException { - // dictType is never used for reading the header. Passing an empty string. - final BinaryDictionary binaryDictionary = new BinaryDictionary( - dictFile.getAbsolutePath(), offset, length, - true /* useFullEditDistance */, null /* locale */, "" /* dictType */, - false /* isUpdatable */); - final DictionaryHeader header = binaryDictionary.getHeader(); - binaryDictionary.close(); - if (header == null) { - throw new IOException(); - } - return header; - } - - public static boolean renameDict(final File dictFile, final File newDictFile) { - if (dictFile.isFile()) { - return dictFile.renameTo(newDictFile); - } else if (dictFile.isDirectory()) { - final String dictName = dictFile.getName(); - final String newDictName = newDictFile.getName(); - if (newDictFile.exists()) { - return false; - } - for (final File file : dictFile.listFiles()) { - if (!file.isFile()) { - continue; - } - final String fileName = file.getName(); - final String newFileName = fileName.replaceFirst( - Pattern.quote(dictName), Matcher.quoteReplacement(newDictName)); - if (!file.renameTo(new File(dictFile, newFileName))) { - return false; - } - } - return dictFile.renameTo(newDictFile); - } - return false; - } - - @UsedForTesting - public static boolean createEmptyDictFile(final String filePath, final long dictVersion, - final Locale locale, final Map<String, String> attributeMap) { - final String[] keyArray = new String[attributeMap.size()]; - final String[] valueArray = new String[attributeMap.size()]; - int index = 0; - for (final String key : attributeMap.keySet()) { - keyArray[index] = key; - valueArray[index] = attributeMap.get(key); - index++; - } - return createEmptyDictFileNative(filePath, dictVersion, locale.toString(), keyArray, - valueArray); - } - - public static float calcNormalizedScore(final String before, final String after, - final int score) { - return calcNormalizedScoreNative(StringUtils.toCodePointArray(before), - StringUtils.toCodePointArray(after), score); - } - - /** - * Control the current time to be used in the native code. If currentTime >= 0, this method sets - * the current time and gets into test mode. - * In test mode, set timestamp is used as the current time in the native code. - * If currentTime < 0, quit the test mode and returns to using time() to get the current time. - * - * @param currentTime seconds since the unix epoch - * @return current time got in the native code. - */ - @UsedForTesting - public static int setCurrentTimeForTest(final int currentTime) { - return setCurrentTimeForTestNative(currentTime); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java deleted file mode 100644 index 47f65499d..000000000 --- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.text.InputType; -import android.text.TextUtils; - -import com.android.inputmethod.latin.WordComposer; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.settings.SpacingAndPunctuations; - -import java.util.ArrayList; -import java.util.Locale; - -public final class CapsModeUtils { - private CapsModeUtils() { - // This utility class is not publicly instantiable. - } - - /** - * Apply an auto-caps mode to a string. - * - * This intentionally does NOT apply manual caps mode. It only changes the capitalization if - * the mode is one of the auto-caps modes. - * @param s The string to capitalize. - * @param capitalizeMode The mode in which to capitalize. - * @param locale The locale for capitalizing. - * @return The capitalized string. - */ - public static String applyAutoCapsMode(final String s, final int capitalizeMode, - final Locale locale) { - if (WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == capitalizeMode) { - return s.toUpperCase(locale); - } else if (WordComposer.CAPS_MODE_AUTO_SHIFTED == capitalizeMode) { - return StringUtils.capitalizeFirstCodePoint(s, locale); - } else { - return s; - } - } - - /** - * Return whether a constant represents an auto-caps mode (either auto-shift or auto-shift-lock) - * @param mode The mode to test for - * @return true if this represents an auto-caps mode, false otherwise - */ - public static boolean isAutoCapsMode(final int mode) { - return WordComposer.CAPS_MODE_AUTO_SHIFTED == mode - || WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == mode; - } - - /** - * Helper method to find out if a code point is starting punctuation. - * - * This include the Unicode START_PUNCTUATION category, but also some other symbols that are - * starting, like the inverted question mark or the double quote. - * - * @param codePoint the code point - * @return true if it's starting punctuation, false otherwise. - */ - private static boolean isStartPunctuation(final int codePoint) { - return (codePoint == Constants.CODE_DOUBLE_QUOTE || codePoint == Constants.CODE_SINGLE_QUOTE - || codePoint == Constants.CODE_INVERTED_QUESTION_MARK - || codePoint == Constants.CODE_INVERTED_EXCLAMATION_MARK - || Character.getType(codePoint) == Character.START_PUNCTUATION); - } - - /** - * Determine what caps mode should be in effect at the current offset in - * the text. Only the mode bits set in <var>reqModes</var> will be - * checked. Note that the caps mode flags here are explicitly defined - * to match those in {@link InputType}. - * - * This code is a straight copy of TextUtils.getCapsMode (modulo namespace and formatting - * issues). This will change in the future as we simplify the code for our use and fix bugs. - * - * @param cs The text that should be checked for caps modes. - * @param reqModes The modes to be checked: may be any combination of - * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and - * {@link TextUtils#CAP_MODE_SENTENCES}. - * @param spacingAndPunctuations The current spacing and punctuations settings. - * @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs - * - * @return Returns the actual capitalization modes that can be in effect - * at the current position, which is any combination of - * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and - * {@link TextUtils#CAP_MODE_SENTENCES}. - */ - public static int getCapsMode(final CharSequence cs, final int reqModes, - final SpacingAndPunctuations spacingAndPunctuations, final boolean hasSpaceBefore) { - // Quick description of what we want to do: - // CAP_MODE_CHARACTERS is always on. - // CAP_MODE_WORDS is on if there is some whitespace before the cursor. - // CAP_MODE_SENTENCES is on if there is some whitespace before the cursor, and the end - // of a sentence just before that. - // We ignore opening parentheses and the like just before the cursor for purposes of - // finding whitespace for WORDS and SENTENCES modes. - // The end of a sentence ends with a period, question mark or exclamation mark. If it's - // a period, it also needs not to be an abbreviation, which means it also needs to either - // be immediately preceded by punctuation, or by a string of only letters with single - // periods interleaved. - - // Step 1 : check for cap MODE_CHARACTERS. If it's looked for, it's always on. - if ((reqModes & (TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES)) == 0) { - // Here we are not looking for MODE_WORDS or MODE_SENTENCES, so since we already - // evaluated MODE_CHARACTERS, we can return. - return TextUtils.CAP_MODE_CHARACTERS & reqModes; - } - - // Step 2 : Skip (ignore at the end of input) any opening punctuation. This includes - // opening parentheses, brackets, opening quotes, everything that *opens* a span of - // text in the linguistic sense. In RTL languages, this is still an opening sign, although - // it may look like a right parenthesis for example. We also include double quote and - // single quote since they aren't start punctuation in the unicode sense, but should still - // be skipped for English. TODO: does this depend on the language? - int i; - if (hasSpaceBefore) { - i = cs.length() + 1; - } else { - for (i = cs.length(); i > 0; i--) { - final char c = cs.charAt(i - 1); - if (!isStartPunctuation(c)) { - break; - } - } - } - - // We are now on the character that precedes any starting punctuation, so in the most - // frequent case this will be whitespace or a letter, although it may occasionally be a - // start of line, or some symbol. - - // Step 3 : Search for the start of a paragraph. From the starting point computed in step 2, - // we go back over any space or tab char sitting there. We find the start of a paragraph - // if the first char that's not a space or tab is a start of line (as in \n, start of text, - // or some other similar characters). - int j = i; - char prevChar = Constants.CODE_SPACE; - if (hasSpaceBefore) --j; - while (j > 0) { - prevChar = cs.charAt(j - 1); - if (!Character.isSpaceChar(prevChar) && prevChar != Constants.CODE_TAB) break; - j--; - } - if (j <= 0 || Character.isWhitespace(prevChar)) { - if (spacingAndPunctuations.mUsesGermanRules) { - // In German typography rules, there is a specific case that the first character - // of a new line should not be capitalized if the previous line ends in a comma. - boolean hasNewLine = false; - while (--j >= 0 && Character.isWhitespace(prevChar)) { - if (Constants.CODE_ENTER == prevChar) { - hasNewLine = true; - } - prevChar = cs.charAt(j); - } - if (Constants.CODE_COMMA == prevChar && hasNewLine) { - return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; - } - } - // There are only spacing chars between the start of the paragraph and the cursor, - // defined as a isWhitespace() char that is neither a isSpaceChar() nor a tab. Both - // MODE_WORDS and MODE_SENTENCES should be active. - return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS - | TextUtils.CAP_MODE_SENTENCES) & reqModes; - } - if (i == j) { - // If we don't have whitespace before index i, it means neither MODE_WORDS - // nor mode sentences should be on so we can return right away. - return TextUtils.CAP_MODE_CHARACTERS & reqModes; - } - if ((reqModes & TextUtils.CAP_MODE_SENTENCES) == 0) { - // Here we know we have whitespace before the cursor (if not, we returned in the above - // if i == j clause), so we need MODE_WORDS to be on. And we don't need to evaluate - // MODE_SENTENCES so we can return right away. - return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; - } - // Please note that because of the reqModes & CAP_MODE_SENTENCES test a few lines above, - // we know that MODE_SENTENCES is being requested. - - // Step 4 : Search for MODE_SENTENCES. - // English is a special case in that "American typography" rules, which are the most common - // in English, state that a sentence terminator immediately following a quotation mark - // should be swapped with it and de-duplicated (included in the quotation mark), - // e.g. <<Did they say, "let's go home?">> - // No other language has such a rule as far as I know, instead putting inside the quotation - // mark as the exact thing quoted and handling the surrounding punctuation independently, - // e.g. <<Did they say, "let's go home"?>> - if (spacingAndPunctuations.mUsesAmericanTypography) { - for (; j > 0; j--) { - // Here we look to go over any closing punctuation. This is because in dominant - // variants of English, the final period is placed within double quotes and maybe - // other closing punctuation signs. This is generally not true in other languages. - final char c = cs.charAt(j - 1); - if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE - && Character.getType(c) != Character.END_PUNCTUATION) { - break; - } - } - } - - if (j <= 0) return TextUtils.CAP_MODE_CHARACTERS & reqModes; - char c = cs.charAt(--j); - - // We found the next interesting chunk of text ; next we need to determine if it's the - // end of a sentence. If we have a sentence terminator (typically a question mark or an - // exclamation mark), then it's the end of a sentence; however, we treat the abbreviation - // marker specially because usually is the same char as the sentence separator (the - // period in most languages) and in this case we need to apply a heuristic to determine - // in which of these senses it's used. - if (spacingAndPunctuations.isSentenceTerminator(c) - && !spacingAndPunctuations.isAbbreviationMarker(c)) { - return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS - | TextUtils.CAP_MODE_SENTENCES) & reqModes; - } - // If we reach here, we know we have whitespace before the cursor and before that there - // is something that either does not terminate the sentence, or a symbol preceded by the - // start of the text, or it's the sentence separator AND it happens to be the same code - // point as the abbreviation marker. - // If it's a symbol or something that does not terminate the sentence, then we need to - // return caps for MODE_CHARACTERS and MODE_WORDS, but not for MODE_SENTENCES. - if (!spacingAndPunctuations.isSentenceSeparator(c) || j <= 0) { - return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; - } - - // We found out that we have a period. We need to determine if this is a full stop or - // otherwise sentence-ending period, or an abbreviation like "e.g.". An abbreviation - // looks like (\w\.){2,}. Moreover, in German, you put periods after digits for dates - // and some other things, and in German specifically we need to not go into autocaps after - // a whitespace-digits-period sequence. - // To find out, we will have a simple state machine with the following states : - // START, WORD, PERIOD, ABBREVIATION, NUMBER - // On START : (just before the first period) - // letter => WORD - // digit => NUMBER if German; end with caps otherwise - // whitespace => end with no caps (it was a stand-alone period) - // otherwise => end with caps (several periods/symbols in a row) - // On WORD : (within the word just before the first period) - // letter => WORD - // period => PERIOD - // otherwise => end with caps (it was a word with a full stop at the end) - // On PERIOD : (period within a potential abbreviation) - // letter => LETTER - // otherwise => end with caps (it was not an abbreviation) - // On LETTER : (letter within a potential abbreviation) - // letter => LETTER - // period => PERIOD - // otherwise => end with no caps (it was an abbreviation) - // On NUMBER : (period immediately preceded by one or more digits) - // digit => NUMBER - // letter => LETTER (promote to word) - // otherwise => end with no caps (it was a whitespace-digits-period sequence, - // or a punctuation-digits-period sequence like "11.11.") - // "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This - // should capitalize. - - final int START = 0; - final int WORD = 1; - final int PERIOD = 2; - final int LETTER = 3; - final int NUMBER = 4; - final int caps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS - | TextUtils.CAP_MODE_SENTENCES) & reqModes; - final int noCaps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; - int state = START; - while (j > 0) { - c = cs.charAt(--j); - switch (state) { - case START: - if (Character.isLetter(c)) { - state = WORD; - } else if (Character.isWhitespace(c)) { - return noCaps; - } else if (Character.isDigit(c) && spacingAndPunctuations.mUsesGermanRules) { - state = NUMBER; - } else { - return caps; - } - break; - case WORD: - if (Character.isLetter(c)) { - state = WORD; - } else if (spacingAndPunctuations.isSentenceSeparator(c)) { - state = PERIOD; - } else { - return caps; - } - break; - case PERIOD: - if (Character.isLetter(c)) { - state = LETTER; - } else { - return caps; - } - break; - case LETTER: - if (Character.isLetter(c)) { - state = LETTER; - } else if (spacingAndPunctuations.isSentenceSeparator(c)) { - state = PERIOD; - } else { - return noCaps; - } - break; - case NUMBER: - if (Character.isLetter(c)) { - state = WORD; - } else if (Character.isDigit(c)) { - state = NUMBER; - } else { - return noCaps; - } - } - } - // Here we arrived at the start of the line. This should behave exactly like whitespace. - return (START == state || LETTER == state) ? noCaps : caps; - } - - /** - * Convert capitalize mode flags into human readable text. - * - * @param capsFlags The modes flags to be converted. It may be any combination of - * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and - * {@link TextUtils#CAP_MODE_SENTENCES}. - * @return the text that describe the <code>capsMode</code>. - */ - public static String flagsToString(final int capsFlags) { - final int capsFlagsMask = TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS - | TextUtils.CAP_MODE_SENTENCES; - if ((capsFlags & ~capsFlagsMask) != 0) { - return "unknown<0x" + Integer.toHexString(capsFlags) + ">"; - } - final ArrayList<String> builder = new ArrayList<>(); - if ((capsFlags & android.text.TextUtils.CAP_MODE_CHARACTERS) != 0) { - builder.add("characters"); - } - if ((capsFlags & android.text.TextUtils.CAP_MODE_WORDS) != 0) { - builder.add("words"); - } - if ((capsFlags & android.text.TextUtils.CAP_MODE_SENTENCES) != 0) { - builder.add("sentences"); - } - return builder.isEmpty() ? "none" : TextUtils.join("|", builder); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java deleted file mode 100644 index 5c0c4328f..000000000 --- a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import com.android.inputmethod.latin.makedict.DictionaryHeader; -import com.android.inputmethod.latin.makedict.NgramProperty; -import com.android.inputmethod.latin.makedict.ProbabilityInfo; -import com.android.inputmethod.latin.makedict.WordProperty; - -import java.util.HashMap; - -public class CombinedFormatUtils { - public static final String DICTIONARY_TAG = "dictionary"; - public static final String BIGRAM_TAG = "bigram"; - public static final String NGRAM_TAG = "ngram"; - public static final String NGRAM_PREV_WORD_TAG = "prev_word"; - public static final String PROBABILITY_TAG = "f"; - public static final String HISTORICAL_INFO_TAG = "historicalInfo"; - public static final String HISTORICAL_INFO_SEPARATOR = ":"; - public static final String WORD_TAG = "word"; - public static final String BEGINNING_OF_SENTENCE_TAG = "beginning_of_sentence"; - public static final String NOT_A_WORD_TAG = "not_a_word"; - public static final String POSSIBLY_OFFENSIVE_TAG = "possibly_offensive"; - public static final String TRUE_VALUE = "true"; - - public static String formatAttributeMap(final HashMap<String, String> attributeMap) { - final StringBuilder builder = new StringBuilder(); - builder.append(DICTIONARY_TAG + "="); - if (attributeMap.containsKey(DictionaryHeader.DICTIONARY_ID_KEY)) { - builder.append(attributeMap.get(DictionaryHeader.DICTIONARY_ID_KEY)); - } - for (final String key : attributeMap.keySet()) { - if (key.equals(DictionaryHeader.DICTIONARY_ID_KEY)) { - continue; - } - final String value = attributeMap.get(key); - builder.append("," + key + "=" + value); - } - builder.append("\n"); - return builder.toString(); - } - - public static String formatWordProperty(final WordProperty wordProperty) { - final StringBuilder builder = new StringBuilder(); - builder.append(" " + WORD_TAG + "=" + wordProperty.mWord); - builder.append(","); - builder.append(formatProbabilityInfo(wordProperty.mProbabilityInfo)); - if (wordProperty.mIsBeginningOfSentence) { - builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=" + TRUE_VALUE); - } - if (wordProperty.mIsNotAWord) { - builder.append("," + NOT_A_WORD_TAG + "=" + TRUE_VALUE); - } - if (wordProperty.mIsPossiblyOffensive) { - builder.append("," + POSSIBLY_OFFENSIVE_TAG + "=" + TRUE_VALUE); - } - builder.append("\n"); - if (wordProperty.mHasNgrams) { - for (final NgramProperty ngramProperty : wordProperty.mNgrams) { - builder.append(" " + NGRAM_TAG + "=" + ngramProperty.mTargetWord.mWord); - builder.append(","); - builder.append(formatProbabilityInfo(ngramProperty.mTargetWord.mProbabilityInfo)); - builder.append("\n"); - for (int i = 0; i < ngramProperty.mNgramContext.getPrevWordCount(); i++) { - builder.append(" " + NGRAM_PREV_WORD_TAG + "[" + i + "]=" - + ngramProperty.mNgramContext.getNthPrevWord(i + 1)); - if (ngramProperty.mNgramContext.isNthPrevWordBeginningOfSentence(i + 1)) { - builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=true"); - } - builder.append("\n"); - } - } - } - return builder.toString(); - } - - public static String formatProbabilityInfo(final ProbabilityInfo probabilityInfo) { - final StringBuilder builder = new StringBuilder(); - builder.append(PROBABILITY_TAG + "=" + probabilityInfo.mProbability); - if (probabilityInfo.hasHistoricalInfo()) { - builder.append(","); - builder.append(HISTORICAL_INFO_TAG + "="); - builder.append(probabilityInfo.mTimestamp); - builder.append(HISTORICAL_INFO_SEPARATOR); - builder.append(probabilityInfo.mLevel); - builder.append(HISTORICAL_INFO_SEPARATOR); - builder.append(probabilityInfo.mCount); - } - return builder.toString(); - } - - public static boolean isLiteralTrue(final String value) { - return TRUE_VALUE.equalsIgnoreCase(value); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java deleted file mode 100644 index 5ccf0e079..000000000 --- a/java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.text.TextUtils; -import android.view.inputmethod.CompletionInfo; - -import java.util.Arrays; - -/** - * Utilities to do various stuff with CompletionInfo. - */ -public class CompletionInfoUtils { - private CompletionInfoUtils() { - // This utility class is not publicly instantiable. - } - - public static CompletionInfo[] removeNulls(final CompletionInfo[] src) { - int j = 0; - final CompletionInfo[] dst = new CompletionInfo[src.length]; - for (int i = 0; i < src.length; ++i) { - if (null != src[i] && !TextUtils.isEmpty(src[i].getText())) { - dst[j] = src[i]; - ++j; - } - } - return Arrays.copyOfRange(dst, 0, j); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java deleted file mode 100644 index 41090c054..000000000 --- a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import android.annotation.TargetApi; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.inputmethodservice.ExtractEditText; -import android.inputmethodservice.InputMethodService; -import android.os.Build; -import android.text.Layout; -import android.text.Spannable; -import android.text.Spanned; -import android.view.View; -import android.view.ViewParent; -import android.view.inputmethod.CursorAnchorInfo; -import android.widget.TextView; - -import com.android.inputmethod.compat.BuildCompatUtils; -import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * This class allows input methods to extract {@link CursorAnchorInfo} directly from the given - * {@link TextView}. This is useful and even necessary to support full-screen mode where the default - * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} event callback must be - * ignored because it reports the character locations of the target application rather than - * characters on {@link ExtractEditText}. - */ -public final class CursorAnchorInfoUtils { - private CursorAnchorInfoUtils() { - // This helper class is not instantiable. - } - - private static boolean isPositionVisible(final View view, final float positionX, - final float positionY) { - final float[] position = new float[] { positionX, positionY }; - View currentView = view; - - while (currentView != null) { - if (currentView != view) { - // Local scroll is already taken into account in positionX/Y - position[0] -= currentView.getScrollX(); - position[1] -= currentView.getScrollY(); - } - - if (position[0] < 0 || position[1] < 0 || - position[0] > currentView.getWidth() || position[1] > currentView.getHeight()) { - return false; - } - - if (!currentView.getMatrix().isIdentity()) { - currentView.getMatrix().mapPoints(position); - } - - position[0] += currentView.getLeft(); - position[1] += currentView.getTop(); - - final ViewParent parent = currentView.getParent(); - if (parent instanceof View) { - currentView = (View) parent; - } else { - // We've reached the ViewRoot, stop iterating - currentView = null; - } - } - - // We've been able to walk up the view hierarchy and the position was never clipped - return true; - } - - /** - * Extracts {@link CursorAnchorInfoCompatWrapper} from the given {@link TextView}. - * @param textView the target text view from which {@link CursorAnchorInfoCompatWrapper} is to - * be extracted. - * @return the {@link CursorAnchorInfoCompatWrapper} object based on the current layout. - * {@code null} if {@code Build.VERSION.SDK_INT} is 20 or prior or {@link TextView} is not - * ready to provide layout information. - */ - @Nullable - public static CursorAnchorInfoCompatWrapper extractFromTextView( - @Nonnull final TextView textView) { - if (BuildCompatUtils.EFFECTIVE_SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return null; - } - return CursorAnchorInfoCompatWrapper.wrap(extractFromTextViewInternal(textView)); - } - - /** - * Returns {@link CursorAnchorInfo} from the given {@link TextView}. - * @param textView the target text view from which {@link CursorAnchorInfo} is to be extracted. - * @return the {@link CursorAnchorInfo} object based on the current layout. {@code null} if it - * is not feasible. - */ - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Nullable - private static CursorAnchorInfo extractFromTextViewInternal(@Nonnull final TextView textView) { - final Layout layout = textView.getLayout(); - if (layout == null) { - return null; - } - - final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); - - final int selectionStart = textView.getSelectionStart(); - builder.setSelectionRange(selectionStart, textView.getSelectionEnd()); - - // Construct transformation matrix from view local coordinates to screen coordinates. - final Matrix viewToScreenMatrix = new Matrix(textView.getMatrix()); - final int[] viewOriginInScreen = new int[2]; - textView.getLocationOnScreen(viewOriginInScreen); - viewToScreenMatrix.postTranslate(viewOriginInScreen[0], viewOriginInScreen[1]); - builder.setMatrix(viewToScreenMatrix); - - if (layout.getLineCount() == 0) { - return null; - } - final Rect lineBoundsWithoutOffset = new Rect(); - final Rect lineBoundsWithOffset = new Rect(); - layout.getLineBounds(0, lineBoundsWithoutOffset); - textView.getLineBounds(0, lineBoundsWithOffset); - final float viewportToContentHorizontalOffset = lineBoundsWithOffset.left - - lineBoundsWithoutOffset.left - textView.getScrollX(); - final float viewportToContentVerticalOffset = lineBoundsWithOffset.top - - lineBoundsWithoutOffset.top - textView.getScrollY(); - - final CharSequence text = textView.getText(); - if (text instanceof Spannable) { - // Here we assume that the composing text is marked as SPAN_COMPOSING flag. This is not - // necessarily true, but basically works. - int composingTextStart = text.length(); - int composingTextEnd = 0; - final Spannable spannable = (Spannable) text; - final Object[] spans = spannable.getSpans(0, text.length(), Object.class); - for (Object span : spans) { - final int spanFlag = spannable.getSpanFlags(span); - if ((spanFlag & Spanned.SPAN_COMPOSING) != 0) { - composingTextStart = Math.min(composingTextStart, - spannable.getSpanStart(span)); - composingTextEnd = Math.max(composingTextEnd, spannable.getSpanEnd(span)); - } - } - - final boolean hasComposingText = - (0 <= composingTextStart) && (composingTextStart < composingTextEnd); - if (hasComposingText) { - final CharSequence composingText = text.subSequence(composingTextStart, - composingTextEnd); - builder.setComposingText(composingTextStart, composingText); - - final int minLine = layout.getLineForOffset(composingTextStart); - final int maxLine = layout.getLineForOffset(composingTextEnd - 1); - for (int line = minLine; line <= maxLine; ++line) { - final int lineStart = layout.getLineStart(line); - final int lineEnd = layout.getLineEnd(line); - final int offsetStart = Math.max(lineStart, composingTextStart); - final int offsetEnd = Math.min(lineEnd, composingTextEnd); - final boolean ltrLine = - layout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; - final float[] widths = new float[offsetEnd - offsetStart]; - layout.getPaint().getTextWidths(text, offsetStart, offsetEnd, widths); - final float top = layout.getLineTop(line); - final float bottom = layout.getLineBottom(line); - for (int offset = offsetStart; offset < offsetEnd; ++offset) { - final float charWidth = widths[offset - offsetStart]; - final boolean isRtl = layout.isRtlCharAt(offset); - final float primary = layout.getPrimaryHorizontal(offset); - final float secondary = layout.getSecondaryHorizontal(offset); - // TODO: This doesn't work perfectly for text with custom styles and TAB - // chars. - final float left; - final float right; - if (ltrLine) { - if (isRtl) { - left = secondary - charWidth; - right = secondary; - } else { - left = primary; - right = primary + charWidth; - } - } else { - if (!isRtl) { - left = secondary; - right = secondary + charWidth; - } else { - left = primary - charWidth; - right = primary; - } - } - // TODO: Check top-right and bottom-left as well. - final float localLeft = left + viewportToContentHorizontalOffset; - final float localRight = right + viewportToContentHorizontalOffset; - final float localTop = top + viewportToContentVerticalOffset; - final float localBottom = bottom + viewportToContentVerticalOffset; - final boolean isTopLeftVisible = isPositionVisible(textView, - localLeft, localTop); - final boolean isBottomRightVisible = - isPositionVisible(textView, localRight, localBottom); - int characterBoundsFlags = 0; - if (isTopLeftVisible || isBottomRightVisible) { - characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; - } - if (!isTopLeftVisible || !isBottomRightVisible) { - characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; - } - if (isRtl) { - characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; - } - // Here offset is the index in Java chars. - builder.addCharacterBounds(offset, localLeft, localTop, localRight, - localBottom, characterBoundsFlags); - } - } - } - } - - // Treat selectionStart as the insertion point. - if (0 <= selectionStart) { - final int offset = selectionStart; - final int line = layout.getLineForOffset(offset); - final float insertionMarkerX = layout.getPrimaryHorizontal(offset) - + viewportToContentHorizontalOffset; - final float insertionMarkerTop = layout.getLineTop(line) - + viewportToContentVerticalOffset; - final float insertionMarkerBaseline = layout.getLineBaseline(line) - + viewportToContentVerticalOffset; - final float insertionMarkerBottom = layout.getLineBottom(line) - + viewportToContentVerticalOffset; - final boolean isTopVisible = - isPositionVisible(textView, insertionMarkerX, insertionMarkerTop); - final boolean isBottomVisible = - isPositionVisible(textView, insertionMarkerX, insertionMarkerBottom); - int insertionMarkerFlags = 0; - if (isTopVisible || isBottomVisible) { - insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; - } - if (!isTopVisible || !isBottomVisible) { - insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; - } - if (layout.isRtlCharAt(offset)) { - insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL; - } - builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, - insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags); - } - return builder.build(); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java b/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java deleted file mode 100644 index 1ab834fc3..000000000 --- a/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.utils; - -import android.util.Log; - -import com.android.inputmethod.latin.define.DebugFlags; - -/** - * A class for logging and debugging utility methods. - */ -public final class DebugLogUtils { - private final static String TAG = DebugLogUtils.class.getSimpleName(); - private final static boolean sDBG = DebugFlags.DEBUG_ENABLED; - - /** - * Calls .toString() on its non-null argument or returns "null" - * @param o the object to convert to a string - * @return the result of .toString() or null - */ - public static String s(final Object o) { - return null == o ? "null" : o.toString(); - } - - /** - * Get the string representation of the current stack trace, for debugging purposes. - * @return a readable, carriage-return-separated string for the current stack trace. - */ - public static String getStackTrace() { - return getStackTrace(Integer.MAX_VALUE - 1); - } - - /** - * Get the string representation of the current stack trace, for debugging purposes. - * @param limit the maximum number of stack frames to be returned. - * @return a readable, carriage-return-separated string for the current stack trace. - */ - public static String getStackTrace(final int limit) { - final StringBuilder sb = new StringBuilder(); - try { - throw new RuntimeException(); - } catch (final RuntimeException e) { - final StackTraceElement[] frames = e.getStackTrace(); - // Start at 1 because the first frame is here and we don't care about it - for (int j = 1; j < frames.length && j < limit + 1; ++j) { - sb.append(frames[j].toString() + "\n"); - } - } - return sb.toString(); - } - - /** - * Get the stack trace contained in an exception as a human-readable string. - * @param t the throwable - * @return the human-readable stack trace - */ - public static String getStackTrace(final Throwable t) { - final StringBuilder sb = new StringBuilder(); - final StackTraceElement[] frames = t.getStackTrace(); - for (int j = 0; j < frames.length; ++j) { - sb.append(frames[j].toString() + "\n"); - } - return sb.toString(); - } - - /** - * Helper log method to ease null-checks and adding spaces. - * - * This sends all arguments to the log, separated by spaces. Any null argument is converted - * to the "null" string. It uses a very visible tag and log level for debugging purposes. - * - * @param args the stuff to send to the log - */ - public static void l(final Object... args) { - if (!sDBG) return; - final StringBuilder sb = new StringBuilder(); - for (final Object o : args) { - sb.append(s(o).toString()); - sb.append(" "); - } - Log.e(TAG, sb.toString()); - } - - /** - * Helper log method to put stuff in red. - * - * This does the same as #l but prints in red - * - * @param args the stuff to send to the log - */ - public static void r(final Object... args) { - if (!sDBG) return; - final StringBuilder sb = new StringBuilder("\u001B[31m"); - for (final Object o : args) { - sb.append(s(o).toString()); - sb.append(" "); - } - sb.append("\u001B[0m"); - Log.e(TAG, sb.toString()); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/DialogUtils.java b/java/src/com/android/inputmethod/latin/utils/DialogUtils.java deleted file mode 100644 index a05c932d0..000000000 --- a/java/src/com/android/inputmethod/latin/utils/DialogUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import android.content.Context; -import android.view.ContextThemeWrapper; - -import com.android.inputmethod.latin.R; - -public final class DialogUtils { - private DialogUtils() { - // This utility class is not publicly instantiable. - } - - public static Context getPlatformDialogThemeContext(final Context context) { - // Because {@link AlertDialog.Builder.create()} doesn't honor the specified theme with - // createThemeContextWrapper=false, the result dialog box has unneeded paddings around it. - return new ContextThemeWrapper(context, R.style.platformDialogTheme); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryHeaderUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryHeaderUtils.java deleted file mode 100644 index 5eb613c9b..000000000 --- a/java/src/com/android/inputmethod/latin/utils/DictionaryHeaderUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.inputmethod.latin.utils; - -import com.android.inputmethod.latin.AssetFileAddress; -import com.android.inputmethod.latin.makedict.DictionaryHeader; - -import java.io.File; - -public class DictionaryHeaderUtils { - - public static int getContentVersion(AssetFileAddress fileAddress) { - final DictionaryHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull( - new File(fileAddress.mFilename), fileAddress.mOffset, fileAddress.mLength); - return Integer.parseInt(header.mVersionString); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java deleted file mode 100644 index cea2e13b1..000000000 --- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java +++ /dev/null @@ -1,613 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.content.ContentValues; -import android.content.Context; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.text.TextUtils; -import android.util.Log; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.dictionarypack.UpdateHandler; -import com.android.inputmethod.latin.AssetFileAddress; -import com.android.inputmethod.latin.BinaryDictionaryGetter; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodManager; -import com.android.inputmethod.latin.common.FileUtils; -import com.android.inputmethod.latin.common.LocaleUtils; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; -import com.android.inputmethod.latin.makedict.DictionaryHeader; -import com.android.inputmethod.latin.makedict.UnsupportedFormatException; -import com.android.inputmethod.latin.settings.SpacingAndPunctuations; - -import java.io.File; -import java.io.FilenameFilter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * This class encapsulates the logic for the Latin-IME side of dictionary information management. - */ -public class DictionaryInfoUtils { - private static final String TAG = DictionaryInfoUtils.class.getSimpleName(); - public static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName(); - private static final String DEFAULT_MAIN_DICT = "main"; - private static final String MAIN_DICT_PREFIX = "main_"; - private static final String DECODER_DICT_SUFFIX = DecoderSpecificConstants.DECODER_DICT_SUFFIX; - // 6 digits - unicode is limited to 21 bits - private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6; - - private static final String TEMP_DICT_FILE_SUB = UpdateHandler.TEMP_DICT_FILE_SUB; - - public static class DictionaryInfo { - private static final String LOCALE_COLUMN = "locale"; - private static final String WORDLISTID_COLUMN = "id"; - private static final String LOCAL_FILENAME_COLUMN = "filename"; - private static final String DESCRIPTION_COLUMN = "description"; - private static final String DATE_COLUMN = "date"; - private static final String FILESIZE_COLUMN = "filesize"; - private static final String VERSION_COLUMN = "version"; - - @Nonnull public final String mId; - @Nonnull public final Locale mLocale; - @Nullable public final String mDescription; - @Nullable public final String mFilename; - public final long mFilesize; - public final long mModifiedTimeMillis; - public final int mVersion; - - public DictionaryInfo(@Nonnull String id, @Nonnull Locale locale, - @Nullable String description, @Nullable String filename, - long filesize, long modifiedTimeMillis, int version) { - mId = id; - mLocale = locale; - mDescription = description; - mFilename = filename; - mFilesize = filesize; - mModifiedTimeMillis = modifiedTimeMillis; - mVersion = version; - } - - public ContentValues toContentValues() { - final ContentValues values = new ContentValues(); - values.put(WORDLISTID_COLUMN, mId); - values.put(LOCALE_COLUMN, mLocale.toString()); - values.put(DESCRIPTION_COLUMN, mDescription); - values.put(LOCAL_FILENAME_COLUMN, mFilename != null ? mFilename : ""); - values.put(DATE_COLUMN, TimeUnit.MILLISECONDS.toSeconds(mModifiedTimeMillis)); - values.put(FILESIZE_COLUMN, mFilesize); - values.put(VERSION_COLUMN, mVersion); - return values; - } - - @Override - public String toString() { - return "DictionaryInfo : Id = '" + mId - + "' : Locale=" + mLocale - + " : Version=" + mVersion; - } - } - - private DictionaryInfoUtils() { - // Private constructor to forbid instantation of this helper class. - } - - /** - * Returns whether we may want to use this character as part of a file name. - * - * This basically only accepts ascii letters and numbers, and rejects everything else. - */ - private static boolean isFileNameCharacter(int codePoint) { - if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit - if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase - if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase - return codePoint == '_'; // Underscore - } - - /** - * Escapes a string for any characters that may be suspicious for a file or directory name. - * - * Concretely this does a sort of URL-encoding except it will encode everything that's not - * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which - * we cannot allow here) - */ - // TODO: create a unit test for this method - public static String replaceFileNameDangerousCharacters(final String name) { - // This assumes '%' is fully available as a non-separator, normal - // character in a file name. This is probably true for all file systems. - final StringBuilder sb = new StringBuilder(); - final int nameLength = name.length(); - for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) { - final int codePoint = name.codePointAt(i); - if (DictionaryInfoUtils.isFileNameCharacter(codePoint)) { - sb.appendCodePoint(codePoint); - } else { - sb.append(String.format((Locale)null, "%%%1$0" + MAX_HEX_DIGITS_FOR_CODEPOINT + "x", - codePoint)); - } - } - return sb.toString(); - } - - /** - * Helper method to get the top level cache directory. - */ - private static String getWordListCacheDirectory(final Context context) { - return context.getFilesDir() + File.separator + "dicts"; - } - - /** - * Helper method to get the top level cache directory. - */ - public static String getWordListStagingDirectory(final Context context) { - return context.getFilesDir() + File.separator + "staging"; - } - - /** - * Helper method to get the top level temp directory. - */ - public static String getWordListTempDirectory(final Context context) { - return context.getFilesDir() + File.separator + "tmp"; - } - - /** - * Reverse escaping done by {@link #replaceFileNameDangerousCharacters(String)}. - */ - @Nonnull - public static String getWordListIdFromFileName(@Nonnull final String fname) { - final StringBuilder sb = new StringBuilder(); - final int fnameLength = fname.length(); - for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) { - final int codePoint = fname.codePointAt(i); - if ('%' != codePoint) { - sb.appendCodePoint(codePoint); - } else { - // + 1 to pass the % sign - final int encodedCodePoint = Integer.parseInt( - fname.substring(i + 1, i + 1 + MAX_HEX_DIGITS_FOR_CODEPOINT), 16); - i += MAX_HEX_DIGITS_FOR_CODEPOINT; - sb.appendCodePoint(encodedCodePoint); - } - } - return sb.toString(); - } - - /** - * Helper method to the list of cache directories, one for each distinct locale. - */ - public static File[] getCachedDirectoryList(final Context context) { - return new File(DictionaryInfoUtils.getWordListCacheDirectory(context)).listFiles(); - } - - public static File[] getStagingDirectoryList(final Context context) { - return new File(DictionaryInfoUtils.getWordListStagingDirectory(context)).listFiles(); - } - - @Nullable - public static File[] getUnusedDictionaryList(final Context context) { - return context.getFilesDir().listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String filename) { - return !TextUtils.isEmpty(filename) && filename.endsWith(".dict") - && filename.contains(TEMP_DICT_FILE_SUB); - } - }); - } - - /** - * Returns the category for a given file name. - * - * This parses the file name, extracts the category, and returns it. See - * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}. - * @return The category as a string or null if it can't be found in the file name. - */ - @Nullable - public static String getCategoryFromFileName(@Nonnull final String fileName) { - final String id = getWordListIdFromFileName(fileName); - final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR); - // An id is supposed to be in format category:locale, so splitting on the separator - // should yield a 2-elements array - if (2 != idArray.length) { - return null; - } - return idArray[0]; - } - - /** - * Find out the cache directory associated with a specific locale. - */ - public static String getCacheDirectoryForLocale(final String locale, final Context context) { - final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale); - final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator - + relativeDirectoryName; - final File directory = new File(absoluteDirectoryName); - if (!directory.exists()) { - if (!directory.mkdirs()) { - Log.e(TAG, "Could not create the directory for locale" + locale); - } - } - return absoluteDirectoryName; - } - - /** - * Generates a file name for the id and locale passed as an argument. - * - * In the current implementation the file name returned will always be unique for - * any id/locale pair, but please do not expect that the id can be the same for - * different dictionaries with different locales. An id should be unique for any - * dictionary. - * The file name is pretty much an URL-encoded version of the id inside a directory - * named like the locale, except it will also escape characters that look dangerous - * to some file systems. - * @param id the id of the dictionary for which to get a file name - * @param locale the locale for which to get the file name as a string - * @param context the context to use for getting the directory - * @return the name of the file to be created - */ - public static String getCacheFileName(String id, String locale, Context context) { - final String fileName = replaceFileNameDangerousCharacters(id); - return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; - } - - public static String getStagingFileName(String id, String locale, Context context) { - final String stagingDirectory = getWordListStagingDirectory(context); - // create the directory if it does not exist. - final File directory = new File(stagingDirectory); - if (!directory.exists()) { - if (!directory.mkdirs()) { - Log.e(TAG, "Could not create the staging directory."); - } - } - // e.g. id="main:en_in", locale ="en_IN" - final String fileName = replaceFileNameDangerousCharacters( - locale + TEMP_DICT_FILE_SUB + id); - return stagingDirectory + File.separator + fileName; - } - - public static void moveStagingFilesIfExists(Context context) { - final File[] stagingFiles = DictionaryInfoUtils.getStagingDirectoryList(context); - if (stagingFiles != null && stagingFiles.length > 0) { - for (final File stagingFile : stagingFiles) { - final String fileName = stagingFile.getName(); - final int index = fileName.indexOf(TEMP_DICT_FILE_SUB); - if (index == -1) { - // This should never happen. - Log.e(TAG, "Staging file does not have ___ substring."); - continue; - } - final String[] localeAndFileId = fileName.split(TEMP_DICT_FILE_SUB); - if (localeAndFileId.length != 2) { - Log.e(TAG, String.format("malformed staging file %s. Deleting.", - stagingFile.getAbsoluteFile())); - stagingFile.delete(); - continue; - } - - final String locale = localeAndFileId[0]; - // already escaped while moving to staging. - final String fileId = localeAndFileId[1]; - final String cacheDirectoryForLocale = getCacheDirectoryForLocale(locale, context); - final String cacheFilename = cacheDirectoryForLocale + File.separator + fileId; - final File cacheFile = new File(cacheFilename); - // move the staging file to cache file. - if (!FileUtils.renameTo(stagingFile, cacheFile)) { - Log.e(TAG, String.format("Failed to rename from %s to %s.", - stagingFile.getAbsoluteFile(), cacheFile.getAbsoluteFile())); - } - } - } - } - - public static boolean isMainWordListId(final String id) { - final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR); - // An id is supposed to be in format category:locale, so splitting on the separator - // should yield a 2-elements array - if (2 != idArray.length) { - return false; - } - return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]); - } - - /** - * Find out whether a dictionary is available for this locale. - * @param context the context on which to check resources. - * @param locale the locale to check for. - * @return whether a (non-placeholder) dictionary is available or not. - */ - public static boolean isDictionaryAvailable(final Context context, final Locale locale) { - final Resources res = context.getResources(); - return 0 != getMainDictionaryResourceIdIfAvailableForLocale(res, locale); - } - - /** - * Helper method to return a dictionary res id for a locale, or 0 if none. - * @param res resources for the app - * @param locale dictionary locale - * @return main dictionary resource id - */ - public static int getMainDictionaryResourceIdIfAvailableForLocale(final Resources res, - final Locale locale) { - int resId; - // Try to find main_language_country dictionary. - if (!locale.getCountry().isEmpty()) { - final String dictLanguageCountry = MAIN_DICT_PREFIX - + locale.toString().toLowerCase(Locale.ROOT) + DECODER_DICT_SUFFIX; - if ((resId = res.getIdentifier( - dictLanguageCountry, "raw", RESOURCE_PACKAGE_NAME)) != 0) { - return resId; - } - } - - // Try to find main_language dictionary. - final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage() + DECODER_DICT_SUFFIX; - if ((resId = res.getIdentifier(dictLanguage, "raw", RESOURCE_PACKAGE_NAME)) != 0) { - return resId; - } - - // Not found, return 0 - return 0; - } - - /** - * Returns a main dictionary resource id - * @param res resources for the app - * @param locale dictionary locale - * @return main dictionary resource id - */ - public static int getMainDictionaryResourceId(final Resources res, final Locale locale) { - int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale); - if (0 != resourceId) { - return resourceId; - } - return res.getIdentifier(DEFAULT_MAIN_DICT + DecoderSpecificConstants.DECODER_DICT_SUFFIX, - "raw", RESOURCE_PACKAGE_NAME); - } - - /** - * Returns the id associated with the main word list for a specified locale. - * - * Word lists stored in Android Keyboard's resources are referred to as the "main" - * word lists. Since they can be updated like any other list, we need to assign a - * unique ID to them. This ID is just the name of the language (locale-wise) they - * are for, and this method returns this ID. - */ - public static String getMainDictId(@Nonnull final Locale locale) { - // This works because we don't include by default different dictionaries for - // different countries. This actually needs to return the id that we would - // like to use for word lists included in resources, and the following is okay. - return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY + - BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.toString().toLowerCase(); - } - - public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file, - final long offset, final long length) { - try { - final DictionaryHeader header = - BinaryDictionaryUtils.getHeaderWithOffsetAndLength(file, offset, length); - return header; - } catch (UnsupportedFormatException e) { - return null; - } catch (IOException e) { - return null; - } - } - - /** - * Returns information of the dictionary. - * - * @param fileAddress the asset dictionary file address. - * @param locale Locale for this file. - * @return information of the specified dictionary. - */ - private static DictionaryInfo createDictionaryInfoFromFileAddress( - @Nonnull final AssetFileAddress fileAddress, final Locale locale) { - final String id = getMainDictId(locale); - final int version = DictionaryHeaderUtils.getContentVersion(fileAddress); - final String description = SubtypeLocaleUtils - .getSubtypeLocaleDisplayName(locale.toString()); - // Do not store the filename on db as it will try to move the filename from db to the - // cached directory. If the filename is already in cached directory, this is not - // necessary. - final String filenameToStoreOnDb = null; - return new DictionaryInfo(id, locale, description, filenameToStoreOnDb, - fileAddress.mLength, new File(fileAddress.mFilename).lastModified(), version); - } - - /** - * Returns the information of the dictionary for the given {@link AssetFileAddress}. - * If the file is corrupted or a pre-fava file, then the file gets deleted and the null - * value is returned. - */ - @Nullable - private static DictionaryInfo createDictionaryInfoForUnCachedFile( - @Nonnull final AssetFileAddress fileAddress, final Locale locale) { - final String id = getMainDictId(locale); - final int version = DictionaryHeaderUtils.getContentVersion(fileAddress); - - if (version == -1) { - // Purge the pre-fava/corrupted unused dictionaires. - fileAddress.deleteUnderlyingFile(); - return null; - } - - final String description = SubtypeLocaleUtils - .getSubtypeLocaleDisplayName(locale.toString()); - - final File unCachedFile = new File(fileAddress.mFilename); - // Store just the filename and not the full path. - final String filenameToStoreOnDb = unCachedFile.getName(); - return new DictionaryInfo(id, locale, description, filenameToStoreOnDb, fileAddress.mLength, - unCachedFile.lastModified(), version); - } - - /** - * Returns dictionary information for the given locale. - */ - private static DictionaryInfo createDictionaryInfoFromLocale(Locale locale) { - final String id = getMainDictId(locale); - final int version = -1; - final String description = SubtypeLocaleUtils - .getSubtypeLocaleDisplayName(locale.toString()); - return new DictionaryInfo(id, locale, description, null, 0L, 0L, version); - } - - private static void addOrUpdateDictInfo(final ArrayList<DictionaryInfo> dictList, - final DictionaryInfo newElement) { - final Iterator<DictionaryInfo> iter = dictList.iterator(); - while (iter.hasNext()) { - final DictionaryInfo thisDictInfo = iter.next(); - if (thisDictInfo.mLocale.equals(newElement.mLocale)) { - if (newElement.mVersion <= thisDictInfo.mVersion) { - return; - } - iter.remove(); - } - } - dictList.add(newElement); - } - - public static ArrayList<DictionaryInfo> getCurrentDictionaryFileNameAndVersionInfo( - final Context context) { - final ArrayList<DictionaryInfo> dictList = new ArrayList<>(); - - // Retrieve downloaded dictionaries from cached directories - final File[] directoryList = getCachedDirectoryList(context); - if (null != directoryList) { - for (final File directory : directoryList) { - final String localeString = getWordListIdFromFileName(directory.getName()); - final File[] dicts = BinaryDictionaryGetter.getCachedWordLists( - localeString, context); - for (final File dict : dicts) { - final String wordListId = getWordListIdFromFileName(dict.getName()); - if (!DictionaryInfoUtils.isMainWordListId(wordListId)) { - continue; - } - final Locale locale = LocaleUtils.constructLocaleFromString(localeString); - final AssetFileAddress fileAddress = AssetFileAddress.makeFromFile(dict); - final DictionaryInfo dictionaryInfo = - createDictionaryInfoFromFileAddress(fileAddress, locale); - // Protect against cases of a less-specific dictionary being found, like an - // en dictionary being used for an en_US locale. In this case, the en dictionary - // should be used for en_US but discounted for listing purposes. - if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) { - continue; - } - addOrUpdateDictInfo(dictList, dictionaryInfo); - } - } - } - - // Retrieve downloaded dictionaries from the unused dictionaries. - File[] unusedDictionaryList = getUnusedDictionaryList(context); - if (unusedDictionaryList != null) { - for (File dictionaryFile : unusedDictionaryList) { - String fileName = dictionaryFile.getName(); - int index = fileName.indexOf(TEMP_DICT_FILE_SUB); - if (index == -1) { - continue; - } - String locale = fileName.substring(0, index); - DictionaryInfo dictionaryInfo = createDictionaryInfoForUnCachedFile( - AssetFileAddress.makeFromFile(dictionaryFile), - LocaleUtils.constructLocaleFromString(locale)); - if (dictionaryInfo != null) { - addOrUpdateDictInfo(dictList, dictionaryInfo); - } - } - } - - // Retrieve files from assets - final Resources resources = context.getResources(); - final AssetManager assets = resources.getAssets(); - for (final String localeString : assets.getLocales()) { - final Locale locale = LocaleUtils.constructLocaleFromString(localeString); - final int resourceId = - DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale( - context.getResources(), locale); - if (0 == resourceId) { - continue; - } - final AssetFileAddress fileAddress = - BinaryDictionaryGetter.loadFallbackResource(context, resourceId); - final DictionaryInfo dictionaryInfo = createDictionaryInfoFromFileAddress(fileAddress, - locale); - // Protect against cases of a less-specific dictionary being found, like an - // en dictionary being used for an en_US locale. In this case, the en dictionary - // should be used for en_US but discounted for listing purposes. - // TODO: Remove dictionaryInfo == null when the static LMs have the headers. - if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) { - continue; - } - addOrUpdateDictInfo(dictList, dictionaryInfo); - } - - // Generate the dictionary information from the enabled subtypes. This will not - // overwrite the real records. - RichInputMethodManager.init(context); - List<InputMethodSubtype> enabledSubtypes = RichInputMethodManager - .getInstance().getMyEnabledInputMethodSubtypeList(true); - for (InputMethodSubtype subtype : enabledSubtypes) { - Locale locale = LocaleUtils.constructLocaleFromString(subtype.getLocale()); - DictionaryInfo dictionaryInfo = createDictionaryInfoFromLocale(locale); - addOrUpdateDictInfo(dictList, dictionaryInfo); - } - - return dictList; - } - - @UsedForTesting - public static boolean looksValidForDictionaryInsertion(final CharSequence text, - final SpacingAndPunctuations spacingAndPunctuations) { - if (TextUtils.isEmpty(text)) { - return false; - } - final int length = text.length(); - if (length > DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH) { - return false; - } - int i = 0; - int digitCount = 0; - while (i < length) { - final int codePoint = Character.codePointAt(text, i); - final int charCount = Character.charCount(codePoint); - i += charCount; - if (Character.isDigit(codePoint)) { - // Count digits: see below - digitCount += charCount; - continue; - } - if (!spacingAndPunctuations.isWordCodePoint(codePoint)) { - return false; - } - } - // We reject strings entirely comprised of digits to avoid using PIN codes or credit - // card numbers. It would come in handy for word prediction though; a good example is - // when writing one's address where the street number is usually quite discriminative, - // as well as the postal code. - return digitCount < length; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java deleted file mode 100644 index 8ce6eff92..000000000 --- a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import android.util.Log; - -import com.android.inputmethod.annotations.UsedForTesting; - -import java.lang.Thread.UncaughtExceptionHandler; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -/** - * Utilities to manage executors. - */ -public class ExecutorUtils { - - private static final String TAG = "ExecutorUtils"; - - public static final String KEYBOARD = "Keyboard"; - public static final String SPELLING = "Spelling"; - - private static ScheduledExecutorService sKeyboardExecutorService = newExecutorService(KEYBOARD); - private static ScheduledExecutorService sSpellingExecutorService = newExecutorService(SPELLING); - - private static ScheduledExecutorService newExecutorService(final String name) { - return Executors.newSingleThreadScheduledExecutor(new ExecutorFactory(name)); - } - - private static class ExecutorFactory implements ThreadFactory { - private final String mName; - - private ExecutorFactory(final String name) { - mName = name; - } - - @Override - public Thread newThread(final Runnable runnable) { - Thread thread = new Thread(runnable, TAG); - thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable ex) { - Log.w(mName + "-" + runnable.getClass().getSimpleName(), ex); - } - }); - return thread; - } - } - - @UsedForTesting - private static ScheduledExecutorService sExecutorServiceForTests; - - @UsedForTesting - public static void setExecutorServiceForTests( - final ScheduledExecutorService executorServiceForTests) { - sExecutorServiceForTests = executorServiceForTests; - } - - // - // Public methods used to schedule a runnable for execution. - // - - /** - * @param name Executor's name. - * @return scheduled executor service used to run background tasks - */ - public static ScheduledExecutorService getBackgroundExecutor(final String name) { - if (sExecutorServiceForTests != null) { - return sExecutorServiceForTests; - } - switch (name) { - case KEYBOARD: - return sKeyboardExecutorService; - case SPELLING: - return sSpellingExecutorService; - default: - throw new IllegalArgumentException("Invalid executor: " + name); - } - } - - public static void killTasks(final String name) { - final ScheduledExecutorService executorService = getBackgroundExecutor(name); - executorService.shutdownNow(); - try { - executorService.awaitTermination(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Log.wtf(TAG, "Failed to shut down: " + name); - } - if (executorService == sExecutorServiceForTests) { - // Don't do anything to the test service. - return; - } - switch (name) { - case KEYBOARD: - sKeyboardExecutorService = newExecutorService(KEYBOARD); - break; - case SPELLING: - sSpellingExecutorService = newExecutorService(SPELLING); - break; - default: - throw new IllegalArgumentException("Invalid executor: " + name); - } - } - - @UsedForTesting - public static Runnable chain(final Runnable... runnables) { - return new RunnableChain(runnables); - } - - @UsedForTesting - public static class RunnableChain implements Runnable { - private final Runnable[] mRunnables; - - private RunnableChain(final Runnable... runnables) { - if (runnables == null || runnables.length == 0) { - throw new IllegalArgumentException("Attempting to construct an empty chain"); - } - mRunnables = runnables; - } - - @UsedForTesting - public Runnable[] getRunnables() { - return mRunnables; - } - - @Override - public void run() { - for (Runnable runnable : mRunnables) { - if (Thread.interrupted()) { - return; - } - runnable.run(); - } - } - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java b/java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java deleted file mode 100644 index 67de8ba32..000000000 --- a/java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.content.Context; -import android.content.Intent; - -@SuppressWarnings("unused") -public class FeedbackUtils { - public static boolean isHelpAndFeedbackFormSupported() { - return false; - } - - public static void showHelpAndFeedbackForm(Context context) { - } - - public static int getAboutKeyboardTitleResId() { - return 0; - } - - public static Intent getAboutKeyboardIntent(Context context) { - return null; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/FileTransforms.java b/java/src/com/android/inputmethod/latin/utils/FileTransforms.java deleted file mode 100644 index 9f4584ec9..000000000 --- a/java/src/com/android/inputmethod/latin/utils/FileTransforms.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.utils; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.zip.GZIPInputStream; - -public final class FileTransforms { - public static OutputStream getCryptedStream(OutputStream out) { - // Crypt the stream. - return out; - } - - public static InputStream getDecryptedStream(InputStream in) { - // Decrypt the stream. - return in; - } - - public static InputStream getUncompressedStream(InputStream in) throws IOException { - return new GZIPInputStream(in); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java deleted file mode 100644 index 147e57b13..000000000 --- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import com.android.inputmethod.dictionarypack.DictionarySettingsFragment; -import com.android.inputmethod.latin.about.AboutPreferences; -import com.android.inputmethod.latin.settings.AccountsSettingsFragment; -import com.android.inputmethod.latin.settings.AdvancedSettingsFragment; -import com.android.inputmethod.latin.settings.AppearanceSettingsFragment; -import com.android.inputmethod.latin.settings.CorrectionSettingsFragment; -import com.android.inputmethod.latin.settings.CustomInputStyleSettingsFragment; -import com.android.inputmethod.latin.settings.DebugSettingsFragment; -import com.android.inputmethod.latin.settings.GestureSettingsFragment; -import com.android.inputmethod.latin.settings.PreferencesSettingsFragment; -import com.android.inputmethod.latin.settings.SettingsFragment; -import com.android.inputmethod.latin.settings.ThemeSettingsFragment; -import com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsFragment; -import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordFragment; -import com.android.inputmethod.latin.userdictionary.UserDictionaryList; -import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker; -import com.android.inputmethod.latin.userdictionary.UserDictionarySettings; - -import java.util.HashSet; - -public class FragmentUtils { - private static final HashSet<String> sLatinImeFragments = new HashSet<>(); - static { - sLatinImeFragments.add(DictionarySettingsFragment.class.getName()); - sLatinImeFragments.add(AboutPreferences.class.getName()); - sLatinImeFragments.add(PreferencesSettingsFragment.class.getName()); - sLatinImeFragments.add(AccountsSettingsFragment.class.getName()); - sLatinImeFragments.add(AppearanceSettingsFragment.class.getName()); - sLatinImeFragments.add(ThemeSettingsFragment.class.getName()); - sLatinImeFragments.add(CustomInputStyleSettingsFragment.class.getName()); - sLatinImeFragments.add(GestureSettingsFragment.class.getName()); - sLatinImeFragments.add(CorrectionSettingsFragment.class.getName()); - sLatinImeFragments.add(AdvancedSettingsFragment.class.getName()); - sLatinImeFragments.add(DebugSettingsFragment.class.getName()); - sLatinImeFragments.add(SettingsFragment.class.getName()); - sLatinImeFragments.add(SpellCheckerSettingsFragment.class.getName()); - sLatinImeFragments.add(UserDictionaryAddWordFragment.class.getName()); - sLatinImeFragments.add(UserDictionaryList.class.getName()); - sLatinImeFragments.add(UserDictionaryLocalePicker.class.getName()); - sLatinImeFragments.add(UserDictionarySettings.class.getName()); - } - - public static boolean isValidFragment(String fragmentName) { - return sLatinImeFragments.contains(fragmentName); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java deleted file mode 100644 index cea263b3b..000000000 --- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import android.Manifest; -import android.content.Context; -import android.content.SharedPreferences; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.permissions.PermissionsUtil; -import com.android.inputmethod.latin.settings.SettingsValues; - -import java.util.concurrent.TimeUnit; - -public final class ImportantNoticeUtils { - private static final String TAG = ImportantNoticeUtils.class.getSimpleName(); - - // {@link SharedPreferences} name to save the last important notice version that has been - // displayed to users. - private static final String PREFERENCE_NAME = "important_notice_pref"; - - private static final String KEY_SUGGEST_CONTACTS_NOTICE = "important_notice_suggest_contacts"; - - @UsedForTesting - static final String KEY_TIMESTAMP_OF_CONTACTS_NOTICE = "timestamp_of_suggest_contacts_notice"; - - @UsedForTesting - static final long TIMEOUT_OF_IMPORTANT_NOTICE = TimeUnit.HOURS.toMillis(23); - - // Copy of the hidden {@link Settings.Secure#USER_SETUP_COMPLETE} settings key. - // The value is zero until each multiuser completes system setup wizard. - // Caveat: This is a hidden API. - private static final String Settings_Secure_USER_SETUP_COMPLETE = "user_setup_complete"; - private static final int USER_SETUP_IS_NOT_COMPLETE = 0; - - private ImportantNoticeUtils() { - // This utility class is not publicly instantiable. - } - - @UsedForTesting - static boolean isInSystemSetupWizard(final Context context) { - try { - final int userSetupComplete = Settings.Secure.getInt( - context.getContentResolver(), Settings_Secure_USER_SETUP_COMPLETE); - return userSetupComplete == USER_SETUP_IS_NOT_COMPLETE; - } catch (final SettingNotFoundException e) { - Log.w(TAG, "Can't find settings in Settings.Secure: key=" - + Settings_Secure_USER_SETUP_COMPLETE); - return false; - } - } - - @UsedForTesting - static SharedPreferences getImportantNoticePreferences(final Context context) { - return context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - } - - @UsedForTesting - static boolean hasContactsNoticeShown(final Context context) { - return getImportantNoticePreferences(context).getBoolean( - KEY_SUGGEST_CONTACTS_NOTICE, false); - } - - public static boolean shouldShowImportantNotice(final Context context, - final SettingsValues settingsValues) { - // Check to see whether "Use Contacts" is enabled by the user. - if (!settingsValues.mUseContactsDict) { - return false; - } - - if (hasContactsNoticeShown(context)) { - return false; - } - - // Don't show the dialog if we have all the permissions. - if (PermissionsUtil.checkAllPermissionsGranted( - context, Manifest.permission.READ_CONTACTS)) { - return false; - } - - final String importantNoticeTitle = getSuggestContactsNoticeTitle(context); - if (TextUtils.isEmpty(importantNoticeTitle)) { - return false; - } - if (isInSystemSetupWizard(context)) { - return false; - } - if (hasContactsNoticeTimeoutPassed(context, System.currentTimeMillis())) { - updateContactsNoticeShown(context); - return false; - } - return true; - } - - public static String getSuggestContactsNoticeTitle(final Context context) { - return context.getResources().getString(R.string.important_notice_suggest_contact_names); - } - - @UsedForTesting - static boolean hasContactsNoticeTimeoutPassed( - final Context context, final long currentTimeInMillis) { - final SharedPreferences prefs = getImportantNoticePreferences(context); - if (!prefs.contains(KEY_TIMESTAMP_OF_CONTACTS_NOTICE)) { - prefs.edit() - .putLong(KEY_TIMESTAMP_OF_CONTACTS_NOTICE, currentTimeInMillis) - .apply(); - } - final long firstDisplayTimeInMillis = prefs.getLong( - KEY_TIMESTAMP_OF_CONTACTS_NOTICE, currentTimeInMillis); - final long elapsedTime = currentTimeInMillis - firstDisplayTimeInMillis; - return elapsedTime >= TIMEOUT_OF_IMPORTANT_NOTICE; - } - - public static void updateContactsNoticeShown(final Context context) { - getImportantNoticePreferences(context) - .edit() - .putBoolean(KEY_SUGGEST_CONTACTS_NOTICE, true) - .remove(KEY_TIMESTAMP_OF_CONTACTS_NOTICE) - .apply(); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java deleted file mode 100644 index 19cd34011..000000000 --- a/java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.utils; - -import android.text.InputType; -import android.view.inputmethod.EditorInfo; - -public final class InputTypeUtils implements InputType { - private static final int WEB_TEXT_PASSWORD_INPUT_TYPE = - TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD; - private static final int WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE = - TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; - private static final int NUMBER_PASSWORD_INPUT_TYPE = - TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_PASSWORD; - private static final int TEXT_PASSWORD_INPUT_TYPE = - TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD; - private static final int TEXT_VISIBLE_PASSWORD_INPUT_TYPE = - TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; - private static final int[] SUPPRESSING_AUTO_SPACES_FIELD_VARIATION = { - InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS, - InputType.TYPE_TEXT_VARIATION_PASSWORD, - InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD, - InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD }; - public static final int IME_ACTION_CUSTOM_LABEL = EditorInfo.IME_MASK_ACTION + 1; - - private InputTypeUtils() { - // This utility class is not publicly instantiable. - } - - private static boolean isWebEditTextInputType(final int inputType) { - return inputType == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); - } - - private static boolean isWebPasswordInputType(final int inputType) { - return WEB_TEXT_PASSWORD_INPUT_TYPE != 0 - && inputType == WEB_TEXT_PASSWORD_INPUT_TYPE; - } - - private static boolean isWebEmailAddressInputType(final int inputType) { - return WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE != 0 - && inputType == WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE; - } - - private static boolean isNumberPasswordInputType(final int inputType) { - return NUMBER_PASSWORD_INPUT_TYPE != 0 - && inputType == NUMBER_PASSWORD_INPUT_TYPE; - } - - private static boolean isTextPasswordInputType(final int inputType) { - return inputType == TEXT_PASSWORD_INPUT_TYPE; - } - - private static boolean isWebEmailAddressVariation(int variation) { - return variation == TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; - } - - public static boolean isEmailVariation(final int variation) { - return variation == TYPE_TEXT_VARIATION_EMAIL_ADDRESS - || isWebEmailAddressVariation(variation); - } - - public static boolean isWebInputType(final int inputType) { - final int maskedInputType = - inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); - return isWebEditTextInputType(maskedInputType) || isWebPasswordInputType(maskedInputType) - || isWebEmailAddressInputType(maskedInputType); - } - - // Please refer to TextView.isPasswordInputType - public static boolean isPasswordInputType(final int inputType) { - final int maskedInputType = - inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); - return isTextPasswordInputType(maskedInputType) || isWebPasswordInputType(maskedInputType) - || isNumberPasswordInputType(maskedInputType); - } - - // Please refer to TextView.isVisiblePasswordInputType - public static boolean isVisiblePasswordInputType(final int inputType) { - final int maskedInputType = - inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); - return maskedInputType == TEXT_VISIBLE_PASSWORD_INPUT_TYPE; - } - - public static boolean isAutoSpaceFriendlyType(final int inputType) { - if (TYPE_CLASS_TEXT != (TYPE_MASK_CLASS & inputType)) return false; - final int variation = TYPE_MASK_VARIATION & inputType; - for (final int fieldVariation : SUPPRESSING_AUTO_SPACES_FIELD_VARIATION) { - if (variation == fieldVariation) return false; - } - return true; - } - - public static int getImeOptionsActionIdFromEditorInfo(final EditorInfo editorInfo) { - if ((editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { - return EditorInfo.IME_ACTION_NONE; - } else if (editorInfo.actionLabel != null) { - return IME_ACTION_CUSTOM_LABEL; - } else { - // Note: this is different from editorInfo.actionId, hence "ImeOptionsActionId" - return editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION; - } - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/IntentUtils.java b/java/src/com/android/inputmethod/latin/utils/IntentUtils.java deleted file mode 100644 index ea0168117..000000000 --- a/java/src/com/android/inputmethod/latin/utils/IntentUtils.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.content.Intent; -import android.text.TextUtils; - -public final class IntentUtils { - private static final String EXTRA_INPUT_METHOD_ID = "input_method_id"; - // TODO: Can these be constants instead of literal String constants? - private static final String INPUT_METHOD_SUBTYPE_SETTINGS = - "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS"; - - private IntentUtils() { - // This utility class is not publicly instantiable. - } - - public static Intent getInputLanguageSelectionIntent(final String inputMethodId, - final int flagsForSubtypeSettings) { - // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS - final String action = INPUT_METHOD_SUBTYPE_SETTINGS; - final Intent intent = new Intent(action); - if (!TextUtils.isEmpty(inputMethodId)) { - intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId); - } - if (flagsForSubtypeSettings > 0) { - intent.setFlags(flagsForSubtypeSettings); - } - return intent; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/JniUtils.java b/java/src/com/android/inputmethod/latin/utils/JniUtils.java deleted file mode 100644 index e7fdafaeb..000000000 --- a/java/src/com/android/inputmethod/latin/utils/JniUtils.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.utils; - -import android.util.Log; - -import com.android.inputmethod.latin.define.JniLibName; - -public final class JniUtils { - private static final String TAG = JniUtils.class.getSimpleName(); - - static { - try { - System.loadLibrary(JniLibName.JNI_LIB_NAME); - } catch (UnsatisfiedLinkError ule) { - Log.e(TAG, "Could not load native library " + JniLibName.JNI_LIB_NAME, ule); - } - } - - private JniUtils() { - // This utility class is not publicly instantiable. - } - - public static void loadNativeLibrary() { - // Ensures the static initializer is called - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java deleted file mode 100644 index 6dd8d9711..000000000 --- a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.util.JsonReader; -import android.util.JsonWriter; -import android.util.Log; - -import java.io.Closeable; -import java.io.IOException; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public final class JsonUtils { - private static final String TAG = JsonUtils.class.getSimpleName(); - - private static final String INTEGER_CLASS_NAME = Integer.class.getSimpleName(); - private static final String STRING_CLASS_NAME = String.class.getSimpleName(); - - private static final String EMPTY_STRING = ""; - - public static List<Object> jsonStrToList(final String s) { - final ArrayList<Object> list = new ArrayList<>(); - final JsonReader reader = new JsonReader(new StringReader(s)); - try { - reader.beginArray(); - while (reader.hasNext()) { - reader.beginObject(); - while (reader.hasNext()) { - final String name = reader.nextName(); - if (name.equals(INTEGER_CLASS_NAME)) { - list.add(reader.nextInt()); - } else if (name.equals(STRING_CLASS_NAME)) { - list.add(reader.nextString()); - } else { - Log.w(TAG, "Invalid name: " + name); - reader.skipValue(); - } - } - reader.endObject(); - } - reader.endArray(); - return list; - } catch (final IOException e) { - } finally { - close(reader); - } - return Collections.<Object>emptyList(); - } - - public static String listToJsonStr(final List<Object> list) { - if (list == null || list.isEmpty()) { - return EMPTY_STRING; - } - final StringWriter sw = new StringWriter(); - final JsonWriter writer = new JsonWriter(sw); - try { - writer.beginArray(); - for (final Object o : list) { - writer.beginObject(); - if (o instanceof Integer) { - writer.name(INTEGER_CLASS_NAME).value((Integer)o); - } else if (o instanceof String) { - writer.name(STRING_CLASS_NAME).value((String)o); - } - writer.endObject(); - } - writer.endArray(); - return sw.toString(); - } catch (final IOException e) { - } finally { - close(writer); - } - return EMPTY_STRING; - } - - private static void close(final Closeable closeable) { - try { - if (closeable != null) { - closeable.close(); - } - } catch (final IOException e) { - // Ignore - } - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageOnSpacebarUtils.java b/java/src/com/android/inputmethod/latin/utils/LanguageOnSpacebarUtils.java deleted file mode 100644 index a5a1ea921..000000000 --- a/java/src/com/android/inputmethod/latin/utils/LanguageOnSpacebarUtils.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.latin.RichInputMethodSubtype; - -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -import javax.annotation.Nonnull; - -/** - * This class determines that the language name on the spacebar should be displayed in what format. - */ -public final class LanguageOnSpacebarUtils { - public static final int FORMAT_TYPE_NONE = 0; - public static final int FORMAT_TYPE_LANGUAGE_ONLY = 1; - public static final int FORMAT_TYPE_FULL_LOCALE = 2; - - private static List<InputMethodSubtype> sEnabledSubtypes = Collections.emptyList(); - private static boolean sIsSystemLanguageSameAsInputLanguage; - - private LanguageOnSpacebarUtils() { - // This utility class is not publicly instantiable. - } - - public static int getLanguageOnSpacebarFormatType( - @Nonnull final RichInputMethodSubtype subtype) { - if (subtype.isNoLanguage()) { - return FORMAT_TYPE_FULL_LOCALE; - } - // Only this subtype is enabled and equals to the system locale. - if (sEnabledSubtypes.size() < 2 && sIsSystemLanguageSameAsInputLanguage) { - return FORMAT_TYPE_NONE; - } - final Locale locale = subtype.getLocale(); - if (locale == null) { - return FORMAT_TYPE_NONE; - } - final String keyboardLanguage = locale.getLanguage(); - final String keyboardLayout = subtype.getKeyboardLayoutSetName(); - int sameLanguageAndLayoutCount = 0; - for (final InputMethodSubtype ims : sEnabledSubtypes) { - final String language = SubtypeLocaleUtils.getSubtypeLocale(ims).getLanguage(); - if (keyboardLanguage.equals(language) && keyboardLayout.equals( - SubtypeLocaleUtils.getKeyboardLayoutSetName(ims))) { - sameLanguageAndLayoutCount++; - } - } - // Display full locale name only when there are multiple subtypes that have the same - // locale and keyboard layout. Otherwise displaying language name is enough. - return sameLanguageAndLayoutCount > 1 ? FORMAT_TYPE_FULL_LOCALE - : FORMAT_TYPE_LANGUAGE_ONLY; - } - - public static void setEnabledSubtypes(@Nonnull final List<InputMethodSubtype> enabledSubtypes) { - sEnabledSubtypes = enabledSubtypes; - } - - public static void onSubtypeChanged(@Nonnull final RichInputMethodSubtype subtype, - final boolean implicitlyEnabledSubtype, @Nonnull final Locale systemLocale) { - final Locale newLocale = subtype.getLocale(); - if (systemLocale.equals(newLocale)) { - sIsSystemLanguageSameAsInputLanguage = true; - return; - } - if (!systemLocale.getLanguage().equals(newLocale.getLanguage())) { - sIsSystemLanguageSameAsInputLanguage = false; - return; - } - // If the subtype is enabled explicitly, the language name should be displayed even when - // the keyboard language and the system language are equal. - sIsSystemLanguageSameAsInputLanguage = implicitlyEnabledSubtype; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java deleted file mode 100644 index 9a5be99b3..000000000 --- a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.utils; - -import android.os.Handler; -import android.os.Looper; - -import java.lang.ref.WeakReference; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class LeakGuardHandlerWrapper<T> extends Handler { - private final WeakReference<T> mOwnerInstanceRef; - - public LeakGuardHandlerWrapper(@Nonnull final T ownerInstance) { - this(ownerInstance, Looper.myLooper()); - } - - public LeakGuardHandlerWrapper(@Nonnull final T ownerInstance, final Looper looper) { - super(looper); - mOwnerInstanceRef = new WeakReference<>(ownerInstance); - } - - @Nullable - public T getOwnerInstance() { - return mOwnerInstanceRef.get(); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/ManagedProfileUtils.java b/java/src/com/android/inputmethod/latin/utils/ManagedProfileUtils.java deleted file mode 100644 index ef1872bf4..000000000 --- a/java/src/com/android/inputmethod/latin/utils/ManagedProfileUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import android.content.Context; - -import com.android.inputmethod.annotations.UsedForTesting; - -public class ManagedProfileUtils { - private static ManagedProfileUtils INSTANCE = new ManagedProfileUtils(); - private static ManagedProfileUtils sTestInstance; - - private ManagedProfileUtils() { - // This utility class is not publicly instantiable. - } - - @UsedForTesting - public static void setTestInstance(final ManagedProfileUtils testInstance) { - sTestInstance = testInstance; - } - - public static ManagedProfileUtils getInstance() { - return sTestInstance == null ? INSTANCE : sTestInstance; - } - - public boolean hasWorkProfile(final Context context) { - return false; - } -}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java b/java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java deleted file mode 100644 index 97fb17de3..000000000 --- a/java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import com.android.inputmethod.latin.R; - -import android.content.Context; - -/** - * Helper class to get the metadata URI and the additional ID. - */ -@SuppressWarnings("unused") -public class MetadataFileUriGetter { - private MetadataFileUriGetter() { - // This helper class is not instantiable. - } - - public static String getMetadataUri(final Context context) { - return context.getString(R.string.dictionary_pack_metadata_uri); - } - - public static String getMetadataAdditionalId(final Context context) { - return ""; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java b/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java deleted file mode 100644 index c05ffd693..000000000 --- a/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import com.android.inputmethod.latin.NgramContext; -import com.android.inputmethod.latin.NgramContext.WordInfo; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; -import com.android.inputmethod.latin.settings.SpacingAndPunctuations; - -import java.util.Arrays; -import java.util.regex.Pattern; - -import javax.annotation.Nonnull; - -public final class NgramContextUtils { - private NgramContextUtils() { - // Intentional empty constructor for utility class. - } - - private static final Pattern NEWLINE_REGEX = Pattern.compile("[\\r\\n]+"); - private static final Pattern SPACE_REGEX = Pattern.compile("\\s+"); - // Get context information from nth word before the cursor. n = 1 retrieves the words - // immediately before the cursor, n = 2 retrieves the words before that, and so on. This splits - // on whitespace only. - // Also, it won't return words that end in a separator (if the nth word before the cursor - // ends in a separator, it returns information representing beginning-of-sentence). - // Example (when Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM is 2): - // (n = 1) "abc def|" -> abc, def - // (n = 1) "abc def |" -> abc, def - // (n = 1) "abc 'def|" -> empty, 'def - // (n = 1) "abc def. |" -> beginning-of-sentence - // (n = 1) "abc def . |" -> beginning-of-sentence - // (n = 2) "abc def|" -> beginning-of-sentence, abc - // (n = 2) "abc def |" -> beginning-of-sentence, abc - // (n = 2) "abc 'def|" -> empty. The context is different from "abc def", but we cannot - // represent this situation using NgramContext. See TODO in the method. - // TODO: The next example's result should be "abc, def". This have to be fixed before we - // retrieve the prior context of Beginning-of-Sentence. - // (n = 2) "abc def. |" -> beginning-of-sentence, abc - // (n = 2) "abc def . |" -> abc, def - // (n = 2) "abc|" -> beginning-of-sentence - // (n = 2) "abc |" -> beginning-of-sentence - // (n = 2) "abc. def|" -> beginning-of-sentence - @Nonnull - public static NgramContext getNgramContextFromNthPreviousWord(final CharSequence prev, - final SpacingAndPunctuations spacingAndPunctuations, final int n) { - if (prev == null) return NgramContext.EMPTY_PREV_WORDS_INFO; - final String[] lines = NEWLINE_REGEX.split(prev); - if (lines.length == 0) { - return new NgramContext(WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO); - } - final String[] w = SPACE_REGEX.split(lines[lines.length - 1]); - final WordInfo[] prevWordsInfo = - new WordInfo[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; - Arrays.fill(prevWordsInfo, WordInfo.EMPTY_WORD_INFO); - for (int i = 0; i < prevWordsInfo.length; i++) { - final int focusedWordIndex = w.length - n - i; - // Referring to the word after the focused word. - if ((focusedWordIndex + 1) >= 0 && (focusedWordIndex + 1) < w.length) { - final String wordFollowingTheNthPrevWord = w[focusedWordIndex + 1]; - if (!wordFollowingTheNthPrevWord.isEmpty()) { - final char firstChar = wordFollowingTheNthPrevWord.charAt(0); - if (spacingAndPunctuations.isWordConnector(firstChar)) { - // The word following the focused word is starting with a word connector. - // TODO: Return meaningful context for this case. - break; - } - } - } - // If we can't find (n + i) words, the context is beginning-of-sentence. - if (focusedWordIndex < 0) { - prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO; - break; - } - - final String focusedWord = w[focusedWordIndex]; - // If the word is empty, the context is beginning-of-sentence. - final int length = focusedWord.length(); - if (length <= 0) { - prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO; - break; - } - // If the word ends in a sentence terminator, the context is beginning-of-sentence. - final char lastChar = focusedWord.charAt(length - 1); - if (spacingAndPunctuations.isSentenceTerminator(lastChar)) { - prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO; - break; - } - // If ends in a word separator or connector, the context is unclear. - // TODO: Return meaningful context for this case. - if (spacingAndPunctuations.isWordSeparator(lastChar) - || spacingAndPunctuations.isWordConnector(lastChar)) { - break; - } - prevWordsInfo[i] = new WordInfo(focusedWord); - } - return new NgramContext(prevWordsInfo); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java deleted file mode 100644 index 737e33228..000000000 --- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import com.android.inputmethod.latin.common.StringUtils; - -import java.util.Locale; - -/** - * The status of the current recapitalize process. - */ -public class RecapitalizeStatus { - public static final int NOT_A_RECAPITALIZE_MODE = -1; - public static final int CAPS_MODE_ORIGINAL_MIXED_CASE = 0; - public static final int CAPS_MODE_ALL_LOWER = 1; - public static final int CAPS_MODE_FIRST_WORD_UPPER = 2; - public static final int CAPS_MODE_ALL_UPPER = 3; - // When adding a new mode, don't forget to update the CAPS_MODE_LAST constant. - public static final int CAPS_MODE_LAST = CAPS_MODE_ALL_UPPER; - - private static final int[] ROTATION_STYLE = { - CAPS_MODE_ORIGINAL_MIXED_CASE, - CAPS_MODE_ALL_LOWER, - CAPS_MODE_FIRST_WORD_UPPER, - CAPS_MODE_ALL_UPPER - }; - - private static final int getStringMode(final String string, final int[] sortedSeparators) { - if (StringUtils.isIdenticalAfterUpcase(string)) { - return CAPS_MODE_ALL_UPPER; - } else if (StringUtils.isIdenticalAfterDowncase(string)) { - return CAPS_MODE_ALL_LOWER; - } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, sortedSeparators)) { - return CAPS_MODE_FIRST_WORD_UPPER; - } else { - return CAPS_MODE_ORIGINAL_MIXED_CASE; - } - } - - public static String modeToString(final int recapitalizeMode) { - switch (recapitalizeMode) { - case NOT_A_RECAPITALIZE_MODE: return "undefined"; - case CAPS_MODE_ORIGINAL_MIXED_CASE: return "mixedCase"; - case CAPS_MODE_ALL_LOWER: return "allLower"; - case CAPS_MODE_FIRST_WORD_UPPER: return "firstWordUpper"; - case CAPS_MODE_ALL_UPPER: return "allUpper"; - default: return "unknown<" + recapitalizeMode + ">"; - } - } - - /** - * We store the location of the cursor and the string that was there before the recapitalize - * action was done, and the location of the cursor and the string that was there after. - */ - private int mCursorStartBefore; - private String mStringBefore; - private int mCursorStartAfter; - private int mCursorEndAfter; - private int mRotationStyleCurrentIndex; - private boolean mSkipOriginalMixedCaseMode; - private Locale mLocale; - private int[] mSortedSeparators; - private String mStringAfter; - private boolean mIsStarted; - private boolean mIsEnabled = true; - - private static final int[] EMPTY_STORTED_SEPARATORS = {}; - - public RecapitalizeStatus() { - // By default, initialize with fake values that won't match any real recapitalize. - start(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS); - stop(); - } - - public void start(final int cursorStart, final int cursorEnd, final String string, - final Locale locale, final int[] sortedSeparators) { - if (!mIsEnabled) { - return; - } - mCursorStartBefore = cursorStart; - mStringBefore = string; - mCursorStartAfter = cursorStart; - mCursorEndAfter = cursorEnd; - mStringAfter = string; - final int initialMode = getStringMode(mStringBefore, sortedSeparators); - mLocale = locale; - mSortedSeparators = sortedSeparators; - if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) { - mRotationStyleCurrentIndex = 0; - mSkipOriginalMixedCaseMode = false; - } else { - // Find the current mode in the array. - int currentMode; - for (currentMode = ROTATION_STYLE.length - 1; currentMode > 0; --currentMode) { - if (ROTATION_STYLE[currentMode] == initialMode) { - break; - } - } - mRotationStyleCurrentIndex = currentMode; - mSkipOriginalMixedCaseMode = true; - } - mIsStarted = true; - } - - public void stop() { - mIsStarted = false; - } - - public boolean isStarted() { - return mIsStarted; - } - - public void enable() { - mIsEnabled = true; - } - - public void disable() { - mIsEnabled = false; - } - - public boolean mIsEnabled() { - return mIsEnabled; - } - - public boolean isSetAt(final int cursorStart, final int cursorEnd) { - return cursorStart == mCursorStartAfter && cursorEnd == mCursorEndAfter; - } - - /** - * Rotate through the different possible capitalization modes. - */ - public void rotate() { - final String oldResult = mStringAfter; - int count = 0; // Protection against infinite loop. - do { - mRotationStyleCurrentIndex = (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length; - if (CAPS_MODE_ORIGINAL_MIXED_CASE == ROTATION_STYLE[mRotationStyleCurrentIndex] - && mSkipOriginalMixedCaseMode) { - mRotationStyleCurrentIndex = - (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length; - } - ++count; - switch (ROTATION_STYLE[mRotationStyleCurrentIndex]) { - case CAPS_MODE_ORIGINAL_MIXED_CASE: - mStringAfter = mStringBefore; - break; - case CAPS_MODE_ALL_LOWER: - mStringAfter = mStringBefore.toLowerCase(mLocale); - break; - case CAPS_MODE_FIRST_WORD_UPPER: - mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSortedSeparators, - mLocale); - break; - case CAPS_MODE_ALL_UPPER: - mStringAfter = mStringBefore.toUpperCase(mLocale); - break; - default: - mStringAfter = mStringBefore; - } - } while (mStringAfter.equals(oldResult) && count < ROTATION_STYLE.length + 1); - mCursorEndAfter = mCursorStartAfter + mStringAfter.length(); - } - - /** - * Remove leading/trailing whitespace from the considered string. - */ - public void trim() { - final int len = mStringBefore.length(); - int nonWhitespaceStart = 0; - for (; nonWhitespaceStart < len; - nonWhitespaceStart = mStringBefore.offsetByCodePoints(nonWhitespaceStart, 1)) { - final int codePoint = mStringBefore.codePointAt(nonWhitespaceStart); - if (!Character.isWhitespace(codePoint)) break; - } - int nonWhitespaceEnd = len; - for (; nonWhitespaceEnd > 0; - nonWhitespaceEnd = mStringBefore.offsetByCodePoints(nonWhitespaceEnd, -1)) { - final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd); - if (!Character.isWhitespace(codePoint)) break; - } - // If nonWhitespaceStart >= nonWhitespaceEnd, that means the selection contained only - // whitespace, so we leave it as is. - if ((0 != nonWhitespaceStart || len != nonWhitespaceEnd) - && nonWhitespaceStart < nonWhitespaceEnd) { - mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd; - mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart; - mStringAfter = mStringBefore = - mStringBefore.substring(nonWhitespaceStart, nonWhitespaceEnd); - } - } - - public String getRecapitalizedString() { - return mStringAfter; - } - - public int getNewCursorStart() { - return mCursorStartAfter; - } - - public int getNewCursorEnd() { - return mCursorEndAfter; - } - - public int getCurrentMode() { - return ROTATION_STYLE[mRotationStyleCurrentIndex]; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java deleted file mode 100644 index 37b34751a..000000000 --- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.utils; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Insets; -import android.os.Build; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.TypedValue; -import android.view.Window; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.WindowMetrics; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.settings.SettingsValues; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.regex.PatternSyntaxException; - -public final class ResourceUtils { - private static final String TAG = ResourceUtils.class.getSimpleName(); - - public static final float UNDEFINED_RATIO = -1.0f; - public static final int UNDEFINED_DIMENSION = -1; - - private ResourceUtils() { - // This utility class is not publicly instantiable. - } - - private static final HashMap<String, String> sDeviceOverrideValueMap = new HashMap<>(); - - private static final String[] BUILD_KEYS_AND_VALUES = { - "HARDWARE", Build.HARDWARE, - "MODEL", Build.MODEL, - "BRAND", Build.BRAND, - "MANUFACTURER", Build.MANUFACTURER - }; - private static final HashMap<String, String> sBuildKeyValues; - private static final String sBuildKeyValuesDebugString; - - static { - sBuildKeyValues = new HashMap<>(); - final ArrayList<String> keyValuePairs = new ArrayList<>(); - final int keyCount = BUILD_KEYS_AND_VALUES.length / 2; - for (int i = 0; i < keyCount; i++) { - final int index = i * 2; - final String key = BUILD_KEYS_AND_VALUES[index]; - final String value = BUILD_KEYS_AND_VALUES[index + 1]; - sBuildKeyValues.put(key, value); - keyValuePairs.add(key + '=' + value); - } - sBuildKeyValuesDebugString = "[" + TextUtils.join(" ", keyValuePairs) + "]"; - } - - public static String getDeviceOverrideValue(final Resources res, final int overrideResId, - final String defaultValue) { - final int orientation = res.getConfiguration().orientation; - final String key = overrideResId + "-" + orientation; - if (sDeviceOverrideValueMap.containsKey(key)) { - return sDeviceOverrideValueMap.get(key); - } - - final String[] overrideArray = res.getStringArray(overrideResId); - final String overrideValue = findConstantForKeyValuePairs(sBuildKeyValues, overrideArray); - // The overrideValue might be an empty string. - if (overrideValue != null) { - Log.i(TAG, "Find override value:" - + " resource="+ res.getResourceEntryName(overrideResId) - + " build=" + sBuildKeyValuesDebugString - + " override=" + overrideValue); - sDeviceOverrideValueMap.put(key, overrideValue); - return overrideValue; - } - - sDeviceOverrideValueMap.put(key, defaultValue); - return defaultValue; - } - - @SuppressWarnings("serial") - static class DeviceOverridePatternSyntaxError extends Exception { - public DeviceOverridePatternSyntaxError(final String message, final String expression) { - this(message, expression, null); - } - - public DeviceOverridePatternSyntaxError(final String message, final String expression, - final Throwable throwable) { - super(message + ": " + expression, throwable); - } - } - - /** - * Find the condition that fulfills specified key value pairs from an array of - * "condition,constant", and return the corresponding string constant. A condition is - * "pattern1[:pattern2...] (or an empty string for the default). A pattern is - * "key=regexp_value" string. The condition matches only if all patterns of the condition - * are true for the specified key value pairs. - * - * For example, "condition,constant" has the following format. - * - HARDWARE=mako,constantForNexus4 - * - MODEL=Nexus 4:MANUFACTURER=LGE,constantForNexus4 - * - ,defaultConstant - * - * @param keyValuePairs attributes to be used to look for a matched condition. - * @param conditionConstantArray an array of "condition,constant" elements to be searched. - * @return the constant part of the matched "condition,constant" element. Returns null if no - * condition matches. - * @see com.android.inputmethod.latin.utils.ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp() - */ - @UsedForTesting - static String findConstantForKeyValuePairs(final HashMap<String, String> keyValuePairs, - final String[] conditionConstantArray) { - if (conditionConstantArray == null || keyValuePairs == null) { - return null; - } - String foundValue = null; - for (final String conditionConstant : conditionConstantArray) { - final int posComma = conditionConstant.indexOf(','); - if (posComma < 0) { - Log.w(TAG, "Array element has no comma: " + conditionConstant); - continue; - } - final String condition = conditionConstant.substring(0, posComma); - if (condition.isEmpty()) { - Log.w(TAG, "Array element has no condition: " + conditionConstant); - continue; - } - try { - if (fulfillsCondition(keyValuePairs, condition)) { - // Take first match - if (foundValue == null) { - foundValue = conditionConstant.substring(posComma + 1); - } - // And continue walking through all conditions. - } - } catch (final DeviceOverridePatternSyntaxError e) { - Log.w(TAG, "Syntax error, ignored", e); - } - } - return foundValue; - } - - private static boolean fulfillsCondition(final HashMap<String,String> keyValuePairs, - final String condition) throws DeviceOverridePatternSyntaxError { - final String[] patterns = condition.split(":"); - // Check all patterns in a condition are true - boolean matchedAll = true; - for (final String pattern : patterns) { - final int posEqual = pattern.indexOf('='); - if (posEqual < 0) { - throw new DeviceOverridePatternSyntaxError("Pattern has no '='", condition); - } - final String key = pattern.substring(0, posEqual); - final String value = keyValuePairs.get(key); - if (value == null) { - throw new DeviceOverridePatternSyntaxError("Unknown key", condition); - } - final String patternRegexpValue = pattern.substring(posEqual + 1); - try { - if (!value.matches(patternRegexpValue)) { - matchedAll = false; - // And continue walking through all patterns. - } - } catch (final PatternSyntaxException e) { - throw new DeviceOverridePatternSyntaxError("Syntax error", condition, e); - } - } - return matchedAll; - } - - public static int getDefaultKeyboardWidth(final Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - // Since Android 15’s edge-to-edge enforcement, window insets should be considered. - final WindowManager wm = context.getSystemService(WindowManager.class); - final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); - final Insets insets = - windowMetrics - .getWindowInsets() - .getInsetsIgnoringVisibility( - WindowInsets.Type.systemBars() - | WindowInsets.Type.displayCutout()); - return windowMetrics.getBounds().width() - insets.left - insets.right; - } - final DisplayMetrics dm = context.getResources().getDisplayMetrics(); - return dm.widthPixels; - } - - public static int getKeyboardHeight(final Resources res, final SettingsValues settingsValues) { - final int defaultKeyboardHeight = getDefaultKeyboardHeight(res); - if (settingsValues.mHasKeyboardResize) { - // mKeyboardHeightScale Ranges from [.5,1.2], from xml/prefs_screen_debug.xml - return (int)(defaultKeyboardHeight * settingsValues.mKeyboardHeightScale); - } - return defaultKeyboardHeight; - } - - public static int getDefaultKeyboardHeight(final Resources res) { - final DisplayMetrics dm = res.getDisplayMetrics(); - final String keyboardHeightInDp = getDeviceOverrideValue( - res, R.array.keyboard_heights, null /* defaultValue */); - final float keyboardHeight; - if (TextUtils.isEmpty(keyboardHeightInDp)) { - keyboardHeight = res.getDimension(R.dimen.config_default_keyboard_height); - } else { - keyboardHeight = Float.parseFloat(keyboardHeightInDp) * dm.density; - } - final float maxKeyboardHeight = res.getFraction( - R.fraction.config_max_keyboard_height, dm.heightPixels, dm.heightPixels); - float minKeyboardHeight = res.getFraction( - R.fraction.config_min_keyboard_height, dm.heightPixels, dm.heightPixels); - if (minKeyboardHeight < 0.0f) { - // Specified fraction was negative, so it should be calculated against display - // width. - minKeyboardHeight = -res.getFraction( - R.fraction.config_min_keyboard_height, dm.widthPixels, dm.widthPixels); - } - // Keyboard height will not exceed maxKeyboardHeight and will not be less than - // minKeyboardHeight. - return (int)Math.max(Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight); - } - - public static boolean isValidFraction(final float fraction) { - return fraction >= 0.0f; - } - - // {@link Resources#getDimensionPixelSize(int)} returns at least one pixel size. - public static boolean isValidDimensionPixelSize(final int dimension) { - return dimension > 0; - } - - // {@link Resources#getDimensionPixelOffset(int)} may return zero pixel offset. - public static boolean isValidDimensionPixelOffset(final int dimension) { - return dimension >= 0; - } - - public static float getFloatFromFraction(final Resources res, final int fractionResId) { - return res.getFraction(fractionResId, 1, 1); - } - - public static float getFraction(final TypedArray a, final int index, final float defValue) { - final TypedValue value = a.peekValue(index); - if (value == null || !isFractionValue(value)) { - return defValue; - } - return a.getFraction(index, 1, 1, defValue); - } - - public static float getFraction(final TypedArray a, final int index) { - return getFraction(a, index, UNDEFINED_RATIO); - } - - public static int getDimensionPixelSize(final TypedArray a, final int index) { - final TypedValue value = a.peekValue(index); - if (value == null || !isDimensionValue(value)) { - return ResourceUtils.UNDEFINED_DIMENSION; - } - return a.getDimensionPixelSize(index, ResourceUtils.UNDEFINED_DIMENSION); - } - - public static float getDimensionOrFraction(final TypedArray a, final int index, final int base, - final float defValue) { - final TypedValue value = a.peekValue(index); - if (value == null) { - return defValue; - } - if (isFractionValue(value)) { - return a.getFraction(index, base, base, defValue); - } else if (isDimensionValue(value)) { - return a.getDimension(index, defValue); - } - return defValue; - } - - public static int getEnumValue(final TypedArray a, final int index, final int defValue) { - final TypedValue value = a.peekValue(index); - if (value == null) { - return defValue; - } - if (isIntegerValue(value)) { - return a.getInt(index, defValue); - } - return defValue; - } - - public static boolean isFractionValue(final TypedValue v) { - return v.type == TypedValue.TYPE_FRACTION; - } - - public static boolean isDimensionValue(final TypedValue v) { - return v.type == TypedValue.TYPE_DIMENSION; - } - - public static boolean isIntegerValue(final TypedValue v) { - return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT; - } - - public static boolean isStringValue(final TypedValue v) { - return v.type == TypedValue.TYPE_STRING; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/RunInLocale.java b/java/src/com/android/inputmethod/latin/utils/RunInLocale.java deleted file mode 100644 index 1ea16e6ef..000000000 --- a/java/src/com/android/inputmethod/latin/utils/RunInLocale.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.content.res.Configuration; -import android.content.res.Resources; - -import java.util.Locale; - -public abstract class RunInLocale<T> { - private static final Object sLockForRunInLocale = new Object(); - - protected abstract T job(final Resources res); - - /** - * Execute {@link #job(Resources)} method in specified system locale exclusively. - * - * @param res the resources to use. - * @param newLocale the locale to change to. Run in system locale if null. - * @return the value returned from {@link #job(Resources)}. - */ - public T runInLocale(final Resources res, final Locale newLocale) { - synchronized (sLockForRunInLocale) { - final Configuration conf = res.getConfiguration(); - if (newLocale == null || newLocale.equals(conf.locale)) { - return job(res); - } - final Locale savedLocale = conf.locale; - try { - conf.locale = newLocale; - res.updateConfiguration(conf, null); - return job(res); - } finally { - conf.locale = savedLocale; - res.updateConfiguration(conf, null); - } - } - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java b/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java deleted file mode 100644 index 537713091..000000000 --- a/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.utils; - -import java.util.Locale; -import java.util.TreeMap; - -/** - * A class to help with handling different writing scripts. - */ -public class ScriptUtils { - - // Used for hardware keyboards - public static final int SCRIPT_UNKNOWN = -1; - - public static final int SCRIPT_ARABIC = 0; - public static final int SCRIPT_ARMENIAN = 1; - public static final int SCRIPT_BENGALI = 2; - public static final int SCRIPT_CYRILLIC = 3; - public static final int SCRIPT_DEVANAGARI = 4; - public static final int SCRIPT_GEORGIAN = 5; - public static final int SCRIPT_GREEK = 6; - public static final int SCRIPT_HEBREW = 7; - public static final int SCRIPT_KANNADA = 8; - public static final int SCRIPT_KHMER = 9; - public static final int SCRIPT_LAO = 10; - public static final int SCRIPT_LATIN = 11; - public static final int SCRIPT_MALAYALAM = 12; - public static final int SCRIPT_MYANMAR = 13; - public static final int SCRIPT_SINHALA = 14; - public static final int SCRIPT_TAMIL = 15; - public static final int SCRIPT_TELUGU = 16; - public static final int SCRIPT_THAI = 17; - - private static final TreeMap<String, Integer> mLanguageCodeToScriptCode; - - static { - mLanguageCodeToScriptCode = new TreeMap<>(); - mLanguageCodeToScriptCode.put("", SCRIPT_LATIN); // default - mLanguageCodeToScriptCode.put("ar", SCRIPT_ARABIC); - mLanguageCodeToScriptCode.put("hy", SCRIPT_ARMENIAN); - mLanguageCodeToScriptCode.put("bn", SCRIPT_BENGALI); - mLanguageCodeToScriptCode.put("bg", SCRIPT_CYRILLIC); - mLanguageCodeToScriptCode.put("sr", SCRIPT_CYRILLIC); - mLanguageCodeToScriptCode.put("ru", SCRIPT_CYRILLIC); - mLanguageCodeToScriptCode.put("ka", SCRIPT_GEORGIAN); - mLanguageCodeToScriptCode.put("el", SCRIPT_GREEK); - mLanguageCodeToScriptCode.put("iw", SCRIPT_HEBREW); - mLanguageCodeToScriptCode.put("km", SCRIPT_KHMER); - mLanguageCodeToScriptCode.put("lo", SCRIPT_LAO); - mLanguageCodeToScriptCode.put("ml", SCRIPT_MALAYALAM); - mLanguageCodeToScriptCode.put("my", SCRIPT_MYANMAR); - mLanguageCodeToScriptCode.put("si", SCRIPT_SINHALA); - mLanguageCodeToScriptCode.put("ta", SCRIPT_TAMIL); - mLanguageCodeToScriptCode.put("te", SCRIPT_TELUGU); - mLanguageCodeToScriptCode.put("th", SCRIPT_THAI); - } - - /* - * Returns whether the code point is a letter that makes sense for the specified - * locale for this spell checker. - * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml - * and is limited to EFIGS languages and Russian. - * Hence at the moment this explicitly tests for Cyrillic characters or Latin characters - * as appropriate, and explicitly excludes CJK, Arabic and Hebrew characters. - */ - public static boolean isLetterPartOfScript(final int codePoint, final int scriptId) { - switch (scriptId) { - case SCRIPT_ARABIC: - // Arabic letters can be in any of the following blocks: - // Arabic U+0600..U+06FF - // Arabic Supplement, Thaana U+0750..U+077F, U+0780..U+07BF - // Arabic Extended-A U+08A0..U+08FF - // Arabic Presentation Forms-A U+FB50..U+FDFF - // Arabic Presentation Forms-B U+FE70..U+FEFF - return (codePoint >= 0x600 && codePoint <= 0x6FF) - || (codePoint >= 0x750 && codePoint <= 0x7BF) - || (codePoint >= 0x8A0 && codePoint <= 0x8FF) - || (codePoint >= 0xFB50 && codePoint <= 0xFDFF) - || (codePoint >= 0xFE70 && codePoint <= 0xFEFF); - case SCRIPT_ARMENIAN: - // Armenian letters are in the Armenian unicode block, U+0530..U+058F and - // Alphabetic Presentation Forms block, U+FB00..U+FB4F, but only in the Armenian part - // of that block, which is U+FB13..U+FB17. - return (codePoint >= 0x530 && codePoint <= 0x58F - || codePoint >= 0xFB13 && codePoint <= 0xFB17); - case SCRIPT_BENGALI: - // Bengali unicode block is U+0980..U+09FF - return (codePoint >= 0x980 && codePoint <= 0x9FF); - case SCRIPT_CYRILLIC: - // All Cyrillic characters are in the 400~52F block. There are some in the upper - // Unicode range, but they are archaic characters that are not used in modern - // Russian and are not used by our dictionary. - return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint); - case SCRIPT_DEVANAGARI: - // Devanagari unicode block is +0900..U+097F - return (codePoint >= 0x900 && codePoint <= 0x97F); - case SCRIPT_GEORGIAN: - // Georgian letters are in the Georgian unicode block, U+10A0..U+10FF, - // or Georgian supplement block, U+2D00..U+2D2F - return (codePoint >= 0x10A0 && codePoint <= 0x10FF - || codePoint >= 0x2D00 && codePoint <= 0x2D2F); - case SCRIPT_GREEK: - // Greek letters are either in the 370~3FF range (Greek & Coptic), or in the - // 1F00~1FFF range (Greek extended). Our dictionary contains both sort of characters. - // Our dictionary also contains a few words with 0xF2; it would be best to check - // if that's correct, but a web search does return results for these words so - // they are probably okay. - return (codePoint >= 0x370 && codePoint <= 0x3FF) - || (codePoint >= 0x1F00 && codePoint <= 0x1FFF) - || codePoint == 0xF2; - case SCRIPT_HEBREW: - // Hebrew letters are in the Hebrew unicode block, which spans from U+0590 to U+05FF, - // or in the Alphabetic Presentation Forms block, U+FB00..U+FB4F, but only in the - // Hebrew part of that block, which is U+FB1D..U+FB4F. - return (codePoint >= 0x590 && codePoint <= 0x5FF - || codePoint >= 0xFB1D && codePoint <= 0xFB4F); - case SCRIPT_KANNADA: - // Kannada unicode block is U+0C80..U+0CFF - return (codePoint >= 0xC80 && codePoint <= 0xCFF); - case SCRIPT_KHMER: - // Khmer letters are in unicode block U+1780..U+17FF, and the Khmer symbols block - // is U+19E0..U+19FF - return (codePoint >= 0x1780 && codePoint <= 0x17FF - || codePoint >= 0x19E0 && codePoint <= 0x19FF); - case SCRIPT_LAO: - // The Lao block is U+0E80..U+0EFF - return (codePoint >= 0xE80 && codePoint <= 0xEFF); - case SCRIPT_LATIN: - // Our supported latin script dictionaries (EFIGS) at the moment only include - // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode - // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF, - // so the below is a very efficient way to test for it. As for the 0-0x3F, it's - // excluded from isLetter anyway. - return codePoint <= 0x2AF && Character.isLetter(codePoint); - case SCRIPT_MALAYALAM: - // Malayalam unicode block is U+0D00..U+0D7F - return (codePoint >= 0xD00 && codePoint <= 0xD7F); - case SCRIPT_MYANMAR: - // Myanmar has three unicode blocks : - // Myanmar U+1000..U+109F - // Myanmar extended-A U+AA60..U+AA7F - // Myanmar extended-B U+A9E0..U+A9FF - return (codePoint >= 0x1000 && codePoint <= 0x109F - || codePoint >= 0xAA60 && codePoint <= 0xAA7F - || codePoint >= 0xA9E0 && codePoint <= 0xA9FF); - case SCRIPT_SINHALA: - // Sinhala unicode block is U+0D80..U+0DFF - return (codePoint >= 0xD80 && codePoint <= 0xDFF); - case SCRIPT_TAMIL: - // Tamil unicode block is U+0B80..U+0BFF - return (codePoint >= 0xB80 && codePoint <= 0xBFF); - case SCRIPT_TELUGU: - // Telugu unicode block is U+0C00..U+0C7F - return (codePoint >= 0xC00 && codePoint <= 0xC7F); - case SCRIPT_THAI: - // Thai unicode block is U+0E00..U+0E7F - return (codePoint >= 0xE00 && codePoint <= 0xE7F); - case SCRIPT_UNKNOWN: - return true; - default: - // Should never come here - throw new RuntimeException("Impossible value of script: " + scriptId); - } - } - - /** - * @param locale spell checker locale - * @return internal Latin IME script code that maps to a language code - * {@see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes} - */ - public static int getScriptFromSpellCheckerLocale(final Locale locale) { - String language = locale.getLanguage(); - Integer script = mLanguageCodeToScriptCode.get(language); - if (script == null) { - // Default to Latin. - script = mLanguageCodeToScriptCode.get(""); - } - return script; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java deleted file mode 100644 index c41817fe6..000000000 --- a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.SpannedString; -import android.text.TextUtils; -import android.text.style.SuggestionSpan; -import android.text.style.URLSpan; - -import com.android.inputmethod.annotations.UsedForTesting; - -import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class SpannableStringUtils { - /** - * Copies the spans from the region <code>start...end</code> in - * <code>source</code> to the region - * <code>destoff...destoff+end-start</code> in <code>dest</code>. - * Spans in <code>source</code> that begin before <code>start</code> - * or end after <code>end</code> but overlap this range are trimmed - * as if they began at <code>start</code> or ended at <code>end</code>. - * Only SuggestionSpans that don't have the SPAN_PARAGRAPH span are copied. - * - * This code is almost entirely taken from {@link TextUtils#copySpansFrom}, except for the - * kind of span that is copied. - * - * @throws IndexOutOfBoundsException if any of the copied spans - * are out of range in <code>dest</code>. - */ - public static void copyNonParagraphSuggestionSpansFrom(Spanned source, int start, int end, - Spannable dest, int destoff) { - Object[] spans = source.getSpans(start, end, SuggestionSpan.class); - - for (int i = 0; i < spans.length; i++) { - int fl = source.getSpanFlags(spans[i]); - // We don't care about the PARAGRAPH flag in LatinIME code. However, if this flag - // is set, Spannable#setSpan will throw an exception unless the span is on the edge - // of a word. But the spans have been split into two by the getText{Before,After}Cursor - // methods, so after concatenation they may end in the middle of a word. - // Since we don't use them, we can just remove them and avoid crashing. - fl &= ~Spanned.SPAN_PARAGRAPH; - - int st = source.getSpanStart(spans[i]); - int en = source.getSpanEnd(spans[i]); - - if (st < start) - st = start; - if (en > end) - en = end; - - dest.setSpan(spans[i], st - start + destoff, en - start + destoff, - fl); - } - } - - /** - * Returns a CharSequence concatenating the specified CharSequences, retaining their - * SuggestionSpans that don't have the PARAGRAPH flag, but not other spans. - * - * This code is almost entirely taken from {@link TextUtils#concat(CharSequence...)}, except - * it calls copyNonParagraphSuggestionSpansFrom instead of {@link TextUtils#copySpansFrom}. - */ - public static CharSequence concatWithNonParagraphSuggestionSpansOnly(CharSequence... text) { - if (text.length == 0) { - return ""; - } - - if (text.length == 1) { - return text[0]; - } - - boolean spanned = false; - for (int i = 0; i < text.length; i++) { - if (text[i] instanceof Spanned) { - spanned = true; - break; - } - } - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < text.length; i++) { - sb.append(text[i]); - } - - if (!spanned) { - return sb.toString(); - } - - SpannableString ss = new SpannableString(sb); - int off = 0; - for (int i = 0; i < text.length; i++) { - int len = text[i].length(); - - if (text[i] instanceof Spanned) { - copyNonParagraphSuggestionSpansFrom((Spanned) text[i], 0, len, ss, off); - } - - off += len; - } - - return new SpannedString(ss); - } - - public static boolean hasUrlSpans(final CharSequence text, - final int startIndex, final int endIndex) { - if (!(text instanceof Spanned)) { - return false; // Not spanned, so no link - } - final Spanned spanned = (Spanned)text; - // getSpans(x, y) does not return spans that start on x or end on y. x-1, y+1 does the - // trick, and works in all cases even if startIndex <= 0 or endIndex >= text.length(). - final URLSpan[] spans = spanned.getSpans(startIndex - 1, endIndex + 1, URLSpan.class); - return null != spans && spans.length > 0; - } - - /** - * Splits the given {@code charSequence} with at occurrences of the given {@code regex}. - * <p> - * This is equivalent to - * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)} - * except that the spans are preserved in the result array. - * </p> - * @param charSequence the character sequence to be split. - * @param regex the regex pattern to be used as the separator. - * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty - * segments. Otherwise, trailing empty segments will be removed before being returned. - * @return the array which contains the result. All the spans in the <code>charSequence</code> - * is preserved. - */ - @UsedForTesting - public static CharSequence[] split(final CharSequence charSequence, final String regex, - final boolean preserveTrailingEmptySegments) { - // A short-cut for non-spanned strings. - if (!(charSequence instanceof Spanned)) { - // -1 means that trailing empty segments will be preserved. - return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0); - } - - // Hereafter, emulate String.split for CharSequence. - final ArrayList<CharSequence> sequences = new ArrayList<>(); - final Matcher matcher = Pattern.compile(regex).matcher(charSequence); - int nextStart = 0; - boolean matched = false; - while (matcher.find()) { - sequences.add(charSequence.subSequence(nextStart, matcher.start())); - nextStart = matcher.end(); - matched = true; - } - if (!matched) { - // never matched. preserveTrailingEmptySegments is ignored in this case. - return new CharSequence[] { charSequence }; - } - sequences.add(charSequence.subSequence(nextStart, charSequence.length())); - if (!preserveTrailingEmptySegments) { - for (int i = sequences.size() - 1; i >= 0; --i) { - if (!TextUtils.isEmpty(sequences.get(i))) { - break; - } - sequences.remove(i); - } - } - return sequences.toArray(new CharSequence[sequences.size()]); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/StatsUtils.java b/java/src/com/android/inputmethod/latin/utils/StatsUtils.java deleted file mode 100644 index 03e58478b..000000000 --- a/java/src/com/android/inputmethod/latin/utils/StatsUtils.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.latin.DictionaryFacilitator; -import com.android.inputmethod.latin.RichInputMethodManager; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.settings.SettingsValues; - -import javax.annotation.Nullable; - -@SuppressWarnings("unused") -public final class StatsUtils { - - private StatsUtils() { - // Intentional empty constructor. - } - - public static void onCreate(final SettingsValues settingsValues, - RichInputMethodManager richImm) { - } - - public static void onPickSuggestionManually(final SuggestedWords suggestedWords, - final SuggestedWords.SuggestedWordInfo suggestionInfo, - final DictionaryFacilitator dictionaryFacilitator) { - } - - public static void onBackspaceWordDelete(int wordLength) { - } - - public static void onBackspacePressed(int lengthToDelete) { - } - - public static void onBackspaceSelectedText(int selectedTextLength) { - } - - public static void onDeleteMultiCharInput(int multiCharLength) { - } - - public static void onRevertAutoCorrect() { - } - - public static void onRevertDoubleSpacePeriod() { - } - - public static void onRevertSwapPunctuation() { - } - - public static void onFinishInputView() { - } - - public static void onCreateInputView() { - } - - public static void onStartInputView(int inputType, int displayOrientation, boolean restarting) { - } - - public static void onAutoCorrection(final String typedWord, final String autoCorrectionWord, - final boolean isBatchInput, final DictionaryFacilitator dictionaryFacilitator, - final String prevWordsContext) { - } - - public static void onWordCommitUserTyped(final String commitWord, final boolean isBatchMode) { - } - - public static void onWordCommitAutoCorrect(final String commitWord, final boolean isBatchMode) { - } - - public static void onWordCommitSuggestionPickedManually( - final String commitWord, final boolean isBatchMode) { - } - - public static void onDoubleSpacePeriod() { - } - - public static void onLoadSettings(SettingsValues settingsValues) { - } - - public static void onInvalidWordIdentification(final String invalidWord) { - } - - public static void onSubtypeChanged(final InputMethodSubtype oldSubtype, - final InputMethodSubtype newSubtype) { - } - - public static void onSettingsActivity(final String entryPoint) { - } - - public static void onInputConnectionLaggy(final int operation, final long duration) { - } - - public static void onDecoderLaggy(final int operation, final long duration) { - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/StatsUtilsManager.java b/java/src/com/android/inputmethod/latin/utils/StatsUtilsManager.java deleted file mode 100644 index cd42f50c7..000000000 --- a/java/src/com/android/inputmethod/latin/utils/StatsUtilsManager.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import android.content.Context; - -import com.android.inputmethod.latin.DictionaryFacilitator; -import com.android.inputmethod.latin.settings.SettingsValues; - -@SuppressWarnings("unused") -public class StatsUtilsManager { - - private static final StatsUtilsManager sInstance = new StatsUtilsManager(); - private static StatsUtilsManager sTestInstance = null; - - /** - * @return the singleton instance of {@link StatsUtilsManager}. - */ - public static StatsUtilsManager getInstance() { - return sTestInstance != null ? sTestInstance : sInstance; - } - - public static void setTestInstance(final StatsUtilsManager testInstance) { - sTestInstance = testInstance; - } - - public void onCreate(final Context context, final DictionaryFacilitator dictionaryFacilitator) { - } - - public void onLoadSettings(final Context context, final SettingsValues settingsValues) { - } - - public void onStartInputView() { - } - - public void onFinishInputView() { - } - - public void onDestroy(final Context context) { - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java deleted file mode 100644 index 54a3fc39c..000000000 --- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.utils; - -import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.COMBINING_RULES; -import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; -import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; - -import android.content.Context; -import android.content.res.Resources; -import android.os.Build; -import android.util.Log; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.common.LocaleUtils; -import com.android.inputmethod.latin.common.StringUtils; - -import java.util.HashMap; -import java.util.Locale; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * A helper class to deal with subtype locales. - */ -// TODO: consolidate this into RichInputMethodSubtype -public final class SubtypeLocaleUtils { - static final String TAG = SubtypeLocaleUtils.class.getSimpleName(); - - // This reference class {@link R} must be located in the same package as LatinIME.java. - private static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName(); - - // Special language code to represent "no language". - public static final String NO_LANGUAGE = "zz"; - public static final String QWERTY = "qwerty"; - public static final String EMOJI = "emoji"; - public static final int UNKNOWN_KEYBOARD_LAYOUT = R.string.subtype_generic; - - private static volatile boolean sInitialized = false; - private static final Object sInitializeLock = new Object(); - private static Resources sResources; - // Keyboard layout to its display name map. - private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = new HashMap<>(); - // Keyboard layout to subtype name resource id map. - private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>(); - // Exceptional locale whose name should be displayed in Locale.ROOT. - private static final HashMap<String, Integer> sExceptionalLocaleDisplayedInRootLocale = - new HashMap<>(); - // Exceptional locale to subtype name resource id map. - private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>(); - // Exceptional locale to subtype name with layout resource id map. - private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap = - new HashMap<>(); - private static final String SUBTYPE_NAME_RESOURCE_PREFIX = - "string/subtype_"; - private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX = - "string/subtype_generic_"; - private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX = - "string/subtype_with_layout_"; - private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX = - "string/subtype_no_language_"; - private static final String SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX = - "string/subtype_in_root_locale_"; - // Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value. - // This is for compatibility to keep the same subtype ids as pre-JellyBean. - private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap = - new HashMap<>(); - - private SubtypeLocaleUtils() { - // Intentional empty constructor for utility class. - } - - // Note that this initialization method can be called multiple times. - public static void init(final Context context) { - synchronized (sInitializeLock) { - if (sInitialized == false) { - initLocked(context); - sInitialized = true; - } - } - } - - private static void initLocked(final Context context) { - final Resources res = context.getResources(); - sResources = res; - - final String[] predefinedLayoutSet = res.getStringArray(R.array.predefined_layouts); - final String[] layoutDisplayNames = res.getStringArray( - R.array.predefined_layout_display_names); - for (int i = 0; i < predefinedLayoutSet.length; i++) { - final String layoutName = predefinedLayoutSet[i]; - sKeyboardLayoutToDisplayNameMap.put(layoutName, layoutDisplayNames[i]); - final String resourceName = SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX + layoutName; - final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); - sKeyboardLayoutToNameIdsMap.put(layoutName, resId); - // Register subtype name resource id of "No language" with key "zz_<layout>" - final String noLanguageResName = SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX + layoutName; - final int noLanguageResId = res.getIdentifier( - noLanguageResName, null, RESOURCE_PACKAGE_NAME); - final String key = getNoLanguageLayoutKey(layoutName); - sKeyboardLayoutToNameIdsMap.put(key, noLanguageResId); - } - - final String[] exceptionalLocaleInRootLocale = res.getStringArray( - R.array.subtype_locale_displayed_in_root_locale); - for (int i = 0; i < exceptionalLocaleInRootLocale.length; i++) { - final String localeString = exceptionalLocaleInRootLocale[i]; - final String resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + localeString; - final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); - sExceptionalLocaleDisplayedInRootLocale.put(localeString, resId); - } - - final String[] exceptionalLocales = res.getStringArray( - R.array.subtype_locale_exception_keys); - for (int i = 0; i < exceptionalLocales.length; i++) { - final String localeString = exceptionalLocales[i]; - final String resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + localeString; - final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); - sExceptionalLocaleToNameIdsMap.put(localeString, resId); - final String resourceNameWithLayout = - SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + localeString; - final int resIdWithLayout = res.getIdentifier( - resourceNameWithLayout, null, RESOURCE_PACKAGE_NAME); - sExceptionalLocaleToWithLayoutNameIdsMap.put(localeString, resIdWithLayout); - } - - final String[] keyboardLayoutSetMap = res.getStringArray( - R.array.locale_and_extra_value_to_keyboard_layout_set_map); - for (int i = 0; i + 1 < keyboardLayoutSetMap.length; i += 2) { - final String key = keyboardLayoutSetMap[i]; - final String keyboardLayoutSet = keyboardLayoutSetMap[i + 1]; - sLocaleAndExtraValueToKeyboardLayoutSetMap.put(key, keyboardLayoutSet); - } - } - - public static boolean isExceptionalLocale(final String localeString) { - return sExceptionalLocaleToNameIdsMap.containsKey(localeString); - } - - private static final String getNoLanguageLayoutKey(final String keyboardLayoutName) { - return NO_LANGUAGE + "_" + keyboardLayoutName; - } - - public static int getSubtypeNameId(final String localeString, final String keyboardLayoutName) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN - && isExceptionalLocale(localeString)) { - return sExceptionalLocaleToWithLayoutNameIdsMap.get(localeString); - } - final String key = NO_LANGUAGE.equals(localeString) - ? getNoLanguageLayoutKey(keyboardLayoutName) - : keyboardLayoutName; - final Integer nameId = sKeyboardLayoutToNameIdsMap.get(key); - return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId; - } - - @Nonnull - public static Locale getDisplayLocaleOfSubtypeLocale(@Nonnull final String localeString) { - if (NO_LANGUAGE.equals(localeString)) { - return sResources.getConfiguration().locale; - } - if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { - return Locale.ROOT; - } - return LocaleUtils.constructLocaleFromString(localeString); - } - - public static String getSubtypeLocaleDisplayNameInSystemLocale( - @Nonnull final String localeString) { - final Locale displayLocale = sResources.getConfiguration().locale; - return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); - } - - @Nonnull - public static String getSubtypeLocaleDisplayName(@Nonnull final String localeString) { - final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); - return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); - } - - @Nonnull - public static String getSubtypeLanguageDisplayName(@Nonnull final String localeString) { - final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); - final String languageString; - if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { - languageString = localeString; - } else { - languageString = LocaleUtils.constructLocaleFromString(localeString).getLanguage(); - } - return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale); - } - - @Nonnull - private static String getSubtypeLocaleDisplayNameInternal(@Nonnull final String localeString, - @Nonnull final Locale displayLocale) { - if (NO_LANGUAGE.equals(localeString)) { - // No language subtype should be displayed in system locale. - return sResources.getString(R.string.subtype_no_language); - } - final Integer exceptionalNameResId; - if (displayLocale.equals(Locale.ROOT) - && sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { - exceptionalNameResId = sExceptionalLocaleDisplayedInRootLocale.get(localeString); - } else if (sExceptionalLocaleToNameIdsMap.containsKey(localeString)) { - exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString); - } else { - exceptionalNameResId = null; - } - - final String displayName; - if (exceptionalNameResId != null) { - final RunInLocale<String> getExceptionalName = new RunInLocale<String>() { - @Override - protected String job(final Resources res) { - return res.getString(exceptionalNameResId); - } - }; - displayName = getExceptionalName.runInLocale(sResources, displayLocale); - } else { - displayName = LocaleUtils.constructLocaleFromString(localeString) - .getDisplayName(displayLocale); - } - return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale); - } - - // InputMethodSubtype's display name in its locale. - // isAdditionalSubtype (T=true, F=false) - // locale layout | display name - // ------ ------- - ---------------------- - // en_US qwerty F English (US) exception - // en_GB qwerty F English (UK) exception - // es_US spanish F Español (EE.UU.) exception - // fr azerty F Français - // fr_CA qwerty F Français (Canada) - // fr_CH swiss F Français (Suisse) - // de qwertz F Deutsch - // de_CH swiss T Deutsch (Schweiz) - // zz qwerty F Alphabet (QWERTY) in system locale - // fr qwertz T Français (QWERTZ) - // de qwerty T Deutsch (QWERTY) - // en_US azerty T English (US) (AZERTY) exception - // zz azerty T Alphabet (AZERTY) in system locale - - @Nonnull - private static String getReplacementString(@Nonnull final InputMethodSubtype subtype, - @Nonnull final Locale displayLocale) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN - && subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) { - return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME); - } - return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale); - } - - @Nonnull - public static String getSubtypeDisplayNameInSystemLocale( - @Nonnull final InputMethodSubtype subtype) { - final Locale displayLocale = sResources.getConfiguration().locale; - return getSubtypeDisplayNameInternal(subtype, displayLocale); - } - - @Nonnull - public static String getSubtypeNameForLogging(@Nullable final InputMethodSubtype subtype) { - if (subtype == null) { - return "<null subtype>"; - } - return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype); - } - - @Nonnull - private static String getSubtypeDisplayNameInternal(@Nonnull final InputMethodSubtype subtype, - @Nonnull final Locale displayLocale) { - final String replacementString = getReplacementString(subtype, displayLocale); - // TODO: rework this for multi-lingual subtypes - final int nameResId = subtype.getNameResId(); - final RunInLocale<String> getSubtypeName = new RunInLocale<String>() { - @Override - protected String job(final Resources res) { - try { - return res.getString(nameResId, replacementString); - } catch (Resources.NotFoundException e) { - // TODO: Remove this catch when InputMethodManager.getCurrentInputMethodSubtype - // is fixed. - Log.w(TAG, "Unknown subtype: mode=" + subtype.getMode() - + " nameResId=" + subtype.getNameResId() - + " locale=" + subtype.getLocale() - + " extra=" + subtype.getExtraValue() - + "\n" + DebugLogUtils.getStackTrace()); - return ""; - } - } - }; - return StringUtils.capitalizeFirstCodePoint( - getSubtypeName.runInLocale(sResources, displayLocale), displayLocale); - } - - @Nonnull - public static Locale getSubtypeLocale(@Nonnull final InputMethodSubtype subtype) { - final String localeString = subtype.getLocale(); - return LocaleUtils.constructLocaleFromString(localeString); - } - - @Nonnull - public static String getKeyboardLayoutSetDisplayName( - @Nonnull final InputMethodSubtype subtype) { - final String layoutName = getKeyboardLayoutSetName(subtype); - return getKeyboardLayoutSetDisplayName(layoutName); - } - - @Nonnull - public static String getKeyboardLayoutSetDisplayName(@Nonnull final String layoutName) { - return sKeyboardLayoutToDisplayNameMap.get(layoutName); - } - - @Nonnull - public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) { - String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET); - if (keyboardLayoutSet == null) { - // This subtype doesn't have a keyboardLayoutSet extra value, so lookup its keyboard - // layout set in sLocaleAndExtraValueToKeyboardLayoutSetMap to keep it compatible with - // pre-JellyBean. - final String key = subtype.getLocale() + ":" + subtype.getExtraValue(); - keyboardLayoutSet = sLocaleAndExtraValueToKeyboardLayoutSetMap.get(key); - } - // TODO: Remove this null check when InputMethodManager.getCurrentInputMethodSubtype is - // fixed. - if (keyboardLayoutSet == null) { - android.util.Log.w(TAG, "KeyboardLayoutSet not found, use QWERTY: " + - "locale=" + subtype.getLocale() + " extraValue=" + subtype.getExtraValue()); - return QWERTY; - } - return keyboardLayoutSet; - } - - public static String getCombiningRulesExtraValue(final InputMethodSubtype subtype) { - return subtype.getExtraValueOf(COMBINING_RULES); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java deleted file mode 100644 index 981355115..000000000 --- a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.define.ProductionFlags; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.TreeSet; - -/** - * A TreeSet of SuggestedWordInfo that is bounded in size and throws everything that's smaller - * than its limit - */ -public final class SuggestionResults extends TreeSet<SuggestedWordInfo> { - public final ArrayList<SuggestedWordInfo> mRawSuggestions; - // TODO: Instead of a boolean , we may want to include the context of this suggestion results, - // such as {@link NgramContext}. - public final boolean mIsBeginningOfSentence; - public final boolean mFirstSuggestionExceedsConfidenceThreshold; - private final int mCapacity; - - public SuggestionResults(final int capacity, final boolean isBeginningOfSentence, - final boolean firstSuggestionExceedsConfidenceThreshold) { - this(sSuggestedWordInfoComparator, capacity, isBeginningOfSentence, - firstSuggestionExceedsConfidenceThreshold); - } - - private SuggestionResults(final Comparator<SuggestedWordInfo> comparator, final int capacity, - final boolean isBeginningOfSentence, - final boolean firstSuggestionExceedsConfidenceThreshold) { - super(comparator); - mCapacity = capacity; - if (ProductionFlags.INCLUDE_RAW_SUGGESTIONS) { - mRawSuggestions = new ArrayList<>(); - } else { - mRawSuggestions = null; - } - mIsBeginningOfSentence = isBeginningOfSentence; - mFirstSuggestionExceedsConfidenceThreshold = firstSuggestionExceedsConfidenceThreshold; - } - - @Override - public boolean add(final SuggestedWordInfo e) { - if (size() < mCapacity) return super.add(e); - if (comparator().compare(e, last()) > 0) return false; - super.add(e); - pollLast(); // removes the last element - return true; - } - - @Override - public boolean addAll(final Collection<? extends SuggestedWordInfo> e) { - if (null == e) return false; - return super.addAll(e); - } - - static final class SuggestedWordInfoComparator implements Comparator<SuggestedWordInfo> { - // This comparator ranks the word info with the higher frequency first. That's because - // that's the order we want our elements in. - @Override - public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) { - if (o1.mScore > o2.mScore) return -1; - if (o1.mScore < o2.mScore) return 1; - if (o1.mCodePointCount < o2.mCodePointCount) return -1; - if (o1.mCodePointCount > o2.mCodePointCount) return 1; - return o1.mWord.compareTo(o2.mWord); - } - } - - private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator = - new SuggestedWordInfoComparator(); -} diff --git a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java deleted file mode 100644 index ab2b00e36..000000000 --- a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.utils; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.AsyncTask; -import android.util.LruCache; - -import com.android.inputmethod.compat.AppWorkaroundsUtils; - -public final class TargetPackageInfoGetterTask extends - AsyncTask<String, Void, PackageInfo> { - private static final int MAX_CACHE_ENTRIES = 64; // arbitrary - private static final LruCache<String, PackageInfo> sCache = new LruCache<>(MAX_CACHE_ENTRIES); - - public static PackageInfo getCachedPackageInfo(final String packageName) { - if (null == packageName) return null; - return sCache.get(packageName); - } - - public static void removeCachedPackageInfo(final String packageName) { - sCache.remove(packageName); - } - - private Context mContext; - private final AsyncResultHolder<AppWorkaroundsUtils> mResult; - - public TargetPackageInfoGetterTask(final Context context, - final AsyncResultHolder<AppWorkaroundsUtils> result) { - mContext = context; - mResult = result; - } - - @Override - protected PackageInfo doInBackground(final String... packageName) { - final PackageManager pm = mContext.getPackageManager(); - mContext = null; // Bazooka-powered anti-leak device - try { - final PackageInfo packageInfo = pm.getPackageInfo(packageName[0], 0 /* flags */); - sCache.put(packageName[0], packageInfo); - return packageInfo; - } catch (android.content.pm.PackageManager.NameNotFoundException e) { - return null; - } - } - - @Override - protected void onPostExecute(final PackageInfo info) { - mResult.set(new AppWorkaroundsUtils(info)); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/TextRange.java b/java/src/com/android/inputmethod/latin/utils/TextRange.java deleted file mode 100644 index dbf3b5060..000000000 --- a/java/src/com/android/inputmethod/latin/utils/TextRange.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.text.Spanned; -import android.text.style.SuggestionSpan; - -import java.util.Arrays; - -/** - * Represents a range of text, relative to the current cursor position. - */ -public final class TextRange { - private final CharSequence mTextAtCursor; - private final int mWordAtCursorStartIndex; - private final int mWordAtCursorEndIndex; - private final int mCursorIndex; - - public final CharSequence mWord; - public final boolean mHasUrlSpans; - - public int getNumberOfCharsInWordBeforeCursor() { - return mCursorIndex - mWordAtCursorStartIndex; - } - - public int getNumberOfCharsInWordAfterCursor() { - return mWordAtCursorEndIndex - mCursorIndex; - } - - public int length() { - return mWord.length(); - } - - /** - * Gets the suggestion spans that are put squarely on the word, with the exact start - * and end of the span matching the boundaries of the word. - * @return the list of spans. - */ - public SuggestionSpan[] getSuggestionSpansAtWord() { - if (!(mTextAtCursor instanceof Spanned && mWord instanceof Spanned)) { - return new SuggestionSpan[0]; - } - final Spanned text = (Spanned)mTextAtCursor; - // Note: it's fine to pass indices negative or greater than the length of the string - // to the #getSpans() method. The reason we need to get from -1 to +1 is that, the - // spans were cut at the cursor position, and #getSpans(start, end) does not return - // spans that end at `start' or begin at `end'. Consider the following case: - // this| is (The | symbolizes the cursor position - // ---- --- - // In this case, the cursor is in position 4, so the 0~7 span has been split into - // a 0~4 part and a 4~7 part. - // If we called #getSpans(0, 4) in this case, we would only get the part from 0 to 4 - // of the span, and not the part from 4 to 7, so we would not realize the span actually - // extends from 0 to 7. But if we call #getSpans(-1, 5) we'll get both the 0~4 and - // the 4~7 spans and we can merge them accordingly. - // Any span starting more than 1 char away from the word boundaries in any direction - // does not touch the word, so we don't need to consider it. That's why requesting - // -1 ~ +1 is enough. - // Of course this is only relevant if the cursor is at one end of the word. If it's - // in the middle, the -1 and +1 are not necessary, but they are harmless. - final SuggestionSpan[] spans = text.getSpans(mWordAtCursorStartIndex - 1, - mWordAtCursorEndIndex + 1, SuggestionSpan.class); - int readIndex = 0; - int writeIndex = 0; - for (; readIndex < spans.length; ++readIndex) { - final SuggestionSpan span = spans[readIndex]; - // The span may be null, as we null them when we find duplicates. Cf a few lines - // down. - if (null == span) continue; - // Tentative span start and end. This may be modified later if we realize the - // same span is also applied to other parts of the string. - int spanStart = text.getSpanStart(span); - int spanEnd = text.getSpanEnd(span); - for (int i = readIndex + 1; i < spans.length; ++i) { - if (span.equals(spans[i])) { - // We found the same span somewhere else. Read the new extent of this - // span, and adjust our values accordingly. - spanStart = Math.min(spanStart, text.getSpanStart(spans[i])); - spanEnd = Math.max(spanEnd, text.getSpanEnd(spans[i])); - // ...and mark the span as processed. - spans[i] = null; - } - } - if (spanStart == mWordAtCursorStartIndex && spanEnd == mWordAtCursorEndIndex) { - // If the span does not start and stop here, ignore it. It probably extends - // past the start or end of the word, as happens in missing space correction - // or EasyEditSpans put by voice input. - spans[writeIndex++] = spans[readIndex]; - } - } - return writeIndex == readIndex ? spans : Arrays.copyOfRange(spans, 0, writeIndex); - } - - public TextRange(final CharSequence textAtCursor, final int wordAtCursorStartIndex, - final int wordAtCursorEndIndex, final int cursorIndex, final boolean hasUrlSpans) { - if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex - || cursorIndex > wordAtCursorEndIndex - || wordAtCursorEndIndex > textAtCursor.length()) { - throw new IndexOutOfBoundsException(); - } - mTextAtCursor = textAtCursor; - mWordAtCursorStartIndex = wordAtCursorStartIndex; - mWordAtCursorEndIndex = wordAtCursorEndIndex; - mCursorIndex = cursorIndex; - mHasUrlSpans = hasUrlSpans; - mWord = mTextAtCursor.subSequence(mWordAtCursorStartIndex, mWordAtCursorEndIndex); - } -}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java deleted file mode 100644 index fafba79c2..000000000 --- a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.latin.utils; - -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.util.SparseArray; - -public final class TypefaceUtils { - private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' }; - private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' }; - - private TypefaceUtils() { - // This utility class is not publicly instantiable. - } - - // This sparse array caches key label text height in pixel indexed by key label text size. - private static final SparseArray<Float> sTextHeightCache = new SparseArray<>(); - // Working variable for the following method. - private static final Rect sTextHeightBounds = new Rect(); - - private static float getCharHeight(final char[] referenceChar, final Paint paint) { - final int key = getCharGeometryCacheKey(referenceChar[0], paint); - synchronized (sTextHeightCache) { - final Float cachedValue = sTextHeightCache.get(key); - if (cachedValue != null) { - return cachedValue; - } - - paint.getTextBounds(referenceChar, 0, 1, sTextHeightBounds); - final float height = sTextHeightBounds.height(); - sTextHeightCache.put(key, height); - return height; - } - } - - // This sparse array caches key label text width in pixel indexed by key label text size. - private static final SparseArray<Float> sTextWidthCache = new SparseArray<>(); - // Working variable for the following method. - private static final Rect sTextWidthBounds = new Rect(); - - private static float getCharWidth(final char[] referenceChar, final Paint paint) { - final int key = getCharGeometryCacheKey(referenceChar[0], paint); - synchronized (sTextWidthCache) { - final Float cachedValue = sTextWidthCache.get(key); - if (cachedValue != null) { - return cachedValue; - } - - paint.getTextBounds(referenceChar, 0, 1, sTextWidthBounds); - final float width = sTextWidthBounds.width(); - sTextWidthCache.put(key, width); - return width; - } - } - - private static int getCharGeometryCacheKey(final char referenceChar, final Paint paint) { - final int labelSize = (int)paint.getTextSize(); - final Typeface face = paint.getTypeface(); - final int codePointOffset = referenceChar << 15; - if (face == Typeface.DEFAULT) { - return codePointOffset + labelSize; - } else if (face == Typeface.DEFAULT_BOLD) { - return codePointOffset + labelSize + 0x1000; - } else if (face == Typeface.MONOSPACE) { - return codePointOffset + labelSize + 0x2000; - } else { - return codePointOffset + labelSize; - } - } - - public static float getReferenceCharHeight(final Paint paint) { - return getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint); - } - - public static float getReferenceCharWidth(final Paint paint) { - return getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint); - } - - public static float getReferenceDigitWidth(final Paint paint) { - return getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint); - } - - // Working variable for the following method. - private static final Rect sStringWidthBounds = new Rect(); - - public static float getStringWidth(final String string, final Paint paint) { - synchronized (sStringWidthBounds) { - paint.getTextBounds(string, 0, string.length(), sStringWidthBounds); - return sStringWidthBounds.width(); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/UncachedInputMethodManagerUtils.java b/java/src/com/android/inputmethod/latin/utils/UncachedInputMethodManagerUtils.java deleted file mode 100644 index 5df00efb9..000000000 --- a/java/src/com/android/inputmethod/latin/utils/UncachedInputMethodManagerUtils.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import android.content.Context; -import android.provider.Settings; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; - -/* - * A utility class for {@link InputMethodManager}. Unlike {@link RichInputMethodManager}, this - * class provides synchronous, non-cached access to {@link InputMethodManager}. The setup activity - * is a good example to use this class because {@link InputMethodManagerService} may not be aware of - * this IME immediately after this IME is installed. - */ -public final class UncachedInputMethodManagerUtils { - /** - * Check if the IME specified by the context is enabled. - * CAVEAT: This may cause a round trip IPC. - * - * @param context package context of the IME to be checked. - * @param imm the {@link InputMethodManager}. - * @return true if this IME is enabled. - */ - public static boolean isThisImeEnabled(final Context context, - final InputMethodManager imm) { - final String packageName = context.getPackageName(); - for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) { - if (packageName.equals(imi.getPackageName())) { - return true; - } - } - return false; - } - - /** - * Check if the IME specified by the context is the current IME. - * CAVEAT: This may cause a round trip IPC. - * - * @param context package context of the IME to be checked. - * @param imm the {@link InputMethodManager}. - * @return true if this IME is the current IME. - */ - public static boolean isThisImeCurrent(final Context context, - final InputMethodManager imm) { - final InputMethodInfo imi = getInputMethodInfoOf(context.getPackageName(), imm); - final String currentImeId = Settings.Secure.getString( - context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); - return imi != null && imi.getId().equals(currentImeId); - } - - /** - * Get {@link InputMethodInfo} of the IME specified by the package name. - * CAVEAT: This may cause a round trip IPC. - * - * @param packageName package name of the IME. - * @param imm the {@link InputMethodManager}. - * @return the {@link InputMethodInfo} of the IME specified by the <code>packageName</code>, - * or null if not found. - */ - public static InputMethodInfo getInputMethodInfoOf(final String packageName, - final InputMethodManager imm) { - for (final InputMethodInfo imi : imm.getInputMethodList()) { - if (packageName.equals(imi.getPackageName())) { - return imi; - } - } - return null; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java deleted file mode 100644 index 0bcba2754..000000000 --- a/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.utils; - -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.MarginLayoutParams; -import android.view.Window; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; - -public final class ViewLayoutUtils { - private ViewLayoutUtils() { - // This utility class is not publicly instantiable. - } - - public static MarginLayoutParams newLayoutParam(final ViewGroup placer, final int width, - final int height) { - if (placer instanceof FrameLayout) { - return new FrameLayout.LayoutParams(width, height); - } else if (placer instanceof RelativeLayout) { - return new RelativeLayout.LayoutParams(width, height); - } else if (placer == null) { - throw new NullPointerException("placer is null"); - } else { - throw new IllegalArgumentException("placer is neither FrameLayout nor RelativeLayout: " - + placer.getClass().getName()); - } - } - - public static void placeViewAt(final View view, final int x, final int y, final int w, - final int h) { - final ViewGroup.LayoutParams lp = view.getLayoutParams(); - if (lp instanceof MarginLayoutParams) { - final MarginLayoutParams marginLayoutParams = (MarginLayoutParams)lp; - marginLayoutParams.width = w; - marginLayoutParams.height = h; - marginLayoutParams.setMargins(x, y, 0, 0); - } - } - - public static void updateLayoutHeightOf(final Window window, final int layoutHeight) { - final WindowManager.LayoutParams params = window.getAttributes(); - if (params != null && params.height != layoutHeight) { - params.height = layoutHeight; - window.setAttributes(params); - } - } - - public static void updateLayoutHeightOf(final View view, final int layoutHeight) { - final ViewGroup.LayoutParams params = view.getLayoutParams(); - if (params != null && params.height != layoutHeight) { - params.height = layoutHeight; - view.setLayoutParams(params); - } - } - - public static void updateLayoutGravityOf(final View view, final int layoutGravity) { - final ViewGroup.LayoutParams lp = view.getLayoutParams(); - if (lp instanceof LinearLayout.LayoutParams) { - final LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)lp; - if (params.gravity != layoutGravity) { - params.gravity = layoutGravity; - view.setLayoutParams(params); - } - } else if (lp instanceof FrameLayout.LayoutParams) { - final FrameLayout.LayoutParams params = (FrameLayout.LayoutParams)lp; - if (params.gravity != layoutGravity) { - params.gravity = layoutGravity; - view.setLayoutParams(params); - } - } else { - throw new IllegalArgumentException("Layout parameter doesn't have gravity: " - + lp.getClass().getName()); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java deleted file mode 100644 index fc0a9cb6c..000000000 --- a/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; - -import android.util.Log; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.NgramContext; -import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.define.DecoderSpecificConstants; -import com.android.inputmethod.latin.settings.SpacingAndPunctuations; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -// Note: this class is used as a parameter type of a native method. You should be careful when you -// rename this class or field name. See BinaryDictionary#addMultipleDictionaryEntriesNative(). -public final class WordInputEventForPersonalization { - private static final String TAG = WordInputEventForPersonalization.class.getSimpleName(); - private static final boolean DEBUG_TOKEN = false; - - public final int[] mTargetWord; - public final int mPrevWordsCount; - public final int[][] mPrevWordArray = - new int[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; - public final boolean[] mIsPrevWordBeginningOfSentenceArray = - new boolean[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; - // Time stamp in seconds. - public final int mTimestamp; - - @UsedForTesting - public WordInputEventForPersonalization(final CharSequence targetWord, - final NgramContext ngramContext, final int timestamp) { - mTargetWord = StringUtils.toCodePointArray(targetWord); - mPrevWordsCount = ngramContext.getPrevWordCount(); - ngramContext.outputToArray(mPrevWordArray, mIsPrevWordBeginningOfSentenceArray); - mTimestamp = timestamp; - } - - // Process a list of words and return a list of {@link WordInputEventForPersonalization} - // objects. - public static ArrayList<WordInputEventForPersonalization> createInputEventFrom( - final List<String> tokens, final int timestamp, - final SpacingAndPunctuations spacingAndPunctuations, final Locale locale) { - final ArrayList<WordInputEventForPersonalization> inputEvents = new ArrayList<>(); - final int N = tokens.size(); - NgramContext ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO; - for (int i = 0; i < N; ++i) { - final String tempWord = tokens.get(i); - if (StringUtils.isEmptyStringOrWhiteSpaces(tempWord)) { - // just skip this token - if (DEBUG_TOKEN) { - Log.d(TAG, "--- isEmptyStringOrWhiteSpaces: \"" + tempWord + "\""); - } - continue; - } - if (!DictionaryInfoUtils.looksValidForDictionaryInsertion( - tempWord, spacingAndPunctuations)) { - if (DEBUG_TOKEN) { - Log.d(TAG, "--- not looksValidForDictionaryInsertion: \"" - + tempWord + "\""); - } - // Sentence terminator found. Split. - // TODO: Detect whether the context is beginning-of-sentence. - ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO; - continue; - } - if (DEBUG_TOKEN) { - Log.d(TAG, "--- word: \"" + tempWord + "\""); - } - final WordInputEventForPersonalization inputEvent = - detectWhetherVaildWordOrNotAndGetInputEvent( - ngramContext, tempWord, timestamp, locale); - if (inputEvent == null) { - continue; - } - inputEvents.add(inputEvent); - ngramContext = ngramContext.getNextNgramContext(new NgramContext.WordInfo(tempWord)); - } - return inputEvents; - } - - private static WordInputEventForPersonalization detectWhetherVaildWordOrNotAndGetInputEvent( - final NgramContext ngramContext, final String targetWord, final int timestamp, - final Locale locale) { - if (locale == null) { - return null; - } - return new WordInputEventForPersonalization(targetWord, ngramContext, timestamp); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java deleted file mode 100644 index bdad16652..000000000 --- a/java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.inputmethod.latin.utils; - -import android.content.res.TypedArray; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; - -public final class XmlParseUtils { - private XmlParseUtils() { - // This utility class is not publicly instantiable. - } - - @SuppressWarnings("serial") - public static class ParseException extends XmlPullParserException { - public ParseException(final String msg, final XmlPullParser parser) { - super(msg + " at " + parser.getPositionDescription()); - } - } - - @SuppressWarnings("serial") - public static final class IllegalStartTag extends ParseException { - public IllegalStartTag(final XmlPullParser parser, final String tag, final String parent) { - super("Illegal start tag " + tag + " in " + parent, parser); - } - } - - @SuppressWarnings("serial") - public static final class IllegalEndTag extends ParseException { - public IllegalEndTag(final XmlPullParser parser, final String tag, final String parent) { - super("Illegal end tag " + tag + " in " + parent, parser); - } - } - - @SuppressWarnings("serial") - public static final class IllegalAttribute extends ParseException { - public IllegalAttribute(final XmlPullParser parser, final String tag, - final String attribute) { - super("Tag " + tag + " has illegal attribute " + attribute, parser); - } - } - - @SuppressWarnings("serial") - public static final class NonEmptyTag extends ParseException{ - public NonEmptyTag(final XmlPullParser parser, final String tag) { - super(tag + " must be empty tag", parser); - } - } - - public static void checkEndTag(final String tag, final XmlPullParser parser) - throws XmlPullParserException, IOException { - if (parser.next() == XmlPullParser.END_TAG && tag.equals(parser.getName())) - return; - throw new NonEmptyTag(parser, tag); - } - - public static void checkAttributeExists(final TypedArray attr, final int attrId, - final String attrName, final String tag, final XmlPullParser parser) - throws XmlPullParserException { - if (attr.hasValue(attrId)) { - return; - } - throw new ParseException( - "No " + attrName + " attribute found in <" + tag + "/>", parser); - } -} |