Merge "Adds the new API - InputMethodSubtypeBuilder#setPhysicalKeyboardHint."

This commit is contained in:
Shu Chen
2022-12-19 03:24:21 +00:00
committed by Android (Google) Code Review
7 changed files with 208 additions and 14 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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">

View File

@@ -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)

View File

@@ -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());
}
}