1 /* 2 * Copyright (C) 2010 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.Constants.Subtype.ExtraValue.REQ_NETWORK_CONNECTIVITY; 20 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.res.Resources; 24 import android.inputmethodservice.InputMethodService; 25 import android.net.ConnectivityManager; 26 import android.net.NetworkInfo; 27 import android.os.AsyncTask; 28 import android.os.IBinder; 29 import android.util.Log; 30 import android.view.inputmethod.InputMethodInfo; 31 import android.view.inputmethod.InputMethodManager; 32 import android.view.inputmethod.InputMethodSubtype; 33 34 import com.android.inputmethod.annotations.UsedForTesting; 35 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; 36 import com.android.inputmethod.keyboard.KeyboardSwitcher; 37 import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper; 38 import com.android.inputmethod.latin.define.DebugFlags; 39 import com.android.inputmethod.latin.utils.LocaleUtils; 40 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; 41 42 import java.util.HashSet; 43 import java.util.List; 44 import java.util.Locale; 45 import java.util.Map; 46 import java.util.Set; 47 48 public final class SubtypeSwitcher { 49 private static boolean DBG = DebugFlags.DEBUG_ENABLED; 50 private static final String TAG = SubtypeSwitcher.class.getSimpleName(); 51 52 private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); 53 54 private /* final */ RichInputMethodManager mRichImm; 55 private /* final */ Resources mResources; 56 57 private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper = 58 new LanguageOnSpacebarHelper(); 59 private InputMethodInfo mShortcutInputMethodInfo; 60 private InputMethodSubtype mShortcutSubtype; 61 private InputMethodSubtype mNoLanguageSubtype; 62 private InputMethodSubtype mEmojiSubtype; 63 private boolean mIsNetworkConnected; 64 65 private static final String KEYBOARD_MODE = "keyboard"; 66 // Dummy no language QWERTY subtype. See {@link R.xml.method}. 67 private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3; 68 private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 69 "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY 70 + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE 71 + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE 72 + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE; 73 private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = 74 InputMethodSubtypeCompatUtils.newInputMethodSubtype( 75 R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark, 76 SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE, 77 EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE, 78 false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, 79 SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE); 80 // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}. 81 // Dummy Emoji subtype. See {@link R.xml.method}. 82 private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0; 83 private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE = 84 "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI 85 + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE; 86 private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE = 87 InputMethodSubtypeCompatUtils.newInputMethodSubtype( 88 R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark, 89 SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE, 90 EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE, 91 false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, 92 SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE); 93 94 public static SubtypeSwitcher getInstance() { 95 return sInstance; 96 } 97 98 public static void init(final Context context) { 99 SubtypeLocaleUtils.init(context); 100 RichInputMethodManager.init(context); 101 sInstance.initialize(context); 102 } 103 104 private SubtypeSwitcher() { 105 // Intentional empty constructor for singleton. 106 } 107 108 private void initialize(final Context context) { 109 if (mResources != null) { 110 return; 111 } 112 mResources = context.getResources(); 113 mRichImm = RichInputMethodManager.getInstance(); 114 ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService( 115 Context.CONNECTIVITY_SERVICE); 116 117 final NetworkInfo info = connectivityManager.getActiveNetworkInfo(); 118 mIsNetworkConnected = (info != null && info.isConnected()); 119 120 onSubtypeChanged(getCurrentSubtype()); 121 updateParametersOnStartInputView(); 122 } 123 124 /** 125 * Update parameters which are changed outside LatinIME. This parameters affect UI so that they 126 * should be updated every time onStartInputView is called. 127 */ 128 public void updateParametersOnStartInputView() { 129 final List<InputMethodSubtype> enabledSubtypesOfThisIme = 130 mRichImm.getMyEnabledInputMethodSubtypeList(true); 131 mLanguageOnSpacebarHelper.updateEnabledSubtypes(enabledSubtypesOfThisIme); 132 updateShortcutIME(); 133 } 134 135 private void updateShortcutIME() { 136 if (DBG) { 137 Log.d(TAG, "Update shortcut IME from : " 138 + (mShortcutInputMethodInfo == null 139 ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " 140 + (mShortcutSubtype == null ? "<null>" : ( 141 mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); 142 } 143 // TODO: Update an icon for shortcut IME 144 final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = 145 mRichImm.getInputMethodManager().getShortcutInputMethodsAndSubtypes(); 146 mShortcutInputMethodInfo = null; 147 mShortcutSubtype = null; 148 for (final InputMethodInfo imi : shortcuts.keySet()) { 149 final List<InputMethodSubtype> subtypes = shortcuts.get(imi); 150 // TODO: Returns the first found IMI for now. Should handle all shortcuts as 151 // appropriate. 152 mShortcutInputMethodInfo = imi; 153 // TODO: Pick up the first found subtype for now. Should handle all subtypes 154 // as appropriate. 155 mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null; 156 break; 157 } 158 if (DBG) { 159 Log.d(TAG, "Update shortcut IME to : " 160 + (mShortcutInputMethodInfo == null 161 ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " 162 + (mShortcutSubtype == null ? "<null>" : ( 163 mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); 164 } 165 } 166 167 // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. 168 public void onSubtypeChanged(final InputMethodSubtype newSubtype) { 169 if (DBG) { 170 Log.w(TAG, "onSubtypeChanged: " 171 + SubtypeLocaleUtils.getSubtypeNameForLogging(newSubtype)); 172 } 173 174 final Locale newLocale = SubtypeLocaleUtils.getSubtypeLocale(newSubtype); 175 final Locale systemLocale = mResources.getConfiguration().locale; 176 final boolean sameLocale = systemLocale.equals(newLocale); 177 final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage()); 178 final boolean implicitlyEnabled = 179 mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype); 180 mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage( 181 sameLocale || (sameLanguage && implicitlyEnabled)); 182 183 updateShortcutIME(); 184 } 185 186 //////////////////////////// 187 // Shortcut IME functions // 188 //////////////////////////// 189 190 public void switchToShortcutIME(final InputMethodService context) { 191 if (mShortcutInputMethodInfo == null) { 192 return; 193 } 194 195 final String imiId = mShortcutInputMethodInfo.getId(); 196 switchToTargetIME(imiId, mShortcutSubtype, context); 197 } 198 199 private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype, 200 final InputMethodService context) { 201 final IBinder token = context.getWindow().getWindow().getAttributes().token; 202 if (token == null) { 203 return; 204 } 205 final InputMethodManager imm = mRichImm.getInputMethodManager(); 206 new AsyncTask<Void, Void, Void>() { 207 @Override 208 protected Void doInBackground(Void... params) { 209 imm.setInputMethodAndSubtype(token, imiId, subtype); 210 return null; 211 } 212 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 213 } 214 215 public boolean isShortcutImeEnabled() { 216 updateShortcutIME(); 217 if (mShortcutInputMethodInfo == null) { 218 return false; 219 } 220 if (mShortcutSubtype == null) { 221 return true; 222 } 223 return mRichImm.checkIfSubtypeBelongsToImeAndEnabled( 224 mShortcutInputMethodInfo, mShortcutSubtype); 225 } 226 227 public boolean isShortcutImeReady() { 228 updateShortcutIME(); 229 if (mShortcutInputMethodInfo == null) { 230 return false; 231 } 232 if (mShortcutSubtype == null) { 233 return true; 234 } 235 if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) { 236 return mIsNetworkConnected; 237 } 238 return true; 239 } 240 241 public void onNetworkStateChanged(final Intent intent) { 242 final boolean noConnection = intent.getBooleanExtra( 243 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); 244 mIsNetworkConnected = !noConnection; 245 246 KeyboardSwitcher.getInstance().onNetworkStateChanged(); 247 } 248 249 ////////////////////////////////// 250 // Subtype Switching functions // 251 ////////////////////////////////// 252 253 public int getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype) { 254 return mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(subtype); 255 } 256 257 public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() { 258 final Locale systemLocale = mResources.getConfiguration().locale; 259 final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>(); 260 final InputMethodManager inputMethodManager = mRichImm.getInputMethodManager(); 261 final List<InputMethodInfo> enabledInputMethodInfoList = 262 inputMethodManager.getEnabledInputMethodList(); 263 for (final InputMethodInfo info : enabledInputMethodInfoList) { 264 final List<InputMethodSubtype> enabledSubtypes = 265 inputMethodManager.getEnabledInputMethodSubtypeList( 266 info, true /* allowsImplicitlySelectedSubtypes */); 267 if (enabledSubtypes.isEmpty()) { 268 // An IME with no subtypes is found. 269 return false; 270 } 271 enabledSubtypesOfEnabledImes.addAll(enabledSubtypes); 272 } 273 for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) { 274 if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty() 275 && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) { 276 return false; 277 } 278 } 279 return true; 280 } 281 282 private static InputMethodSubtype sForcedSubtypeForTesting = null; 283 @UsedForTesting 284 void forceSubtype(final InputMethodSubtype subtype) { 285 sForcedSubtypeForTesting = subtype; 286 } 287 288 public Locale getCurrentSubtypeLocale() { 289 if (null != sForcedSubtypeForTesting) { 290 return LocaleUtils.constructLocaleFromString(sForcedSubtypeForTesting.getLocale()); 291 } 292 return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype()); 293 } 294 295 public InputMethodSubtype getCurrentSubtype() { 296 if (null != sForcedSubtypeForTesting) { 297 return sForcedSubtypeForTesting; 298 } 299 return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype()); 300 } 301 302 public InputMethodSubtype getNoLanguageSubtype() { 303 if (mNoLanguageSubtype == null) { 304 mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( 305 SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY); 306 } 307 if (mNoLanguageSubtype != null) { 308 return mNoLanguageSubtype; 309 } 310 Log.w(TAG, "Can't find any language with QWERTY subtype"); 311 Log.w(TAG, "No input method subtype found; returning dummy subtype: " 312 + DUMMY_NO_LANGUAGE_SUBTYPE); 313 return DUMMY_NO_LANGUAGE_SUBTYPE; 314 } 315 316 public InputMethodSubtype getEmojiSubtype() { 317 if (mEmojiSubtype == null) { 318 mEmojiSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( 319 SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI); 320 } 321 if (mEmojiSubtype != null) { 322 return mEmojiSubtype; 323 } 324 Log.w(TAG, "Can't find emoji subtype"); 325 Log.w(TAG, "No input method subtype found; returning dummy subtype: " 326 + DUMMY_EMOJI_SUBTYPE); 327 return DUMMY_EMOJI_SUBTYPE; 328 } 329 330 public String getCombiningRulesExtraValueOfCurrentSubtype() { 331 return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype()); 332 } 333 } 334