Newer
Older
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import static org.futo.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
import android.content.Context;
import android.content.SharedPreferences;
Tadashi G. Takaoka
committed
import android.inputmethodservice.InputMethodService;
import android.os.AsyncTask;
import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import org.futo.inputmethod.annotations.UsedForTesting;
import org.futo.inputmethod.compat.InputMethodManagerCompatWrapper;
import org.futo.inputmethod.compat.InputMethodSubtypeCompatUtils;
import org.futo.inputmethod.latin.settings.Settings;
import org.futo.inputmethod.latin.utils.AdditionalSubtypeUtils;
import org.futo.inputmethod.latin.utils.LanguageOnSpacebarUtils;
import org.futo.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
Tadashi G. Takaoka
committed
import java.util.Map;
import javax.annotation.Nonnull;
/**
* Enrichment class for InputMethodManager to simplify interaction and add functionality.
*/
// non final for easy mocking.
public class RichInputMethodManager {
private static final String TAG = RichInputMethodManager.class.getSimpleName();
Tadashi G. Takaoka
committed
private static final boolean DEBUG = false;
private RichInputMethodManager() {
// This utility class is not publicly instantiable.
}
private static final RichInputMethodManager sInstance = new RichInputMethodManager();
private InputMethodManagerCompatWrapper mImmWrapper;
private InputMethodInfoCache mInputMethodInfoCache;
private RichInputMethodSubtype mCurrentRichInputMethodSubtype;
Tadashi G. Takaoka
committed
private InputMethodInfo mShortcutInputMethodInfo;
private InputMethodSubtype mShortcutSubtype;
private static final int INDEX_NOT_FOUND = -1;
public static RichInputMethodManager getInstance() {
sInstance.checkInitialized();
return sInstance;
}
public static void init(final Context context) {
sInstance.initInternal(context);
}
private boolean isInitialized() {
return mImmWrapper != null;
}
private void checkInitialized() {
if (!isInitialized()) {
throw new RuntimeException(TAG + " is used before initialization");
}
}
private void initInternal(final Context context) {
if (isInitialized()) {
return;
}
mImmWrapper = new InputMethodManagerCompatWrapper(context);
mInputMethodInfoCache = new InputMethodInfoCache(
mImmWrapper.mImm, context.getPackageName());
// Initialize additional subtypes.
SubtypeLocaleUtils.init(context);
Tadashi G. Takaoka
committed
// Initialize the current input method subtype and the shortcut IME.
refreshSubtypeCaches();
public InputMethodSubtype[] getAdditionalSubtypes() {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(
return AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes);
}
public InputMethodManager getInputMethodManager() {
checkInitialized();
return mImmWrapper.mImm;
}
private static class InputMethodInfoCache {
private final InputMethodManager mImm;
private final String mImePackageName;
private InputMethodInfo mCachedThisImeInfo;
private final HashMap<InputMethodInfo, List<InputMethodSubtype>>
mCachedSubtypeListWithImplicitlySelected;
private final HashMap<InputMethodInfo, List<InputMethodSubtype>>
mCachedSubtypeListOnlyExplicitlySelected;
public InputMethodInfoCache(final InputMethodManager imm, final String imePackageName) {
mImm = imm;
mImePackageName = imePackageName;
mCachedSubtypeListWithImplicitlySelected = new HashMap<>();
mCachedSubtypeListOnlyExplicitlySelected = new HashMap<>();
public synchronized InputMethodInfo getInputMethodOfThisIme() {
if (mCachedThisImeInfo != null) {
return mCachedThisImeInfo;
}
for (final InputMethodInfo imi : mImm.getInputMethodList()) {
if (imi.getPackageName().equals(mImePackageName)) {
return imi;
}
}
throw new RuntimeException("Input method id for " + mImePackageName + " not found.");
}
public synchronized List<InputMethodSubtype> getEnabledInputMethodSubtypeList(
final InputMethodInfo imi, final boolean allowsImplicitlySelectedSubtypes) {
final HashMap<InputMethodInfo, List<InputMethodSubtype>> cache =
allowsImplicitlySelectedSubtypes
? mCachedSubtypeListWithImplicitlySelected
: mCachedSubtypeListOnlyExplicitlySelected;
final List<InputMethodSubtype> cachedList = cache.get(imi);
if (cachedList != null) {
return cachedList;
}
final List<InputMethodSubtype> result = mImm.getEnabledInputMethodSubtypeList(
imi, allowsImplicitlySelectedSubtypes);
cache.put(imi, result);
return result;
}
public synchronized void clear() {
mCachedThisImeInfo = null;
mCachedSubtypeListWithImplicitlySelected.clear();
mCachedSubtypeListOnlyExplicitlySelected.clear();
public InputMethodInfo getInputMethodInfoOfThisIme() {
return mInputMethodInfoCache.getInputMethodOfThisIme();
}
public String getInputMethodIdOfThisIme() {
return getInputMethodInfoOfThisIme().getId();
Tadashi G. Takaoka
committed
public void onSubtypeChanged(@Nonnull final InputMethodSubtype newSubtype) {
updateCurrentSubtype(newSubtype);
updateShortcutIme();
if (DEBUG) {
Tadashi G. Takaoka
committed
Log.w(TAG, "onSubtypeChanged: " + mCurrentRichInputMethodSubtype.getNameForLogging());
}
}
private static RichInputMethodSubtype sForcedSubtypeForTesting = null;
@UsedForTesting
Tadashi G. Takaoka
committed
static void forceSubtype(@Nonnull final InputMethodSubtype subtype) {
sForcedSubtypeForTesting = RichInputMethodSubtype.getRichInputMethodSubtype(subtype);
}
Tadashi G. Takaoka
committed
@Nonnull
if (null != sForcedSubtypeForTesting) {
}
Tadashi G. Takaoka
committed
@Nonnull
public RichInputMethodSubtype getCurrentSubtype() {
if (null != sForcedSubtypeForTesting) {
return sForcedSubtypeForTesting;
}
return mCurrentRichInputMethodSubtype;
}
public String getCombiningRulesExtraValueOfCurrentSubtype() {
return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype().getRawSubtype());
}
Tadashi G. Takaoka
committed
public void refreshSubtypeCaches() {
mInputMethodInfoCache.clear();
updateCurrentSubtype(Subtypes.INSTANCE.getActiveSubtype(mContext));
Tadashi G. Takaoka
committed
updateShortcutIme();
public boolean shouldOfferSwitchingToNextInputMethod(final IBinder binder,
boolean defaultValue) {
// Use the default value instead on Jelly Bean MR2 and previous where
// {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} isn't yet available
// and on KitKat where the API is still just a stub to return true always.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
return defaultValue;
}
return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder);
}
public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
final Locale systemLocale = mContext.getResources().getConfiguration().locale;
final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
final InputMethodManager inputMethodManager = getInputMethodManager();
final List<InputMethodInfo> enabledInputMethodInfoList =
inputMethodManager.getEnabledInputMethodList();
for (final InputMethodInfo info : enabledInputMethodInfoList) {
final List<InputMethodSubtype> enabledSubtypes =
inputMethodManager.getEnabledInputMethodSubtypeList(
info, true /* allowsImplicitlySelectedSubtypes */);
if (enabledSubtypes.isEmpty()) {
// An IME with no subtypes is found.
return false;
}
enabledSubtypesOfEnabledImes.addAll(enabledSubtypes);
}
for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) {
if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty()
&& !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) {
return false;
}
}
return true;
}
Tadashi G. Takaoka
committed
private void updateCurrentSubtype(@Nullable final InputMethodSubtype subtype) {
mCurrentRichInputMethodSubtype = RichInputMethodSubtype.getRichInputMethodSubtype(subtype);
Tadashi G. Takaoka
committed
}
private void updateShortcutIme() {
Tadashi G. Takaoka
committed
if (DEBUG) {
Log.d(TAG, "Update shortcut IME from : "
+ (mShortcutInputMethodInfo == null
? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
+ (mShortcutSubtype == null ? "<null>" : (
mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
}
final RichInputMethodSubtype richSubtype = mCurrentRichInputMethodSubtype;
final Locale systemLocale = mContext.getResources().getConfiguration().locale;
Tadashi G. Takaoka
committed
// TODO: Update an icon for shortcut IME
final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
getInputMethodManager().getShortcutInputMethodsAndSubtypes();
mShortcutInputMethodInfo = null;
mShortcutSubtype = null;
for (final InputMethodInfo imi : shortcuts.keySet()) {
final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
// TODO: Returns the first found IMI for now. Should handle all shortcuts as
// appropriate.
mShortcutInputMethodInfo = imi;
// TODO: Pick up the first found subtype for now. Should handle all subtypes
// as appropriate.
mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null;
break;
}
if (DEBUG) {
Log.d(TAG, "Update shortcut IME to : "
+ (mShortcutInputMethodInfo == null
? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
+ (mShortcutSubtype == null ? "<null>" : (
mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
}
}
Tadashi G. Takaoka
committed
public void switchToShortcutIme(final InputMethodService context) {
Tadashi G. Takaoka
committed
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
if (mShortcutInputMethodInfo == null) {
return;
}
final String imiId = mShortcutInputMethodInfo.getId();
switchToTargetIME(imiId, mShortcutSubtype, context);
}
private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
final InputMethodService context) {
final IBinder token = context.getWindow().getWindow().getAttributes().token;
if (token == null) {
return;
}
final InputMethodManager imm = getInputMethodManager();
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
imm.setInputMethodAndSubtype(token, imiId, subtype);
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public boolean isShortcutImeReady() {
if (mShortcutInputMethodInfo == null) {
return false;
}
if (mShortcutSubtype == null) {
return true;
}
return true;
}