Home | History | Annotate | Download | only in latin
      1 /*
      2  * Copyright (C) 2014 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;
     18 
     19 import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
     20 
     21 import android.os.Build;
     22 import android.util.Log;
     23 import android.view.inputmethod.InputMethodSubtype;
     24 
     25 import com.android.inputmethod.compat.BuildCompatUtils;
     26 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
     27 import com.android.inputmethod.latin.common.Constants;
     28 import com.android.inputmethod.latin.common.LocaleUtils;
     29 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
     30 
     31 import java.util.HashMap;
     32 import java.util.Locale;
     33 
     34 import javax.annotation.Nonnull;
     35 import javax.annotation.Nullable;
     36 
     37 /**
     38  * Enrichment class for InputMethodSubtype to enable concurrent multi-lingual input.
     39  *
     40  * Right now, this returns the extra value of its primary subtype.
     41  */
     42 // non final for easy mocking.
     43 public class RichInputMethodSubtype {
     44     private static final String TAG = RichInputMethodSubtype.class.getSimpleName();
     45 
     46     private static final HashMap<Locale, Locale> sLocaleMap = initializeLocaleMap();
     47     private static final HashMap<Locale, Locale> initializeLocaleMap() {
     48         final HashMap<Locale, Locale> map = new HashMap<>();
     49         if (BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
     50             // Locale#forLanguageTag is available on API Level 21+.
     51             // TODO: Remove this workaround once when we become able to deal with "sr-Latn".
     52             map.put(Locale.forLanguageTag("sr-Latn"), new Locale("sr_ZZ"));
     53         }
     54         return map;
     55     }
     56 
     57     @Nonnull
     58     private final InputMethodSubtype mSubtype;
     59     @Nonnull
     60     private final Locale mLocale;
     61     @Nonnull
     62     private final Locale mOriginalLocale;
     63 
     64     public RichInputMethodSubtype(@Nonnull final InputMethodSubtype subtype) {
     65         mSubtype = subtype;
     66         mOriginalLocale = InputMethodSubtypeCompatUtils.getLocaleObject(mSubtype);
     67         final Locale mappedLocale = sLocaleMap.get(mOriginalLocale);
     68         mLocale = mappedLocale != null ? mappedLocale : mOriginalLocale;
     69     }
     70 
     71     // Extra values are determined by the primary subtype. This is probably right, but
     72     // we may have to revisit this later.
     73     public String getExtraValueOf(@Nonnull final String key) {
     74         return mSubtype.getExtraValueOf(key);
     75     }
     76 
     77     // The mode is also determined by the primary subtype.
     78     public String getMode() {
     79         return mSubtype.getMode();
     80     }
     81 
     82     public boolean isNoLanguage() {
     83         return SubtypeLocaleUtils.NO_LANGUAGE.equals(mSubtype.getLocale());
     84     }
     85 
     86     public String getNameForLogging() {
     87         return toString();
     88     }
     89 
     90     // InputMethodSubtype's display name for spacebar text in its locale.
     91     //        isAdditionalSubtype (T=true, F=false)
     92     // locale layout  |  Middle      Full
     93     // ------ ------- - --------- ----------------------
     94     //  en_US qwerty  F  English   English (US)           exception
     95     //  en_GB qwerty  F  English   English (UK)           exception
     96     //  es_US spanish F  Espaol   Espaol (EE.UU.)       exception
     97     //  fr    azerty  F  Franais  Franais
     98     //  fr_CA qwerty  F  Franais  Franais (Canada)
     99     //  fr_CH swiss   F  Franais  Franais (Suisse)
    100     //  de    qwertz  F  Deutsch   Deutsch
    101     //  de_CH swiss   T  Deutsch   Deutsch (Schweiz)
    102     //  zz    qwerty  F  QWERTY    QWERTY
    103     //  fr    qwertz  T  Franais  Franais
    104     //  de    qwerty  T  Deutsch   Deutsch
    105     //  en_US azerty  T  English   English (US)
    106     //  zz    azerty  T  AZERTY    AZERTY
    107     // Get the RichInputMethodSubtype's full display name in its locale.
    108     @Nonnull
    109     public String getFullDisplayName() {
    110         if (isNoLanguage()) {
    111             return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
    112         }
    113         return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(mSubtype.getLocale());
    114     }
    115 
    116     // Get the RichInputMethodSubtype's middle display name in its locale.
    117     @Nonnull
    118     public String getMiddleDisplayName() {
    119         if (isNoLanguage()) {
    120             return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
    121         }
    122         return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(mSubtype.getLocale());
    123     }
    124 
    125     @Override
    126     public boolean equals(final Object o) {
    127         if (!(o instanceof RichInputMethodSubtype)) {
    128             return false;
    129         }
    130         final RichInputMethodSubtype other = (RichInputMethodSubtype)o;
    131         return mSubtype.equals(other.mSubtype) && mLocale.equals(other.mLocale);
    132     }
    133 
    134     @Override
    135     public int hashCode() {
    136         return mSubtype.hashCode() + mLocale.hashCode();
    137     }
    138 
    139     @Override
    140     public String toString() {
    141         return "Multi-lingual subtype: " + mSubtype + ", " + mLocale;
    142     }
    143 
    144     @Nonnull
    145     public Locale getLocale() {
    146         return mLocale;
    147     }
    148 
    149     @Nonnull
    150     public Locale getOriginalLocale() {
    151         return mOriginalLocale;
    152     }
    153 
    154     public boolean isRtlSubtype() {
    155         // The subtype is considered RTL if the language of the main subtype is RTL.
    156         return LocaleUtils.isRtlLanguage(mLocale);
    157     }
    158 
    159     // TODO: remove this method
    160     @Nonnull
    161     public InputMethodSubtype getRawSubtype() { return mSubtype; }
    162 
    163     @Nonnull
    164     public String getKeyboardLayoutSetName() {
    165         return SubtypeLocaleUtils.getKeyboardLayoutSetName(mSubtype);
    166     }
    167 
    168     public static RichInputMethodSubtype getRichInputMethodSubtype(
    169             @Nullable final InputMethodSubtype subtype) {
    170         if (subtype == null) {
    171             return getNoLanguageSubtype();
    172         } else {
    173             return new RichInputMethodSubtype(subtype);
    174         }
    175     }
    176 
    177     // Dummy no language QWERTY subtype. See {@link R.xml.method}.
    178     private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3;
    179     private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE =
    180             "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY
    181             + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
    182             + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
    183             + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
    184     @Nonnull
    185     private static final RichInputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
    186             new RichInputMethodSubtype(InputMethodSubtypeCompatUtils.newInputMethodSubtype(
    187                     R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
    188                     SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
    189                     EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE,
    190                     false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
    191                     SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE));
    192     // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
    193     // Dummy Emoji subtype. See {@link R.xml.method}.
    194     private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0;
    195     private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE =
    196             "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
    197             + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
    198     @Nonnull
    199     private static final RichInputMethodSubtype DUMMY_EMOJI_SUBTYPE = new RichInputMethodSubtype(
    200             InputMethodSubtypeCompatUtils.newInputMethodSubtype(
    201                     R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
    202                     SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
    203                     EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE,
    204                     false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
    205                     SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE));
    206     private static RichInputMethodSubtype sNoLanguageSubtype;
    207     private static RichInputMethodSubtype sEmojiSubtype;
    208 
    209     @Nonnull
    210     public static RichInputMethodSubtype getNoLanguageSubtype() {
    211         RichInputMethodSubtype noLanguageSubtype = sNoLanguageSubtype;
    212         if (noLanguageSubtype == null) {
    213             final InputMethodSubtype rawNoLanguageSubtype = RichInputMethodManager.getInstance()
    214                     .findSubtypeByLocaleAndKeyboardLayoutSet(
    215                             SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
    216             if (rawNoLanguageSubtype != null) {
    217                 noLanguageSubtype = new RichInputMethodSubtype(rawNoLanguageSubtype);
    218             }
    219         }
    220         if (noLanguageSubtype != null) {
    221             sNoLanguageSubtype = noLanguageSubtype;
    222             return noLanguageSubtype;
    223         }
    224         Log.w(TAG, "Can't find any language with QWERTY subtype");
    225         Log.w(TAG, "No input method subtype found; returning dummy subtype: "
    226                 + DUMMY_NO_LANGUAGE_SUBTYPE);
    227         return DUMMY_NO_LANGUAGE_SUBTYPE;
    228     }
    229 
    230     @Nonnull
    231     public static RichInputMethodSubtype getEmojiSubtype() {
    232         RichInputMethodSubtype emojiSubtype = sEmojiSubtype;
    233         if (emojiSubtype == null) {
    234             final InputMethodSubtype rawEmojiSubtype = RichInputMethodManager.getInstance()
    235                     .findSubtypeByLocaleAndKeyboardLayoutSet(
    236                             SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
    237             if (rawEmojiSubtype != null) {
    238                 emojiSubtype = new RichInputMethodSubtype(rawEmojiSubtype);
    239             }
    240         }
    241         if (emojiSubtype != null) {
    242             sEmojiSubtype = emojiSubtype;
    243             return emojiSubtype;
    244         }
    245         Log.w(TAG, "Can't find emoji subtype");
    246         Log.w(TAG, "No input method subtype found; returning dummy subtype: "
    247                 + DUMMY_EMOJI_SUBTYPE);
    248         return DUMMY_EMOJI_SUBTYPE;
    249     }
    250 }
    251