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