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.keyboard.KeyboardSwitcher; 36 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; 37 38 import java.util.List; 39 import java.util.Locale; 40 import java.util.Map; 41 42 public final class SubtypeSwitcher { 43 private static boolean DBG = LatinImeLogger.sDBG; 44 private static final String TAG = SubtypeSwitcher.class.getSimpleName(); 45 46 private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); 47 48 private /* final */ RichInputMethodManager mRichImm; 49 private /* final */ Resources mResources; 50 private /* final */ ConnectivityManager mConnectivityManager; 51 52 private final NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage(); 53 private InputMethodInfo mShortcutInputMethodInfo; 54 private InputMethodSubtype mShortcutSubtype; 55 private InputMethodSubtype mNoLanguageSubtype; 56 private InputMethodSubtype mEmojiSubtype; 57 private boolean mIsNetworkConnected; 58 59 // Dummy no language QWERTY subtype. See {@link R.xml.method}. 60 private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = new InputMethodSubtype( 61 R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark, 62 SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet=" 63 + SubtypeLocaleUtils.QWERTY 64 + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE 65 + ",EnabledWhenDefaultIsNotAsciiCapable," 66 + Constants.Subtype.ExtraValue.EMOJI_CAPABLE, 67 false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */); 68 // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}. 69 // Dummy Emoji subtype. See {@link R.xml.method}. 70 private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE = new InputMethodSubtype( 71 R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark, 72 SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet=" 73 + SubtypeLocaleUtils.EMOJI + "," 74 + Constants.Subtype.ExtraValue.EMOJI_CAPABLE, 75 false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */); 76 77 static final class NeedsToDisplayLanguage { 78 private int mEnabledSubtypeCount; 79 private boolean mIsSystemLanguageSameAsInputLanguage; 80 81 public boolean getValue() { 82 return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage; 83 } 84 85 public void updateEnabledSubtypeCount(final int count) { 86 mEnabledSubtypeCount = count; 87 } 88 89 public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) { 90 mIsSystemLanguageSameAsInputLanguage = isSame; 91 } 92 } 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 mConnectivityManager = (ConnectivityManager) context.getSystemService( 115 Context.CONNECTIVITY_SERVICE); 116 117 final NetworkInfo info = mConnectivityManager.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 mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size()); 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 mNeedsToDisplayLanguage.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 if (mShortcutInputMethodInfo == null) { 217 return false; 218 } 219 if (mShortcutSubtype == null) { 220 return true; 221 } 222 return mRichImm.checkIfSubtypeBelongsToImeAndEnabled( 223 mShortcutInputMethodInfo, mShortcutSubtype); 224 } 225 226 public boolean isShortcutImeReady() { 227 if (mShortcutInputMethodInfo == null) 228 return false; 229 if (mShortcutSubtype == null) 230 return true; 231 if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) { 232 return mIsNetworkConnected; 233 } 234 return true; 235 } 236 237 public void onNetworkStateChanged(final Intent intent) { 238 final boolean noConnection = intent.getBooleanExtra( 239 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); 240 mIsNetworkConnected = !noConnection; 241 242 KeyboardSwitcher.getInstance().onNetworkStateChanged(); 243 } 244 245 ////////////////////////////////// 246 // Subtype Switching functions // 247 ////////////////////////////////// 248 249 public boolean needsToDisplayLanguage(final Locale keyboardLocale) { 250 if (keyboardLocale.toString().equals(SubtypeLocaleUtils.NO_LANGUAGE)) { 251 return true; 252 } 253 if (!keyboardLocale.equals(getCurrentSubtypeLocale())) { 254 return false; 255 } 256 return mNeedsToDisplayLanguage.getValue(); 257 } 258 259 private static Locale sForcedLocaleForTesting = null; 260 @UsedForTesting 261 void forceLocale(final Locale locale) { 262 sForcedLocaleForTesting = locale; 263 } 264 265 public Locale getCurrentSubtypeLocale() { 266 if (null != sForcedLocaleForTesting) return sForcedLocaleForTesting; 267 return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype()); 268 } 269 270 public InputMethodSubtype getCurrentSubtype() { 271 return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype()); 272 } 273 274 public InputMethodSubtype getNoLanguageSubtype() { 275 if (mNoLanguageSubtype == null) { 276 mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( 277 SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY); 278 } 279 if (mNoLanguageSubtype != null) { 280 return mNoLanguageSubtype; 281 } 282 Log.w(TAG, "Can't find no lanugage with QWERTY subtype"); 283 Log.w(TAG, "No input method subtype found; return dummy subtype: " 284 + DUMMY_NO_LANGUAGE_SUBTYPE); 285 return DUMMY_NO_LANGUAGE_SUBTYPE; 286 } 287 288 public InputMethodSubtype getEmojiSubtype() { 289 if (mEmojiSubtype == null) { 290 mEmojiSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( 291 SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI); 292 } 293 if (mEmojiSubtype != null) { 294 return mEmojiSubtype; 295 } 296 Log.w(TAG, "Can't find Emoji subtype"); 297 Log.w(TAG, "No input method subtype found; return dummy subtype: " + DUMMY_EMOJI_SUBTYPE); 298 return DUMMY_EMOJI_SUBTYPE; 299 } 300 } 301