/*
* Copyright (C) 2010 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.
*/
package org.smc.inputmethod.indic;
//import static org.smc.inputmethod.indic.Constants.Subtype.ExtraValue.REQ_NETWORK_CONNECTIVITY;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
//import android.net.ConnectivityManager;
//import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.IBinder;
import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import org.smc.inputmethod.annotations.UsedForTesting;
import org.smc.inputmethod.compat.InputMethodSubtypeCompatUtils;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
import org.smc.inputmethod.indic.define.DebugFlags;
import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public final class SubtypeSwitcher {
private static boolean DBG = DebugFlags.DEBUG_ENABLED;
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
private /* final */ RichInputMethodManager mRichImm;
private /* final */ Resources mResources;
private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper =
new LanguageOnSpacebarHelper();
private InputMethodInfo mShortcutInputMethodInfo;
private InputMethodSubtype mShortcutSubtype;
private InputMethodSubtype mNoLanguageSubtype;
private InputMethodSubtype mEmojiSubtype;
private boolean mIsNetworkConnected;
private static final String KEYBOARD_MODE = "keyboard";
// Dummy no language QWERTY subtype. See {@link R.xml.method}.
private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3;
private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE =
"KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY
+ "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+ "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
+ "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
InputMethodSubtypeCompatUtils.newInputMethodSubtype(
R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE,
false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE);
// Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
// Dummy Emoji subtype. See {@link R.xml.method}.
private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0;
private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE =
"KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
+ "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE =
InputMethodSubtypeCompatUtils.newInputMethodSubtype(
R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE,
false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE);
public static SubtypeSwitcher getInstance() {
return sInstance;
}
public static void init(final Context context) {
SubtypeLocaleUtils.init(context);
RichInputMethodManager.init(context);
sInstance.initialize(context);
}
private SubtypeSwitcher() {
// Intentional empty constructor for singleton.
}
private void initialize(final Context context) {
if (mResources != null) {
return;
}
mResources = context.getResources();
mRichImm = RichInputMethodManager.getInstance();
//ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(
//Context.CONNECTIVITY_SERVICE);
//final NetworkInfo info = connectivityManager.getActiveNetworkInfo();
//mIsNetworkConnected = (info != null && info.isConnected());
onSubtypeChanged(getCurrentSubtype());
updateParametersOnStartInputView();
}
/**
* Update parameters which are changed outside LatinIME. This parameters affect UI so that they
* should be updated every time onStartInputView is called.
*/
public void updateParametersOnStartInputView() {
final List<InputMethodSubtype> enabledSubtypesOfThisIme =
mRichImm.getMyEnabledInputMethodSubtypeList(true);
mLanguageOnSpacebarHelper.updateEnabledSubtypes(enabledSubtypesOfThisIme);
updateShortcutIME();
}
private void updateShortcutIME() {
if (DBG) {
Log.d(TAG, "Update shortcut IME from : "
+ (mShortcutInputMethodInfo == null
? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
+ (mShortcutSubtype == null ? "<null>" : (
mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
}
// TODO: Update an icon for shortcut IME
final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
mRichImm.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 (DBG) {
Log.d(TAG, "Update shortcut IME to : "
+ (mShortcutInputMethodInfo == null
? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
+ (mShortcutSubtype == null ? "<null>" : (
mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
}
}
// Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
public void onSubtypeChanged(final InputMethodSubtype newSubtype) {
if (DBG) {
Log.w(TAG, "onSubtypeChanged: "
+ SubtypeLocaleUtils.getSubtypeNameForLogging(newSubtype));
}
final Locale newLocale = SubtypeLocaleUtils.getSubtypeLocale(newSubtype);
final Locale systemLocale = mResources.getConfiguration().locale;
final boolean sameLocale = systemLocale.equals(newLocale);
final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage());
final boolean implicitlyEnabled =
mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype);
mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(
sameLocale || (sameLanguage && implicitlyEnabled));
updateShortcutIME();
}
////////////////////////////
// Shortcut IME functions //
////////////////////////////
public void switchToShortcutIME(final InputMethodService context) {
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 = mRichImm.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 isShortcutImeEnabled() {
updateShortcutIME();
if (mShortcutInputMethodInfo == null) {
return false;
}
if (mShortcutSubtype == null) {
return true;
}
return mRichImm.checkIfSubtypeBelongsToImeAndEnabled(
mShortcutInputMethodInfo, mShortcutSubtype);
}
public boolean isShortcutImeReady() {
updateShortcutIME();
if (mShortcutInputMethodInfo == null) {
return false;
}
if (mShortcutSubtype == null) {
return true;
}
//if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) {
//return mIsNetworkConnected;
//}
return true;
}
public void onNetworkStateChanged(final Intent intent) {
//final boolean noConnection = intent.getBooleanExtra(
//ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
//mIsNetworkConnected = !noConnection;
//KeyboardSwitcher.getInstance().onNetworkStateChanged();
}
//////////////////////////////////
// Subtype Switching functions //
//////////////////////////////////
public int getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype) {
return mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(subtype);
}
public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
final Locale systemLocale = mResources.getConfiguration().locale;
final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
final InputMethodManager inputMethodManager = mRichImm.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;
}
private static InputMethodSubtype sForcedSubtypeForTesting = null;
@UsedForTesting
void forceSubtype(final InputMethodSubtype subtype) {
sForcedSubtypeForTesting = subtype;
}
public Locale getCurrentSubtypeLocale() {
if (null != sForcedSubtypeForTesting) {
return LocaleUtils.constructLocaleFromString(sForcedSubtypeForTesting.getLocale());
}
return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype());
}
public InputMethodSubtype getCurrentSubtype() {
if (null != sForcedSubtypeForTesting) {
return sForcedSubtypeForTesting;
}
return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype());
}
public InputMethodSubtype getNoLanguageSubtype() {
if (mNoLanguageSubtype == null) {
mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
}
if (mNoLanguageSubtype != null) {
return mNoLanguageSubtype;
}
Log.w(TAG, "Can't find any language with QWERTY subtype");
Log.w(TAG, "No input method subtype found; returning dummy subtype: "
+ DUMMY_NO_LANGUAGE_SUBTYPE);
return DUMMY_NO_LANGUAGE_SUBTYPE;
}
public InputMethodSubtype getEmojiSubtype() {
if (mEmojiSubtype == null) {
mEmojiSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
}
if (mEmojiSubtype != null) {
return mEmojiSubtype;
}
Log.w(TAG, "Can't find emoji subtype");
Log.w(TAG, "No input method subtype found; returning dummy subtype: "
+ DUMMY_EMOJI_SUBTYPE);
return DUMMY_EMOJI_SUBTYPE;
}
public String getCombiningRulesExtraValueOfCurrentSubtype() {
return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype());
}
}