LatinIME/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java

239 lines
12 KiB
Java

/*
* 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 */ });
}
}