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.Configuration; 24 import android.content.res.Resources; 25 import android.inputmethodservice.InputMethodService; 26 import android.net.ConnectivityManager; 27 import android.net.NetworkInfo; 28 import android.os.AsyncTask; 29 import android.os.IBinder; 30 import android.util.Log; 31 import android.view.inputmethod.InputMethodInfo; 32 import android.view.inputmethod.InputMethodManager; 33 import android.view.inputmethod.InputMethodSubtype; 34 35 import com.android.inputmethod.keyboard.KeyboardSwitcher; 36 37 import java.util.List; 38 import java.util.Locale; 39 import java.util.Map; 40 41 public final class SubtypeSwitcher { 42 private static boolean DBG = LatinImeLogger.sDBG; 43 private static final String TAG = SubtypeSwitcher.class.getSimpleName(); 44 45 private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); 46 private /* final */ InputMethodManager mImm; 47 private /* final */ Resources mResources; 48 private /* final */ ConnectivityManager mConnectivityManager; 49 50 /*-----------------------------------------------------------*/ 51 // Variants which should be changed only by reload functions. 52 private NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage(); 53 private InputMethodInfo mShortcutInputMethodInfo; 54 private InputMethodSubtype mShortcutSubtype; 55 private InputMethodSubtype mNoLanguageSubtype; 56 // Note: This variable is always non-null after {@link #initialize(LatinIME)}. 57 private InputMethodSubtype mCurrentSubtype; 58 private Locale mCurrentSystemLocale; 59 /*-----------------------------------------------------------*/ 60 61 private boolean mIsNetworkConnected; 62 63 static final class NeedsToDisplayLanguage { 64 private int mEnabledSubtypeCount; 65 private boolean mIsSystemLanguageSameAsInputLanguage; 66 67 public boolean getValue() { 68 return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage; 69 } 70 71 public void updateEnabledSubtypeCount(final int count) { 72 mEnabledSubtypeCount = count; 73 } 74 75 public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) { 76 mIsSystemLanguageSameAsInputLanguage = isSame; 77 } 78 } 79 80 public static SubtypeSwitcher getInstance() { 81 return sInstance; 82 } 83 84 public static void init(final Context context) { 85 SubtypeLocale.init(context); 86 sInstance.initialize(context); 87 sInstance.updateAllParameters(context); 88 } 89 90 private SubtypeSwitcher() { 91 // Intentional empty constructor for singleton. 92 } 93 94 private void initialize(final Context service) { 95 mResources = service.getResources(); 96 mImm = ImfUtils.getInputMethodManager(service); 97 mConnectivityManager = (ConnectivityManager) service.getSystemService( 98 Context.CONNECTIVITY_SERVICE); 99 mCurrentSystemLocale = mResources.getConfiguration().locale; 100 mNoLanguageSubtype = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( 101 service, SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY); 102 mCurrentSubtype = ImfUtils.getCurrentInputMethodSubtype(service, mNoLanguageSubtype); 103 if (mNoLanguageSubtype == null) { 104 throw new RuntimeException("Can't find no lanugage with QWERTY subtype"); 105 } 106 107 final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo(); 108 mIsNetworkConnected = (info != null && info.isConnected()); 109 } 110 111 // Update all parameters stored in SubtypeSwitcher. 112 // Only configuration changed event is allowed to call this because this is heavy. 113 private void updateAllParameters(final Context context) { 114 mCurrentSystemLocale = mResources.getConfiguration().locale; 115 updateSubtype(ImfUtils.getCurrentInputMethodSubtype(context, mNoLanguageSubtype)); 116 updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled(); 117 } 118 119 /** 120 * Update parameters which are changed outside LatinIME. This parameters affect UI so they 121 * should be updated every time onStartInputView. 122 * 123 * @return true if the current subtype is enabled. 124 */ 125 public boolean updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled() { 126 final boolean currentSubtypeEnabled = 127 updateEnabledSubtypesAndReturnIfEnabled(mCurrentSubtype); 128 updateShortcutIME(); 129 return currentSubtypeEnabled; 130 } 131 132 /** 133 * Update enabled subtypes from the framework. 134 * 135 * @param subtype the subtype to be checked 136 * @return true if the {@code subtype} is enabled. 137 */ 138 private boolean updateEnabledSubtypesAndReturnIfEnabled(final InputMethodSubtype subtype) { 139 final List<InputMethodSubtype> enabledSubtypesOfThisIme = 140 mImm.getEnabledInputMethodSubtypeList(null, true); 141 mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size()); 142 143 for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) { 144 if (ims.equals(subtype)) { 145 return true; 146 } 147 } 148 if (DBG) { 149 Log.w(TAG, "Subtype: " + subtype.getLocale() + "/" + subtype.getExtraValue() 150 + " was disabled"); 151 } 152 return false; 153 } 154 155 private void updateShortcutIME() { 156 if (DBG) { 157 Log.d(TAG, "Update shortcut IME from : " 158 + (mShortcutInputMethodInfo == null 159 ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " 160 + (mShortcutSubtype == null ? "<null>" : ( 161 mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); 162 } 163 // TODO: Update an icon for shortcut IME 164 final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = 165 mImm.getShortcutInputMethodsAndSubtypes(); 166 mShortcutInputMethodInfo = null; 167 mShortcutSubtype = null; 168 for (final InputMethodInfo imi : shortcuts.keySet()) { 169 final List<InputMethodSubtype> subtypes = shortcuts.get(imi); 170 // TODO: Returns the first found IMI for now. Should handle all shortcuts as 171 // appropriate. 172 mShortcutInputMethodInfo = imi; 173 // TODO: Pick up the first found subtype for now. Should handle all subtypes 174 // as appropriate. 175 mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null; 176 break; 177 } 178 if (DBG) { 179 Log.d(TAG, "Update shortcut IME to : " 180 + (mShortcutInputMethodInfo == null 181 ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " 182 + (mShortcutSubtype == null ? "<null>" : ( 183 mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); 184 } 185 } 186 187 // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. 188 public void updateSubtype(InputMethodSubtype newSubtype) { 189 if (DBG) { 190 Log.w(TAG, "onCurrentInputMethodSubtypeChanged: to: " 191 + newSubtype.getLocale() + "/" + newSubtype.getExtraValue() + ", from: " 192 + mCurrentSubtype.getLocale() + "/" + mCurrentSubtype.getExtraValue()); 193 } 194 195 final Locale newLocale = SubtypeLocale.getSubtypeLocale(newSubtype); 196 mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage( 197 mCurrentSystemLocale.equals(newLocale)); 198 199 if (newSubtype.equals(mCurrentSubtype)) return; 200 201 mCurrentSubtype = newSubtype; 202 updateShortcutIME(); 203 } 204 205 //////////////////////////// 206 // Shortcut IME functions // 207 //////////////////////////// 208 209 public void switchToShortcutIME(final InputMethodService context) { 210 if (mShortcutInputMethodInfo == null) { 211 return; 212 } 213 214 final String imiId = mShortcutInputMethodInfo.getId(); 215 switchToTargetIME(imiId, mShortcutSubtype, context); 216 } 217 218 private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype, 219 final InputMethodService context) { 220 final IBinder token = context.getWindow().getWindow().getAttributes().token; 221 if (token == null) { 222 return; 223 } 224 final InputMethodManager imm = mImm; 225 new AsyncTask<Void, Void, Void>() { 226 @Override 227 protected Void doInBackground(Void... params) { 228 imm.setInputMethodAndSubtype(token, imiId, subtype); 229 return null; 230 } 231 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 232 } 233 234 public boolean isShortcutImeEnabled() { 235 if (mShortcutInputMethodInfo == null) { 236 return false; 237 } 238 if (mShortcutSubtype == null) { 239 return true; 240 } 241 final boolean allowsImplicitlySelectedSubtypes = true; 242 for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList( 243 mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) { 244 if (enabledSubtype.equals(mShortcutSubtype)) { 245 return true; 246 } 247 } 248 return false; 249 } 250 251 public boolean isShortcutImeReady() { 252 if (mShortcutInputMethodInfo == null) 253 return false; 254 if (mShortcutSubtype == null) 255 return true; 256 if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) { 257 return mIsNetworkConnected; 258 } 259 return true; 260 } 261 262 public void onNetworkStateChanged(final Intent intent) { 263 final boolean noConnection = intent.getBooleanExtra( 264 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); 265 mIsNetworkConnected = !noConnection; 266 267 KeyboardSwitcher.getInstance().onNetworkStateChanged(); 268 } 269 270 ////////////////////////////////// 271 // Subtype Switching functions // 272 ////////////////////////////////// 273 274 public boolean needsToDisplayLanguage(final Locale keyboardLocale) { 275 if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) { 276 return true; 277 } 278 if (!keyboardLocale.equals(getCurrentSubtypeLocale())) { 279 return false; 280 } 281 return mNeedsToDisplayLanguage.getValue(); 282 } 283 284 public Locale getCurrentSubtypeLocale() { 285 return SubtypeLocale.getSubtypeLocale(mCurrentSubtype); 286 } 287 288 public boolean onConfigurationChanged(final Configuration conf, final Context context) { 289 final Locale systemLocale = conf.locale; 290 final boolean systemLocaleChanged = !systemLocale.equals(mCurrentSystemLocale); 291 // If system configuration was changed, update all parameters. 292 if (systemLocaleChanged) { 293 updateAllParameters(context); 294 } 295 return systemLocaleChanged; 296 } 297 298 public InputMethodSubtype getCurrentSubtype() { 299 return mCurrentSubtype; 300 } 301 302 public InputMethodSubtype getNoLanguageSubtype() { 303 return mNoLanguageSubtype; 304 } 305 } 306