blob: 2ab30246662caf1acd903dc5912bc6b759aff5c1 [file] [log] [blame]
/*
* 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 android.view.inputmethod;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Slog;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IllegalFormatException;
import java.util.List;
import java.util.Locale;
/**
* This class is used to specify meta information of a subtype contained in an input method editor
* (IME). Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard...),
* and is used for IME switch and settings. The input method subtype allows the system to bring up
* the specified subtype of the designated IME directly.
*
* <p>It should be defined in an XML resource file of the input method with the
* <code>&lt;subtype&gt;</code> element, which resides within an {@code &lt;input-method>} element.
* For more information, see the guide to
* <a href="{@docRoot}guide/topics/text/creating-input-method.html">
* Creating an Input Method</a>.</p>
*
* @see InputMethodInfo
*
* @attr ref android.R.styleable#InputMethod_Subtype_label
* @attr ref android.R.styleable#InputMethod_Subtype_icon
* @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale
* @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode
* @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue
* @attr ref android.R.styleable#InputMethod_Subtype_isAuxiliary
* @attr ref android.R.styleable#InputMethod_Subtype_overridesImplicitlyEnabledSubtype
* @attr ref android.R.styleable#InputMethod_Subtype_subtypeId
* @attr ref android.R.styleable#InputMethod_Subtype_isAsciiCapable
*/
public final class InputMethodSubtype implements Parcelable {
private static final String TAG = InputMethodSubtype.class.getSimpleName();
private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
// TODO: remove this
private static final String EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME =
"UntranslatableReplacementStringInSubtypeName";
private final boolean mIsAuxiliary;
private final boolean mOverridesImplicitlyEnabledSubtype;
private final boolean mIsAsciiCapable;
private final int mSubtypeHashCode;
private final int mSubtypeIconResId;
private final int mSubtypeNameResId;
private final int mSubtypeId;
private final String mSubtypeLocale;
private final String mSubtypeMode;
private final String mSubtypeExtraValue;
private volatile HashMap<String, String> mExtraValueHashMapCache;
/**
* InputMethodSubtypeBuilder is a builder class of InputMethodSubtype.
* This class is designed to be used with
* {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}.
* The developer needs to be aware of what each parameter means.
*/
public static class InputMethodSubtypeBuilder {
/**
* @param isAuxiliary should true when this subtype is auxiliary, false otherwise.
* An auxiliary subtype has the following differences with a regular subtype:
* - An auxiliary subtype cannot be chosen as the default IME in Settings.
* - The framework will never switch to this subtype through
* {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
* Note that the subtype will still be available in the IME switcher.
* The intent is to allow for IMEs to specify they are meant to be invoked temporarily
* in a one-shot way, and to return to the previous IME once finished (e.g. voice input).
*/
public InputMethodSubtypeBuilder setIsAuxiliary(boolean isAuxiliary) {
mIsAuxiliary = isAuxiliary;
return this;
}
private boolean mIsAuxiliary = false;
/**
* @param overridesImplicitlyEnabledSubtype should be true if this subtype should be
* enabled by default if no other subtypes in the IME are enabled explicitly. Note that a
* subtype with this parameter set will not be shown in the list of subtypes in each IME's
* subtype enabler. A canonical use of this would be for an IME to supply an "automatic"
* subtype that adapts to the current system language.
*/
public InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(
boolean overridesImplicitlyEnabledSubtype) {
mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
return this;
}
private boolean mOverridesImplicitlyEnabledSubtype = false;
/**
* @param isAsciiCapable should be true if this subtype is ASCII capable. If the subtype
* is ASCII capable, it should guarantee that the user can input ASCII characters with
* this subtype. This is important because many password fields only allow
* ASCII-characters.
*/
public InputMethodSubtypeBuilder setIsAsciiCapable(boolean isAsciiCapable) {
mIsAsciiCapable = isAsciiCapable;
return this;
}
private boolean mIsAsciiCapable = false;
/**
* @param subtypeIconResId is a resource ID of the subtype icon drawable.
*/
public InputMethodSubtypeBuilder setSubtypeIconResId(int subtypeIconResId) {
mSubtypeIconResId = subtypeIconResId;
return this;
}
private int mSubtypeIconResId = 0;
/**
* @param subtypeNameResId is the resource ID of the subtype name string.
* The string resource may have exactly one %s in it. If present,
* the %s part will be replaced with the locale's display name by
* the formatter. Please refer to {@link #getDisplayName} for details.
*/
public InputMethodSubtypeBuilder setSubtypeNameResId(int subtypeNameResId) {
mSubtypeNameResId = subtypeNameResId;
return this;
}
private int mSubtypeNameResId = 0;
/**
* @param subtypeId is the unique ID for this subtype. The input method framework keeps
* track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will
* stay enabled even if other attributes are different. If the ID is unspecified or 0,
* Arrays.hashCode(new Object[] {locale, mode, extraValue,
* isAuxiliary, overridesImplicitlyEnabledSubtype}) will be used instead.
*/
public InputMethodSubtypeBuilder setSubtypeId(int subtypeId) {
mSubtypeId = subtypeId;
return this;
}
private int mSubtypeId = 0;
/**
* @param subtypeLocale is the locale supported by this subtype.
*/
public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) {
mSubtypeLocale = subtypeLocale == null ? "" : subtypeLocale;
return this;
}
private String mSubtypeLocale = "";
/**
* @param subtypeMode is the mode supported by this subtype.
*/
public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) {
mSubtypeMode = subtypeMode == null ? "" : subtypeMode;
return this;
}
private String mSubtypeMode = "";
/**
* @param subtypeExtraValue is the extra value of the subtype. This string is free-form,
* but the API supplies tools to deal with a key-value comma-separated list; see
* {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
*/
public InputMethodSubtypeBuilder setSubtypeExtraValue(String subtypeExtraValue) {
mSubtypeExtraValue = subtypeExtraValue == null ? "" : subtypeExtraValue;
return this;
}
private String mSubtypeExtraValue = "";
/**
* @return InputMethodSubtype using parameters in this InputMethodSubtypeBuilder.
*/
public InputMethodSubtype build() {
return new InputMethodSubtype(this);
}
}
private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale,
String mode, String extraValue, boolean isAuxiliary,
boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
builder.mSubtypeNameResId = nameId;
builder.mSubtypeIconResId = iconId;
builder.mSubtypeLocale = locale;
builder.mSubtypeMode = mode;
builder.mSubtypeExtraValue = extraValue;
builder.mIsAuxiliary = isAuxiliary;
builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
builder.mSubtypeId = id;
builder.mIsAsciiCapable = isAsciiCapable;
return builder;
}
/**
* Constructor with no subtype ID specified, overridesImplicitlyEnabledSubtype not specified.
* Arguments for this constructor have the same meanings as
* {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean,
* boolean, int)} except "id" and "overridesImplicitlyEnabledSubtype".
* @hide
*/
public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
boolean isAuxiliary) {
this(nameId, iconId, locale, mode, extraValue, isAuxiliary, false);
}
/**
* Constructor with no subtype ID specified.
* @deprecated use {@link InputMethodSubtypeBuilder} instead.
* Arguments for this constructor have the same meanings as
* {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean,
* boolean, int)} except "id".
*/
public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
this(nameId, iconId, locale, mode, extraValue, isAuxiliary,
overridesImplicitlyEnabledSubtype, 0);
}
/**
* Constructor.
* @deprecated use {@link InputMethodSubtypeBuilder} instead.
* "isAsciiCapable" is "false" in this constructor.
* @param nameId Resource ID of the subtype name string. The string resource may have exactly
* one %s in it. If there is, the %s part will be replaced with the locale's display name by
* the formatter. Please refer to {@link #getDisplayName} for details.
* @param iconId Resource ID of the subtype icon drawable.
* @param locale The locale supported by the subtype
* @param mode The mode supported by the subtype
* @param extraValue The extra value of the subtype. This string is free-form, but the API
* supplies tools to deal with a key-value comma-separated list; see
* {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
* @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary
* subtype will not be shown in the list of enabled IMEs for choosing the current IME in
* the Settings even when this subtype is enabled. Please note that this subtype will still
* be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch
* to this subtype while an IME is shown. The framework will never switch the current IME to
* this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
* The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
* auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
* @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default
* if no other subtypes in the IME are enabled explicitly. Note that a subtype with this
* parameter being true will not be shown in the list of subtypes in each IME's subtype enabler.
* Having an "automatic" subtype is an example use of this flag.
* @param id The unique ID for the subtype. The input method framework keeps track of enabled
* subtypes by ID. When the IME package gets upgraded, enabled IDs will stay enabled even if
* other attributes are different. If the ID is unspecified or 0,
* Arrays.hashCode(new Object[] {locale, mode, extraValue,
* isAuxiliary, overridesImplicitlyEnabledSubtype}) will be used instead.
*/
public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) {
this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary,
overridesImplicitlyEnabledSubtype, id, false));
}
/**
* Constructor.
* @param builder Builder for InputMethodSubtype
*/
private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
mSubtypeNameResId = builder.mSubtypeNameResId;
mSubtypeIconResId = builder.mSubtypeIconResId;
mSubtypeLocale = builder.mSubtypeLocale;
mSubtypeMode = builder.mSubtypeMode;
mSubtypeExtraValue = builder.mSubtypeExtraValue;
mIsAuxiliary = builder.mIsAuxiliary;
mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype;
mSubtypeId = builder.mSubtypeId;
mIsAsciiCapable = builder.mIsAsciiCapable;
// If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype,
// just specify 0 as this subtype's id. Then, this subtype's id is treated as 0.
mSubtypeHashCode = mSubtypeId != 0 ? mSubtypeId : hashCodeInternal(mSubtypeLocale,
mSubtypeMode, mSubtypeExtraValue, mIsAuxiliary, mOverridesImplicitlyEnabledSubtype,
mIsAsciiCapable);
}
InputMethodSubtype(Parcel source) {
String s;
mSubtypeNameResId = source.readInt();
mSubtypeIconResId = source.readInt();
s = source.readString();
mSubtypeLocale = s != null ? s : "";
s = source.readString();
mSubtypeMode = s != null ? s : "";
s = source.readString();
mSubtypeExtraValue = s != null ? s : "";
mIsAuxiliary = (source.readInt() == 1);
mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
mSubtypeHashCode = source.readInt();
mSubtypeId = source.readInt();
mIsAsciiCapable = (source.readInt() == 1);
}
/**
* @return Resource ID of the subtype name string.
*/
public int getNameResId() {
return mSubtypeNameResId;
}
/**
* @return Resource ID of the subtype icon drawable.
*/
public int getIconResId() {
return mSubtypeIconResId;
}
/**
* @return The locale of the subtype. This method returns the "locale" string parameter passed
* to the constructor.
*/
public String getLocale() {
return mSubtypeLocale;
}
/**
* @return The mode of the subtype.
*/
public String getMode() {
return mSubtypeMode;
}
/**
* @return The extra value of the subtype.
*/
public String getExtraValue() {
return mSubtypeExtraValue;
}
/**
* @return true if this subtype is auxiliary, false otherwise. An auxiliary subtype will not be
* shown in the list of enabled IMEs for choosing the current IME in the Settings even when this
* subtype is enabled. Please note that this subtype will still be shown in the list of IMEs in
* the IME switcher to allow the user to tentatively switch to this subtype while an IME is
* shown. The framework will never switch the current IME to this subtype by
* {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
* The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
* auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
*/
public boolean isAuxiliary() {
return mIsAuxiliary;
}
/**
* @return true when this subtype will be enabled by default if no other subtypes in the IME
* are enabled explicitly, false otherwise. Note that a subtype with this method returning true
* will not be shown in the list of subtypes in each IME's subtype enabler. Having an
* "automatic" subtype is an example use of this flag.
*/
public boolean overridesImplicitlyEnabledSubtype() {
return mOverridesImplicitlyEnabledSubtype;
}
/**
* @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII
* capable, it should guarantee that the user can input ASCII characters with this subtype.
* This is important because many password fields only allow ASCII-characters.
*/
public boolean isAsciiCapable() {
return mIsAsciiCapable;
}
/**
* @param context Context will be used for getting Locale and PackageManager.
* @param packageName The package name of the IME
* @param appInfo The application info of the IME
* @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
* may have exactly one %s in it. If there is, the %s part will be replaced with the locale's
* display name by the formatter. If there is not, this method returns the string specified by
* mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the framework to
* generate an appropriate display name.
*/
public CharSequence getDisplayName(
Context context, String packageName, ApplicationInfo appInfo) {
final Locale locale = constructLocaleFromString(mSubtypeLocale);
final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale;
if (mSubtypeNameResId == 0) {
return localeStr;
}
final CharSequence subtypeName = context.getPackageManager().getText(
packageName, mSubtypeNameResId, appInfo);
if (!TextUtils.isEmpty(subtypeName)) {
final String replacementString =
containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)
? getExtraValueOf(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)
: localeStr;
try {
return String.format(
subtypeName.toString(), replacementString != null ? replacementString : "");
} catch (IllegalFormatException e) {
Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e);
return "";
}
} else {
return localeStr;
}
}
private HashMap<String, String> getExtraValueHashMap() {
if (mExtraValueHashMapCache == null) {
synchronized(this) {
if (mExtraValueHashMapCache == null) {
mExtraValueHashMapCache = new HashMap<String, String>();
final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
final int N = pairs.length;
for (int i = 0; i < N; ++i) {
final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
if (pair.length == 1) {
mExtraValueHashMapCache.put(pair[0], null);
} else if (pair.length > 1) {
if (pair.length > 2) {
Slog.w(TAG, "ExtraValue has two or more '='s");
}
mExtraValueHashMapCache.put(pair[0], pair[1]);
}
}
}
}
}
return mExtraValueHashMapCache;
}
/**
* The string of ExtraValue in subtype should be defined as follows:
* example: key0,key1=value1,key2,key3,key4=value4
* @param key The key of extra value
* @return The subtype contains specified the extra value
*/
public boolean containsExtraValueKey(String key) {
return getExtraValueHashMap().containsKey(key);
}
/**
* The string of ExtraValue in subtype should be defined as follows:
* example: key0,key1=value1,key2,key3,key4=value4
* @param key The key of extra value
* @return The value of the specified key
*/
public String getExtraValueOf(String key) {
return getExtraValueHashMap().get(key);
}
@Override
public int hashCode() {
return mSubtypeHashCode;
}
@Override
public boolean equals(Object o) {
if (o instanceof InputMethodSubtype) {
InputMethodSubtype subtype = (InputMethodSubtype) o;
if (subtype.mSubtypeId != 0 || mSubtypeId != 0) {
return (subtype.hashCode() == hashCode());
}
return (subtype.hashCode() == hashCode())
&& (subtype.getNameResId() == getNameResId())
&& (subtype.getMode().equals(getMode()))
&& (subtype.getIconResId() == getIconResId())
&& (subtype.getLocale().equals(getLocale()))
&& (subtype.getExtraValue().equals(getExtraValue()))
&& (subtype.isAuxiliary() == isAuxiliary())
&& (subtype.isAsciiCapable() == isAsciiCapable());
}
return false;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeInt(mSubtypeNameResId);
dest.writeInt(mSubtypeIconResId);
dest.writeString(mSubtypeLocale);
dest.writeString(mSubtypeMode);
dest.writeString(mSubtypeExtraValue);
dest.writeInt(mIsAuxiliary ? 1 : 0);
dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
dest.writeInt(mSubtypeHashCode);
dest.writeInt(mSubtypeId);
dest.writeInt(mIsAsciiCapable ? 1 : 0);
}
public static final Parcelable.Creator<InputMethodSubtype> CREATOR
= new Parcelable.Creator<InputMethodSubtype>() {
@Override
public InputMethodSubtype createFromParcel(Parcel source) {
return new InputMethodSubtype(source);
}
@Override
public InputMethodSubtype[] newArray(int size) {
return new InputMethodSubtype[size];
}
};
private static Locale constructLocaleFromString(String localeStr) {
if (TextUtils.isEmpty(localeStr))
return null;
String[] localeParams = localeStr.split("_", 3);
// The length of localeStr is guaranteed to always return a 1 <= value <= 3
// because localeStr is not empty.
if (localeParams.length == 1) {
return new Locale(localeParams[0]);
} else if (localeParams.length == 2) {
return new Locale(localeParams[0], localeParams[1]);
} else if (localeParams.length == 3) {
return new Locale(localeParams[0], localeParams[1], localeParams[2]);
}
return null;
}
private static int hashCodeInternal(String locale, String mode, String extraValue,
boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
boolean isAsciiCapable) {
// CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new
// attribute is added in order to avoid enabled subtypes being unexpectedly disabled.
final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable;
if (needsToCalculateCompatibleHashCode) {
return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
overridesImplicitlyEnabledSubtype});
}
return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
overridesImplicitlyEnabledSubtype, isAsciiCapable});
}
/**
* Sort the list of InputMethodSubtype
* @param context Context will be used for getting localized strings from IME
* @param flags Flags for the sort order
* @param imi InputMethodInfo of which subtypes are subject to be sorted
* @param subtypeList List of InputMethodSubtype which will be sorted
* @return Sorted list of subtypes
* @hide
*/
public static List<InputMethodSubtype> sort(Context context, int flags, InputMethodInfo imi,
List<InputMethodSubtype> subtypeList) {
if (imi == null) return subtypeList;
final HashSet<InputMethodSubtype> inputSubtypesSet = new HashSet<InputMethodSubtype>(
subtypeList);
final ArrayList<InputMethodSubtype> sortedList = new ArrayList<InputMethodSubtype>();
int N = imi.getSubtypeCount();
for (int i = 0; i < N; ++i) {
InputMethodSubtype subtype = imi.getSubtypeAt(i);
if (inputSubtypesSet.contains(subtype)) {
sortedList.add(subtype);
inputSubtypesSet.remove(subtype);
}
}
// If subtypes in inputSubtypesSet remain, that means these subtypes are not
// contained in imi, so the remaining subtypes will be appended.
for (InputMethodSubtype subtype: inputSubtypesSet) {
sortedList.add(subtype);
}
return sortedList;
}
}