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 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 */ RichInputMethodManager mRichImm; 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 /*-----------------------------------------------------------*/ 57 58 private boolean mIsNetworkConnected; 59 60 static final class NeedsToDisplayLanguage { 61 private int mEnabledSubtypeCount; 62 private boolean mIsSystemLanguageSameAsInputLanguage; 63 getValue()64 public boolean getValue() { 65 return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage; 66 } 67 updateEnabledSubtypeCount(final int count)68 public void updateEnabledSubtypeCount(final int count) { 69 mEnabledSubtypeCount = count; 70 } 71 updateIsSystemLanguageSameAsInputLanguage(final boolean isSame)72 public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) { 73 mIsSystemLanguageSameAsInputLanguage = isSame; 74 } 75 } 76 getInstance()77 public static SubtypeSwitcher getInstance() { 78 return sInstance; 79 } 80 init(final Context context)81 public static void init(final Context context) { 82 SubtypeLocale.init(context); 83 RichInputMethodManager.init(context); 84 sInstance.initialize(context); 85 } 86 SubtypeSwitcher()87 private SubtypeSwitcher() { 88 // Intentional empty constructor for singleton. 89 } 90 initialize(final Context context)91 private void initialize(final Context context) { 92 if (mResources != null) { 93 return; 94 } 95 mResources = context.getResources(); 96 mRichImm = RichInputMethodManager.getInstance(); 97 mConnectivityManager = (ConnectivityManager) context.getSystemService( 98 Context.CONNECTIVITY_SERVICE); 99 mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( 100 SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY); 101 if (mNoLanguageSubtype == null) { 102 throw new RuntimeException("Can't find no lanugage with QWERTY subtype"); 103 } 104 105 final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo(); 106 mIsNetworkConnected = (info != null && info.isConnected()); 107 108 onSubtypeChanged(getCurrentSubtype()); 109 updateParametersOnStartInputView(); 110 } 111 112 /** 113 * Update parameters which are changed outside LatinIME. This parameters affect UI so that they 114 * should be updated every time onStartInputView is called. 115 */ updateParametersOnStartInputView()116 public void updateParametersOnStartInputView() { 117 final List<InputMethodSubtype> enabledSubtypesOfThisIme = 118 mRichImm.getMyEnabledInputMethodSubtypeList(true); 119 mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size()); 120 updateShortcutIME(); 121 } 122 updateShortcutIME()123 private void updateShortcutIME() { 124 if (DBG) { 125 Log.d(TAG, "Update shortcut IME from : " 126 + (mShortcutInputMethodInfo == null 127 ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " 128 + (mShortcutSubtype == null ? "<null>" : ( 129 mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); 130 } 131 // TODO: Update an icon for shortcut IME 132 final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = 133 mRichImm.getInputMethodManager().getShortcutInputMethodsAndSubtypes(); 134 mShortcutInputMethodInfo = null; 135 mShortcutSubtype = null; 136 for (final InputMethodInfo imi : shortcuts.keySet()) { 137 final List<InputMethodSubtype> subtypes = shortcuts.get(imi); 138 // TODO: Returns the first found IMI for now. Should handle all shortcuts as 139 // appropriate. 140 mShortcutInputMethodInfo = imi; 141 // TODO: Pick up the first found subtype for now. Should handle all subtypes 142 // as appropriate. 143 mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null; 144 break; 145 } 146 if (DBG) { 147 Log.d(TAG, "Update shortcut IME to : " 148 + (mShortcutInputMethodInfo == null 149 ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " 150 + (mShortcutSubtype == null ? "<null>" : ( 151 mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); 152 } 153 } 154 155 // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. onSubtypeChanged(final InputMethodSubtype newSubtype)156 public void onSubtypeChanged(final InputMethodSubtype newSubtype) { 157 if (DBG) { 158 Log.w(TAG, "onSubtypeChanged: " + SubtypeLocale.getSubtypeDisplayName(newSubtype)); 159 } 160 161 final Locale newLocale = SubtypeLocale.getSubtypeLocale(newSubtype); 162 final Locale systemLocale = mResources.getConfiguration().locale; 163 final boolean sameLocale = systemLocale.equals(newLocale); 164 final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage()); 165 final boolean implicitlyEnabled = 166 mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype); 167 mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage( 168 sameLocale || (sameLanguage && implicitlyEnabled)); 169 170 updateShortcutIME(); 171 } 172 173 //////////////////////////// 174 // Shortcut IME functions // 175 //////////////////////////// 176 switchToShortcutIME(final InputMethodService context)177 public void switchToShortcutIME(final InputMethodService context) { 178 if (mShortcutInputMethodInfo == null) { 179 return; 180 } 181 182 final String imiId = mShortcutInputMethodInfo.getId(); 183 switchToTargetIME(imiId, mShortcutSubtype, context); 184 } 185 switchToTargetIME(final String imiId, final InputMethodSubtype subtype, final InputMethodService context)186 private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype, 187 final InputMethodService context) { 188 final IBinder token = context.getWindow().getWindow().getAttributes().token; 189 if (token == null) { 190 return; 191 } 192 final InputMethodManager imm = mRichImm.getInputMethodManager(); 193 new AsyncTask<Void, Void, Void>() { 194 @Override 195 protected Void doInBackground(Void... params) { 196 imm.setInputMethodAndSubtype(token, imiId, subtype); 197 return null; 198 } 199 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 200 } 201 isShortcutImeEnabled()202 public boolean isShortcutImeEnabled() { 203 if (mShortcutInputMethodInfo == null) { 204 return false; 205 } 206 if (mShortcutSubtype == null) { 207 return true; 208 } 209 return mRichImm.checkIfSubtypeBelongsToImeAndEnabled( 210 mShortcutInputMethodInfo, mShortcutSubtype); 211 } 212 isShortcutImeReady()213 public boolean isShortcutImeReady() { 214 if (mShortcutInputMethodInfo == null) 215 return false; 216 if (mShortcutSubtype == null) 217 return true; 218 if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) { 219 return mIsNetworkConnected; 220 } 221 return true; 222 } 223 onNetworkStateChanged(final Intent intent)224 public void onNetworkStateChanged(final Intent intent) { 225 final boolean noConnection = intent.getBooleanExtra( 226 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); 227 mIsNetworkConnected = !noConnection; 228 229 KeyboardSwitcher.getInstance().onNetworkStateChanged(); 230 } 231 232 ////////////////////////////////// 233 // Subtype Switching functions // 234 ////////////////////////////////// 235 needsToDisplayLanguage(final Locale keyboardLocale)236 public boolean needsToDisplayLanguage(final Locale keyboardLocale) { 237 if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) { 238 return true; 239 } 240 if (!keyboardLocale.equals(getCurrentSubtypeLocale())) { 241 return false; 242 } 243 return mNeedsToDisplayLanguage.getValue(); 244 } 245 246 private static Locale sForcedLocaleForTesting = null; 247 @UsedForTesting forceLocale(final Locale locale)248 void forceLocale(final Locale locale) { 249 sForcedLocaleForTesting = locale; 250 } 251 getCurrentSubtypeLocale()252 public Locale getCurrentSubtypeLocale() { 253 if (null != sForcedLocaleForTesting) return sForcedLocaleForTesting; 254 return SubtypeLocale.getSubtypeLocale(getCurrentSubtype()); 255 } 256 getCurrentSubtype()257 public InputMethodSubtype getCurrentSubtype() { 258 return mRichImm.getCurrentInputMethodSubtype(mNoLanguageSubtype); 259 } 260 getNoLanguageSubtype()261 public InputMethodSubtype getNoLanguageSubtype() { 262 return mNoLanguageSubtype; 263 } 264 } 265