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(); 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 RichInputMethodSubtype(@onnull final InputMethodSubtype subtype)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. getExtraValueOf(@onnull final String key)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. getMode()78 public String getMode() { 79 return mSubtype.getMode(); 80 } 81 isNoLanguage()82 public boolean isNoLanguage() { 83 return SubtypeLocaleUtils.NO_LANGUAGE.equals(mSubtype.getLocale()); 84 } 85 getNameForLogging()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 Español Español (EE.UU.) exception 97 // fr azerty F Français Français 98 // fr_CA qwerty F Français Français (Canada) 99 // fr_CH swiss F Français Français (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 Français Français 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 getFullDisplayName()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 getMiddleDisplayName()118 public String getMiddleDisplayName() { 119 if (isNoLanguage()) { 120 return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype); 121 } 122 return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(mSubtype.getLocale()); 123 } 124 125 @Override equals(final Object o)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 hashCode()135 public int hashCode() { 136 return mSubtype.hashCode() + mLocale.hashCode(); 137 } 138 139 @Override toString()140 public String toString() { 141 return "Multi-lingual subtype: " + mSubtype + ", " + mLocale; 142 } 143 144 @Nonnull getLocale()145 public Locale getLocale() { 146 return mLocale; 147 } 148 149 @Nonnull getOriginalLocale()150 public Locale getOriginalLocale() { 151 return mOriginalLocale; 152 } 153 isRtlSubtype()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 getRawSubtype()161 public InputMethodSubtype getRawSubtype() { return mSubtype; } 162 163 @Nonnull getKeyboardLayoutSetName()164 public String getKeyboardLayoutSetName() { 165 return SubtypeLocaleUtils.getKeyboardLayoutSetName(mSubtype); 166 } 167 getRichInputMethodSubtype( @ullable final InputMethodSubtype subtype)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 // Placeholer for no language QWERTY subtype. See {@link R.xml.method}. 178 private static final int SUBTYPE_ID_OF_PLACEHOLDER_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3; 179 private static final String EXTRA_VALUE_OF_PLACEHOLDER_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 PLACEHOLDER_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_PLACEHOLDER_NO_LANGUAGE_SUBTYPE, 190 false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, 191 SUBTYPE_ID_OF_PLACEHOLDER_NO_LANGUAGE_SUBTYPE)); 192 // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}. 193 // Placeholder Emoji subtype. See {@link R.xml.method}. 194 private static final int SUBTYPE_ID_OF_PLACEHOLDER_EMOJI_SUBTYPE = 0xd78b2ed0; 195 private static final String EXTRA_VALUE_OF_PLACEHOLDER_EMOJI_SUBTYPE = 196 "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI 197 + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE; 198 @Nonnull 199 private static final RichInputMethodSubtype PLACEHOLDER_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_PLACEHOLDER_EMOJI_SUBTYPE, 204 false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, 205 SUBTYPE_ID_OF_PLACEHOLDER_EMOJI_SUBTYPE)); 206 private static RichInputMethodSubtype sNoLanguageSubtype; 207 private static RichInputMethodSubtype sEmojiSubtype; 208 209 @Nonnull getNoLanguageSubtype()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 placeholder subtype: " 226 + PLACEHOLDER_NO_LANGUAGE_SUBTYPE); 227 return PLACEHOLDER_NO_LANGUAGE_SUBTYPE; 228 } 229 230 @Nonnull getEmojiSubtype()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 placeholder subtype: " 247 + PLACEHOLDER_EMOJI_SUBTYPE); 248 return PLACEHOLDER_EMOJI_SUBTYPE; 249 } 250 } 251