Merge "Adds the new API - InputMethodSubtypeBuilder#setPhysicalKeyboardHint."
This commit is contained in:
@@ -1174,6 +1174,8 @@ package android {
|
||||
field public static final int persistentDrawingCache = 16842990; // 0x10100ee
|
||||
field public static final int persistentWhenFeatureAvailable = 16844131; // 0x1010563
|
||||
field @Deprecated public static final int phoneNumber = 16843111; // 0x1010167
|
||||
field public static final int physicalKeyboardHintLanguageTag;
|
||||
field public static final int physicalKeyboardHintLayoutType;
|
||||
field public static final int pivotX = 16843189; // 0x10101b5
|
||||
field public static final int pivotY = 16843190; // 0x10101b6
|
||||
field public static final int pointerIcon = 16844041; // 0x1010509
|
||||
@@ -54438,6 +54440,8 @@ package android.view.inputmethod {
|
||||
method public String getMode();
|
||||
method @NonNull public CharSequence getNameOverride();
|
||||
method public int getNameResId();
|
||||
method @Nullable public android.icu.util.ULocale getPhysicalKeyboardHintLanguageTag();
|
||||
method @NonNull public String getPhysicalKeyboardHintLayoutType();
|
||||
method public boolean isAsciiCapable();
|
||||
method public boolean isAuxiliary();
|
||||
method public boolean overridesImplicitlyEnabledSubtype();
|
||||
@@ -54452,6 +54456,7 @@ package android.view.inputmethod {
|
||||
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
|
||||
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(String);
|
||||
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
|
||||
method @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setPhysicalKeyboardHint(@Nullable android.icu.util.ULocale, @NonNull String);
|
||||
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(String);
|
||||
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
|
||||
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeId(int);
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.content.res.Resources.NotFoundException;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.icu.util.ULocale;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@@ -266,11 +267,18 @@ public final class InputMethodInfo implements Parcelable {
|
||||
}
|
||||
final TypedArray a = res.obtainAttributes(
|
||||
attrs, com.android.internal.R.styleable.InputMethod_Subtype);
|
||||
String pkLanguageTag = a.getString(com.android.internal.R.styleable
|
||||
.InputMethod_Subtype_physicalKeyboardHintLanguageTag);
|
||||
String pkLayoutType = a.getString(com.android.internal.R.styleable
|
||||
.InputMethod_Subtype_physicalKeyboardHintLayoutType);
|
||||
final InputMethodSubtype subtype = new InputMethodSubtypeBuilder()
|
||||
.setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable
|
||||
.InputMethod_Subtype_label, 0))
|
||||
.setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable
|
||||
.InputMethod_Subtype_icon, 0))
|
||||
.setPhysicalKeyboardHint(
|
||||
pkLanguageTag == null ? null : new ULocale(pkLanguageTag),
|
||||
pkLayoutType == null ? "" : pkLayoutType)
|
||||
.setLanguageTag(a.getString(com.android.internal.R.styleable
|
||||
.InputMethod_Subtype_languageTag))
|
||||
.setSubtypeLocale(a.getString(com.android.internal.R.styleable
|
||||
|
||||
@@ -39,6 +39,7 @@ import java.util.HashSet;
|
||||
import java.util.IllegalFormatException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This class is used to specify meta information of a subtype contained in an input method editor
|
||||
@@ -87,6 +88,8 @@ public final class InputMethodSubtype implements Parcelable {
|
||||
private final int mSubtypeIconResId;
|
||||
private final int mSubtypeNameResId;
|
||||
private final CharSequence mSubtypeNameOverride;
|
||||
private final String mPkLanguageTag;
|
||||
private final String mPkLayoutType;
|
||||
private final int mSubtypeId;
|
||||
private final String mSubtypeLocale;
|
||||
private final String mSubtypeLanguageTag;
|
||||
@@ -189,6 +192,30 @@ public final class InputMethodSubtype implements Parcelable {
|
||||
}
|
||||
private CharSequence mSubtypeNameOverride = "";
|
||||
|
||||
/**
|
||||
* Sets the physical keyboard hint information, such as language and layout.
|
||||
*
|
||||
* The system can use the hint information to automatically configure the physical keyboard
|
||||
* for the subtype.
|
||||
*
|
||||
* @param languageTag is the preferred physical keyboard BCP-47 language tag. This is used
|
||||
* to match the keyboardLocale attribute in the physical keyboard definition. If it's
|
||||
* {@code null}, the subtype's language tag will be used.
|
||||
* @param layoutType is the preferred physical keyboard layout, which is used to match the
|
||||
* keyboardLayoutType attribute in the physical keyboard definition. See
|
||||
* {@link android.hardware.input.InputManager#ACTION_QUERY_KEYBOARD_LAYOUTS}.
|
||||
*/
|
||||
@NonNull
|
||||
public InputMethodSubtypeBuilder setPhysicalKeyboardHint(@Nullable ULocale languageTag,
|
||||
@NonNull String layoutType) {
|
||||
Objects.requireNonNull(layoutType, "layoutType cannot be null");
|
||||
mPkLanguageTag = languageTag == null ? "" : languageTag.toLanguageTag();
|
||||
mPkLayoutType = layoutType;
|
||||
return this;
|
||||
}
|
||||
private String mPkLanguageTag = "";
|
||||
private String mPkLayoutType = "";
|
||||
|
||||
/**
|
||||
* @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
|
||||
@@ -322,6 +349,8 @@ public final class InputMethodSubtype implements Parcelable {
|
||||
private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
|
||||
mSubtypeNameResId = builder.mSubtypeNameResId;
|
||||
mSubtypeNameOverride = builder.mSubtypeNameOverride;
|
||||
mPkLanguageTag = builder.mPkLanguageTag;
|
||||
mPkLayoutType = builder.mPkLayoutType;
|
||||
mSubtypeIconResId = builder.mSubtypeIconResId;
|
||||
mSubtypeLocale = builder.mSubtypeLocale;
|
||||
mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
|
||||
@@ -346,6 +375,10 @@ public final class InputMethodSubtype implements Parcelable {
|
||||
mSubtypeNameResId = source.readInt();
|
||||
CharSequence cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
|
||||
mSubtypeNameOverride = cs != null ? cs : "";
|
||||
s = source.readString8();
|
||||
mPkLanguageTag = s != null ? s : "";
|
||||
s = source.readString8();
|
||||
mPkLayoutType = s != null ? s : "";
|
||||
mSubtypeIconResId = source.readInt();
|
||||
s = source.readString();
|
||||
mSubtypeLocale = s != null ? s : "";
|
||||
@@ -377,6 +410,28 @@ public final class InputMethodSubtype implements Parcelable {
|
||||
return mSubtypeNameOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the physical keyboard BCP-47 language tag.
|
||||
*
|
||||
* @attr ref android.R.styleable#InputMethod_Subtype_physicalKeyboardHintLanguageTag
|
||||
* @see InputMethodSubtypeBuilder#setPhysicalKeyboardHint
|
||||
*/
|
||||
@Nullable
|
||||
public ULocale getPhysicalKeyboardHintLanguageTag() {
|
||||
return TextUtils.isEmpty(mPkLanguageTag) ? null : ULocale.forLanguageTag(mPkLanguageTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the physical keyboard layout type string.
|
||||
*
|
||||
* @attr ref android.R.styleable#InputMethod_Subtype_physicalKeyboardHintLayoutType
|
||||
* @see InputMethodSubtypeBuilder#setPhysicalKeyboardHint
|
||||
*/
|
||||
@NonNull
|
||||
public String getPhysicalKeyboardHintLayoutType() {
|
||||
return mPkLayoutType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Resource ID of the subtype icon drawable.
|
||||
*/
|
||||
@@ -729,6 +784,8 @@ public final class InputMethodSubtype implements Parcelable {
|
||||
public void writeToParcel(Parcel dest, int parcelableFlags) {
|
||||
dest.writeInt(mSubtypeNameResId);
|
||||
TextUtils.writeToParcel(mSubtypeNameOverride, dest, parcelableFlags);
|
||||
dest.writeString8(mPkLanguageTag);
|
||||
dest.writeString8(mPkLayoutType);
|
||||
dest.writeInt(mSubtypeIconResId);
|
||||
dest.writeString(mSubtypeLocale);
|
||||
dest.writeString(mSubtypeLanguageTag);
|
||||
|
||||
@@ -3812,6 +3812,17 @@
|
||||
<!-- The BCP-47 Language Tag of the subtype. This replaces
|
||||
{@link android.R.styleable#InputMethod_Subtype_imeSubtypeLocale}. -->
|
||||
<attr name="languageTag" format="string" />
|
||||
<!-- The BCP-47 Language Tag of the preferred physical keyboard of the subtype. If it's not
|
||||
specified, {@link android.R.styleable#InputMethod_Subtype_languageTag} will be used.
|
||||
See also
|
||||
{@link android.view.inputmethod.InputMethodSubtype#getPhysicalKeyboardHintLanguageTag}.
|
||||
-->
|
||||
<attr name="physicalKeyboardHintLanguageTag" format="string" />
|
||||
<!-- The layout type of the preferred physical keyboard of the subtype.
|
||||
It matches the layout type string in the keyboard layout definition. See also
|
||||
{@link android.view.inputmethod.InputMethodSubtype#getPhysicalKeyboardHintLayoutType}.
|
||||
-->
|
||||
<attr name="physicalKeyboardHintLayoutType" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
<!-- Use <code>spell-checker</code> as the root tag of the XML resource that
|
||||
|
||||
@@ -119,6 +119,8 @@
|
||||
<public name="requiredDisplayCategory"/>
|
||||
<public name="removed_maxConcurrentSessionsCount" />
|
||||
<public name="visualQueryDetectionService" />
|
||||
<public name="physicalKeyboardHintLanguageTag" />
|
||||
<public name="physicalKeyboardHintLayoutType" />
|
||||
</staging-public-group>
|
||||
|
||||
<staging-public-group type="id" first-id="0x01cd0000">
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.inputmethod;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.icu.util.ULocale;
|
||||
import android.os.Environment;
|
||||
import android.os.FileUtils;
|
||||
import android.os.UserHandle;
|
||||
@@ -29,6 +30,7 @@ import android.util.Xml;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.modules.utils.TypedXmlPullParser;
|
||||
import com.android.modules.utils.TypedXmlSerializer;
|
||||
|
||||
@@ -59,6 +61,8 @@ final class AdditionalSubtypeUtils {
|
||||
private static final String ATTR_ID = "id";
|
||||
private static final String ATTR_LABEL = "label";
|
||||
private static final String ATTR_NAME_OVERRIDE = "nameOverride";
|
||||
private static final String ATTR_NAME_PK_LANGUAGE_TAG = "pkLanguageTag";
|
||||
private static final String ATTR_NAME_PK_LAYOUT_TYPE = "pkLayoutType";
|
||||
private static final String ATTR_ICON = "icon";
|
||||
private static final String ATTR_IME_SUBTYPE_ID = "subtypeId";
|
||||
private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
|
||||
@@ -74,7 +78,7 @@ final class AdditionalSubtypeUtils {
|
||||
/**
|
||||
* Returns a {@link File} that represents the directory at which subtype.xml will be placed.
|
||||
*
|
||||
* @param userId User ID with with subtype.xml path should be determined.
|
||||
* @param userId User ID with subtype.xml path should be determined.
|
||||
* @return {@link File} that represents the directory.
|
||||
*/
|
||||
@NonNull
|
||||
@@ -134,11 +138,15 @@ final class AdditionalSubtypeUtils {
|
||||
Slog.e(TAG, "Failed to create a parent directory " + inputMethodDir);
|
||||
return;
|
||||
}
|
||||
saveToFile(allSubtypes, methodMap, getAdditionalSubtypeFile(inputMethodDir));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static void saveToFile(ArrayMap<String, List<InputMethodSubtype>> allSubtypes,
|
||||
ArrayMap<String, InputMethodInfo> methodMap, AtomicFile subtypesFile) {
|
||||
// Safety net for the case that this function is called before methodMap is set.
|
||||
final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
|
||||
FileOutputStream fos = null;
|
||||
final AtomicFile subtypesFile = getAdditionalSubtypeFile(inputMethodDir);
|
||||
try {
|
||||
fos = subtypesFile.startWrite();
|
||||
final TypedXmlSerializer out = Xml.resolveSerializer(fos);
|
||||
@@ -150,12 +158,14 @@ final class AdditionalSubtypeUtils {
|
||||
Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
|
||||
continue;
|
||||
}
|
||||
final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
|
||||
if (subtypesList == null) {
|
||||
Slog.e(TAG, "Null subtype list for IME " + imiId);
|
||||
continue;
|
||||
}
|
||||
out.startTag(null, NODE_IMI);
|
||||
out.attribute(null, ATTR_ID, imiId);
|
||||
final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
|
||||
final int numSubtypes = subtypesList.size();
|
||||
for (int i = 0; i < numSubtypes; ++i) {
|
||||
final InputMethodSubtype subtype = subtypesList.get(i);
|
||||
for (final InputMethodSubtype subtype : subtypesList) {
|
||||
out.startTag(null, NODE_SUBTYPE);
|
||||
if (subtype.hasSubtypeId()) {
|
||||
out.attributeInt(null, ATTR_IME_SUBTYPE_ID, subtype.getSubtypeId());
|
||||
@@ -163,6 +173,14 @@ final class AdditionalSubtypeUtils {
|
||||
out.attributeInt(null, ATTR_ICON, subtype.getIconResId());
|
||||
out.attributeInt(null, ATTR_LABEL, subtype.getNameResId());
|
||||
out.attribute(null, ATTR_NAME_OVERRIDE, subtype.getNameOverride().toString());
|
||||
ULocale pkLanguageTag = subtype.getPhysicalKeyboardHintLanguageTag();
|
||||
if (pkLanguageTag != null) {
|
||||
out.attribute(null, ATTR_NAME_PK_LANGUAGE_TAG,
|
||||
pkLanguageTag.toLanguageTag());
|
||||
}
|
||||
out.attribute(null, ATTR_NAME_PK_LAYOUT_TYPE,
|
||||
subtype.getPhysicalKeyboardHintLayoutType());
|
||||
|
||||
out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
|
||||
out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG,
|
||||
subtype.getLanguageTag());
|
||||
@@ -203,19 +221,21 @@ final class AdditionalSubtypeUtils {
|
||||
allSubtypes.clear();
|
||||
|
||||
final AtomicFile subtypesFile = getAdditionalSubtypeFile(getInputMethodDir(userId));
|
||||
if (!subtypesFile.exists()) {
|
||||
// Not having the file means there is no additional subtype.
|
||||
return;
|
||||
// Not having the file means there is no additional subtype.
|
||||
if (subtypesFile.exists()) {
|
||||
loadFromFile(allSubtypes, subtypesFile);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static void loadFromFile(@NonNull ArrayMap<String, List<InputMethodSubtype>> allSubtypes,
|
||||
AtomicFile subtypesFile) {
|
||||
try (FileInputStream fis = subtypesFile.openRead()) {
|
||||
final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
|
||||
int type = parser.getEventType();
|
||||
int type = parser.next();
|
||||
// Skip parsing until START_TAG
|
||||
while (true) {
|
||||
while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
|
||||
type = parser.next();
|
||||
if (type == XmlPullParser.START_TAG || type == XmlPullParser.END_DOCUMENT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
String firstNodeName = parser.getName();
|
||||
if (!NODE_SUBTYPES.equals(firstNodeName)) {
|
||||
@@ -247,6 +267,10 @@ final class AdditionalSubtypeUtils {
|
||||
final int label = parser.getAttributeInt(null, ATTR_LABEL);
|
||||
final String untranslatableName = parser.getAttributeValue(null,
|
||||
ATTR_NAME_OVERRIDE);
|
||||
final String pkLanguageTag = parser.getAttributeValue(null,
|
||||
ATTR_NAME_PK_LANGUAGE_TAG);
|
||||
final String pkLayoutType = parser.getAttributeValue(null,
|
||||
ATTR_NAME_PK_LAYOUT_TYPE);
|
||||
final String imeSubtypeLocale =
|
||||
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
|
||||
final String languageTag =
|
||||
@@ -263,6 +287,9 @@ final class AdditionalSubtypeUtils {
|
||||
builder = new InputMethodSubtype.InputMethodSubtypeBuilder()
|
||||
.setSubtypeNameResId(label)
|
||||
.setSubtypeNameOverride(untranslatableName)
|
||||
.setPhysicalKeyboardHint(
|
||||
pkLanguageTag == null ? null : new ULocale(pkLanguageTag),
|
||||
pkLayoutType == null ? "" : pkLayoutType)
|
||||
.setSubtypeIconResId(icon)
|
||||
.setSubtypeLocale(imeSubtypeLocale)
|
||||
.setLanguageTag(languageTag)
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.server.inputmethod;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import android.icu.util.ULocale;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AtomicFile;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AdditionalSubtypeUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testSaveAndLoad() throws Exception {
|
||||
// Prepares the data to be saved.
|
||||
InputMethodSubtype subtype1 = new InputMethodSubtype.InputMethodSubtypeBuilder()
|
||||
.setSubtypeNameOverride("Subtype1")
|
||||
.setLanguageTag("en-US")
|
||||
.build();
|
||||
InputMethodSubtype subtype2 = new InputMethodSubtype.InputMethodSubtypeBuilder()
|
||||
.setSubtypeNameOverride("Subtype2")
|
||||
.setLanguageTag("zh-CN")
|
||||
.setPhysicalKeyboardHint(new ULocale("en_US"), "qwerty")
|
||||
.build();
|
||||
String fakeImeId = "fakeImeId";
|
||||
ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
|
||||
methodMap.put(fakeImeId, new InputMethodInfo("", "", "", ""));
|
||||
ArrayMap<String, List<InputMethodSubtype>> allSubtypes = new ArrayMap<>();
|
||||
allSubtypes.put(fakeImeId, List.of(subtype1, subtype2));
|
||||
|
||||
// Save & load.
|
||||
AtomicFile atomicFile = new AtomicFile(
|
||||
new File(InstrumentationRegistry.getContext().getCacheDir(), "subtypes.xml"));
|
||||
AdditionalSubtypeUtils.saveToFile(allSubtypes, methodMap, atomicFile);
|
||||
ArrayMap<String, List<InputMethodSubtype>> loadedSubtypes = new ArrayMap<>();
|
||||
AdditionalSubtypeUtils.loadFromFile(loadedSubtypes, atomicFile);
|
||||
|
||||
// Verifies the loaded data.
|
||||
assertEquals(1, loadedSubtypes.size());
|
||||
List<InputMethodSubtype> subtypes = loadedSubtypes.get(fakeImeId);
|
||||
assertNotNull(subtypes);
|
||||
assertEquals(2, subtypes.size());
|
||||
|
||||
verifySubtype(subtypes.get(0), subtype1);
|
||||
verifySubtype(subtypes.get(1), subtype2);
|
||||
}
|
||||
|
||||
private void verifySubtype(InputMethodSubtype subtype, InputMethodSubtype expectedSubtype) {
|
||||
assertEquals(expectedSubtype.getLanguageTag(), subtype.getLanguageTag());
|
||||
assertEquals(expectedSubtype.getPhysicalKeyboardHintLanguageTag(),
|
||||
subtype.getPhysicalKeyboardHintLanguageTag());
|
||||
assertEquals(expectedSubtype.getPhysicalKeyboardHintLayoutType(),
|
||||
subtype.getPhysicalKeyboardHintLayoutType());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user