Home | History | Annotate | Download | only in utils
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.latin.utils;
     18 
     19 import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
     20 import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.ASCII_CAPABLE;
     21 import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
     22 import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE;
     23 import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
     24 import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
     25 
     26 import android.os.Build;
     27 import android.text.TextUtils;
     28 import android.util.Log;
     29 import android.view.inputmethod.InputMethodSubtype;
     30 
     31 import com.android.inputmethod.annotations.UsedForTesting;
     32 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
     33 import com.android.inputmethod.latin.R;
     34 import com.android.inputmethod.latin.common.StringUtils;
     35 
     36 import java.util.ArrayList;
     37 import java.util.Arrays;
     38 
     39 public final class AdditionalSubtypeUtils {
     40     private static final String TAG = AdditionalSubtypeUtils.class.getSimpleName();
     41 
     42     private static final InputMethodSubtype[] EMPTY_SUBTYPE_ARRAY = new InputMethodSubtype[0];
     43 
     44     private AdditionalSubtypeUtils() {
     45         // This utility class is not publicly instantiable.
     46     }
     47 
     48     @UsedForTesting
     49     public static boolean isAdditionalSubtype(final InputMethodSubtype subtype) {
     50         return subtype.containsExtraValueKey(IS_ADDITIONAL_SUBTYPE);
     51     }
     52 
     53     private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":";
     54     private static final int INDEX_OF_LOCALE = 0;
     55     private static final int INDEX_OF_KEYBOARD_LAYOUT = 1;
     56     private static final int INDEX_OF_EXTRA_VALUE = 2;
     57     private static final int LENGTH_WITHOUT_EXTRA_VALUE = (INDEX_OF_KEYBOARD_LAYOUT + 1);
     58     private static final int LENGTH_WITH_EXTRA_VALUE = (INDEX_OF_EXTRA_VALUE + 1);
     59     private static final String PREF_SUBTYPE_SEPARATOR = ";";
     60 
     61     private static InputMethodSubtype createAdditionalSubtypeInternal(
     62             final String localeString, final String keyboardLayoutSetName,
     63             final boolean isAsciiCapable, final boolean isEmojiCapable) {
     64         final int nameId = SubtypeLocaleUtils.getSubtypeNameId(localeString, keyboardLayoutSetName);
     65         final String platformVersionDependentExtraValues = getPlatformVersionDependentExtraValue(
     66                 localeString, keyboardLayoutSetName, isAsciiCapable, isEmojiCapable);
     67         final int platformVersionIndependentSubtypeId =
     68                 getPlatformVersionIndependentSubtypeId(localeString, keyboardLayoutSetName);
     69         // NOTE: In KitKat and later, InputMethodSubtypeBuilder#setIsAsciiCapable is also available.
     70         // TODO: Use InputMethodSubtypeBuilder#setIsAsciiCapable when appropriate.
     71         return InputMethodSubtypeCompatUtils.newInputMethodSubtype(nameId,
     72                 R.drawable.ic_ime_switcher_dark, localeString, KEYBOARD_MODE,
     73                 platformVersionDependentExtraValues,
     74                 false /* isAuxiliary */, false /* overrideImplicitlyEnabledSubtype */,
     75                 platformVersionIndependentSubtypeId);
     76     }
     77 
     78     public static InputMethodSubtype createDummyAdditionalSubtype(
     79             final String localeString, final String keyboardLayoutSetName) {
     80         return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName,
     81                 false /* isAsciiCapable */, false /* isEmojiCapable */);
     82     }
     83 
     84     public static InputMethodSubtype createAsciiEmojiCapableAdditionalSubtype(
     85             final String localeString, final String keyboardLayoutSetName) {
     86         return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName,
     87                 true /* isAsciiCapable */, true /* isEmojiCapable */);
     88     }
     89 
     90     public static String getPrefSubtype(final InputMethodSubtype subtype) {
     91         final String localeString = subtype.getLocale();
     92         final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
     93         final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName;
     94         final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists(
     95                 layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists(
     96                         IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue()));
     97         final String basePrefSubtype = localeString + LOCALE_AND_LAYOUT_SEPARATOR
     98                 + keyboardLayoutSetName;
     99         return extraValue.isEmpty() ? basePrefSubtype
    100                 : basePrefSubtype + LOCALE_AND_LAYOUT_SEPARATOR + extraValue;
    101     }
    102 
    103     public static InputMethodSubtype[] createAdditionalSubtypesArray(final String prefSubtypes) {
    104         if (TextUtils.isEmpty(prefSubtypes)) {
    105             return EMPTY_SUBTYPE_ARRAY;
    106         }
    107         final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
    108         final ArrayList<InputMethodSubtype> subtypesList = new ArrayList<>(prefSubtypeArray.length);
    109         for (final String prefSubtype : prefSubtypeArray) {
    110             final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
    111             if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE
    112                     && elems.length != LENGTH_WITH_EXTRA_VALUE) {
    113                 Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype + " in "
    114                         + prefSubtypes);
    115                 continue;
    116             }
    117             final String localeString = elems[INDEX_OF_LOCALE];
    118             final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT];
    119             // Here we assume that all the additional subtypes have AsciiCapable and EmojiCapable.
    120             // This is actually what the setting dialog for additional subtype is doing.
    121             final InputMethodSubtype subtype = createAsciiEmojiCapableAdditionalSubtype(
    122                     localeString, keyboardLayoutSetName);
    123             if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) {
    124                 // Skip unknown keyboard layout subtype. This may happen when predefined keyboard
    125                 // layout has been removed.
    126                 continue;
    127             }
    128             subtypesList.add(subtype);
    129         }
    130         return subtypesList.toArray(new InputMethodSubtype[subtypesList.size()]);
    131     }
    132 
    133     public static String createPrefSubtypes(final InputMethodSubtype[] subtypes) {
    134         if (subtypes == null || subtypes.length == 0) {
    135             return "";
    136         }
    137         final StringBuilder sb = new StringBuilder();
    138         for (final InputMethodSubtype subtype : subtypes) {
    139             if (sb.length() > 0) {
    140                 sb.append(PREF_SUBTYPE_SEPARATOR);
    141             }
    142             sb.append(getPrefSubtype(subtype));
    143         }
    144         return sb.toString();
    145     }
    146 
    147     public static String createPrefSubtypes(final String[] prefSubtypes) {
    148         if (prefSubtypes == null || prefSubtypes.length == 0) {
    149             return "";
    150         }
    151         final StringBuilder sb = new StringBuilder();
    152         for (final String prefSubtype : prefSubtypes) {
    153             if (sb.length() > 0) {
    154                 sb.append(PREF_SUBTYPE_SEPARATOR);
    155             }
    156             sb.append(prefSubtype);
    157         }
    158         return sb.toString();
    159     }
    160 
    161     /**
    162      * Returns the extra value that is optimized for the running OS.
    163      * <p>
    164      * Historically the extra value has been used as the last resort to annotate various kinds of
    165      * attributes. Some of these attributes are valid only on some platform versions. Thus we cannot
    166      * assume that the extra values stored in a persistent storage are always valid. We need to
    167      * regenerate the extra value on the fly instead.
    168      * </p>
    169      * @param localeString the locale string (e.g., "en_US").
    170      * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak").
    171      * @param isAsciiCapable true when ASCII characters are supported with this layout.
    172      * @param isEmojiCapable true when Unicode Emoji characters are supported with this layout.
    173      * @return extra value that is optimized for the running OS.
    174      * @see #getPlatformVersionIndependentSubtypeId(String, String)
    175      */
    176     private static String getPlatformVersionDependentExtraValue(final String localeString,
    177             final String keyboardLayoutSetName, final boolean isAsciiCapable,
    178             final boolean isEmojiCapable) {
    179         final ArrayList<String> extraValueItems = new ArrayList<>();
    180         extraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName);
    181         if (isAsciiCapable) {
    182             extraValueItems.add(ASCII_CAPABLE);
    183         }
    184         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
    185                 SubtypeLocaleUtils.isExceptionalLocale(localeString)) {
    186             extraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" +
    187                     SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName));
    188         }
    189         if (isEmojiCapable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    190             extraValueItems.add(EMOJI_CAPABLE);
    191         }
    192         extraValueItems.add(IS_ADDITIONAL_SUBTYPE);
    193         return TextUtils.join(",", extraValueItems);
    194     }
    195 
    196     /**
    197      * Returns the subtype ID that is supposed to be compatible between different version of OSes.
    198      * <p>
    199      * From the compatibility point of view, it is important to keep subtype id predictable and
    200      * stable between different OSes. For this purpose, the calculation code in this method is
    201      * carefully chosen and then fixed. Treat the following code as no more or less than a
    202      * hash function. Each component to be hashed can be different from the corresponding value
    203      * that is used to instantiate {@link InputMethodSubtype} actually.
    204      * For example, you don't need to update <code>compatibilityExtraValueItems</code> in this
    205      * method even when we need to add some new extra values for the actual instance of
    206      * {@link InputMethodSubtype}.
    207      * </p>
    208      * @param localeString the locale string (e.g., "en_US").
    209      * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak").
    210      * @return a platform-version independent subtype ID.
    211      * @see #getPlatformVersionDependentExtraValue(String, String, boolean, boolean)
    212      */
    213     private static int getPlatformVersionIndependentSubtypeId(final String localeString,
    214             final String keyboardLayoutSetName) {
    215         // For compatibility reasons, we concatenate the extra values in the following order.
    216         // - KeyboardLayoutSet
    217         // - AsciiCapable
    218         // - UntranslatableReplacementStringInSubtypeName
    219         // - EmojiCapable
    220         // - isAdditionalSubtype
    221         final ArrayList<String> compatibilityExtraValueItems = new ArrayList<>();
    222         compatibilityExtraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName);
    223         compatibilityExtraValueItems.add(ASCII_CAPABLE);
    224         if (SubtypeLocaleUtils.isExceptionalLocale(localeString)) {
    225             compatibilityExtraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" +
    226                     SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName));
    227         }
    228         compatibilityExtraValueItems.add(EMOJI_CAPABLE);
    229         compatibilityExtraValueItems.add(IS_ADDITIONAL_SUBTYPE);
    230         final String compatibilityExtraValues = TextUtils.join(",", compatibilityExtraValueItems);
    231         return Arrays.hashCode(new Object[] {
    232                 localeString,
    233                 KEYBOARD_MODE,
    234                 compatibilityExtraValues,
    235                 false /* isAuxiliary */,
    236                 false /* overrideImplicitlyEnabledSubtype */ });
    237     }
    238 }
    239