Specify language tag and layout type when creating virtual keyboard.
1) Added languageTag in VirtualKeyboardConfig which holds a BCP 47 language tag. 2) Added layoutType in VIrtualKeyboardConfig which holds the type of the layout such "qwerty". 3) Pass the above layout info to native input reader in a similary way that display ID association is handled. Bug: 237537306 Test: atest VirtualKeyboardTest, atest inputflinger_tests Change-Id: Ib37f4785e6c087d58d7b54565ddbaf257a6c0871
This commit is contained in:
@@ -4705,13 +4705,19 @@ package android.hardware.input {
|
||||
|
||||
public final class VirtualKeyboardConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method @NonNull public String getLanguageTag();
|
||||
method @NonNull public String getLayoutType();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualKeyboardConfig> CREATOR;
|
||||
field public static final String DEFAULT_LANGUAGE_TAG = "en-Latn-US";
|
||||
field public static final String DEFAULT_LAYOUT_TYPE = "qwerty";
|
||||
}
|
||||
|
||||
public static final class VirtualKeyboardConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualKeyboardConfig.Builder> {
|
||||
ctor public VirtualKeyboardConfig.Builder();
|
||||
method @NonNull public android.hardware.input.VirtualKeyboardConfig build();
|
||||
method @NonNull public android.hardware.input.VirtualKeyboardConfig.Builder setLanguageTag(@NonNull String);
|
||||
method @NonNull public android.hardware.input.VirtualKeyboardConfig.Builder setLayoutType(@NonNull String);
|
||||
}
|
||||
|
||||
public class VirtualMouse implements java.io.Closeable {
|
||||
|
||||
@@ -18,9 +18,12 @@ package android.hardware.input;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.icu.util.ULocale;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* Configurations to create virtual keyboard.
|
||||
@@ -29,6 +32,18 @@ import android.os.Parcelable;
|
||||
*/
|
||||
@SystemApi
|
||||
public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implements Parcelable {
|
||||
/**
|
||||
* Default language tag when creating virtual keyboard. Used when the language tag is not set.
|
||||
*/
|
||||
public static final String DEFAULT_LANGUAGE_TAG = "en-Latn-US";
|
||||
/** Default layout type when creating virtual keyboard. Used when the layout type is not set. */
|
||||
public static final String DEFAULT_LAYOUT_TYPE = "qwerty";
|
||||
|
||||
@NonNull
|
||||
private final String mLanguageTag;
|
||||
|
||||
@NonNull
|
||||
private final String mLayoutType;
|
||||
|
||||
@NonNull
|
||||
public static final Creator<VirtualKeyboardConfig> CREATOR =
|
||||
@@ -46,10 +61,30 @@ public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implem
|
||||
|
||||
private VirtualKeyboardConfig(@NonNull Builder builder) {
|
||||
super(builder);
|
||||
mLanguageTag = builder.mLanguageTag;
|
||||
mLayoutType = builder.mLayoutType;
|
||||
}
|
||||
|
||||
private VirtualKeyboardConfig(@NonNull Parcel in) {
|
||||
super(in);
|
||||
mLanguageTag = in.readString8();
|
||||
mLayoutType = in.readString8();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Builder#setLanguageTag().
|
||||
*/
|
||||
@NonNull
|
||||
public String getLanguageTag() {
|
||||
return mLanguageTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Builder#setLayoutType().
|
||||
*/
|
||||
@NonNull
|
||||
public String getLayoutType() {
|
||||
return mLayoutType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,12 +95,66 @@ public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implem
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeString8(mLanguageTag);
|
||||
dest.writeString8(mLayoutType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating a {@link VirtualKeyboardConfig}.
|
||||
*/
|
||||
public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
|
||||
@NonNull
|
||||
private String mLanguageTag = DEFAULT_LANGUAGE_TAG;
|
||||
@NonNull
|
||||
private String mLayoutType = DEFAULT_LAYOUT_TYPE;
|
||||
|
||||
/**
|
||||
* Sets the preferred input language of the virtual keyboard using an IETF
|
||||
* <a href="https://tools.ietf.org/html/bcp47">BCP-47</a>
|
||||
* conformant tag. See {@code keyboardLocale} attribute in
|
||||
* frameworks/base/packages/InputDevices/res/xml/keyboard_layouts.xml for a list of
|
||||
* supported language tags.
|
||||
*
|
||||
* The passed in {@code languageTag} will be canonized using {@link
|
||||
* ULocale} and used by the system as a hint to configure the keyboard layout.
|
||||
*
|
||||
* If {@code languageTag} is not specified, the virtual keyboard will be created with {@link
|
||||
* #DEFAULT_LANGUAGE_TAG}.
|
||||
*
|
||||
* Note that the preferred layout is not guaranteed. If the specified language is
|
||||
* well-formed but not supported, the keyboard will be using English US QWERTY layout.
|
||||
*
|
||||
* @throws IllegalArgumentException if either of the language or country is not present in
|
||||
* the language tag.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setLanguageTag(@NonNull String languageTag) {
|
||||
Objects.requireNonNull(languageTag, "languageTag cannot be null");
|
||||
ULocale locale = ULocale.forLanguageTag(languageTag);
|
||||
if (locale.getLanguage().isEmpty() || locale.getCountry().isEmpty()) {
|
||||
throw new IllegalArgumentException("The language tag is not valid.");
|
||||
}
|
||||
mLanguageTag = ULocale.createCanonical(locale).toLanguageTag();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preferred layout type of the virtual keyboard. See {@code keyboardLayoutType}
|
||||
* attribute in frameworks/base/packages/InputDevices/res/xml/keyboard_layouts.xml for a
|
||||
* list of supported layout types.
|
||||
*
|
||||
* Note that the preferred layout is not guaranteed. If the specified layout type is
|
||||
* well-formed but not supported, the keyboard will be using English US QWERTY layout.
|
||||
*
|
||||
* If not specified, the virtual keyboard will be created with {@link #DEFAULT_LAYOUT_TYPE}.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setLayoutType(@NonNull String layoutType) {
|
||||
Objects.requireNonNull(layoutType, "layoutType cannot be null");
|
||||
mLayoutType = layoutType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link VirtualKeyboardConfig} instance.
|
||||
*/
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.hardware.input.InputDeviceCountryCode;
|
||||
import android.hardware.input.InputDeviceIdentifier;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.lights.LightsManager;
|
||||
import android.icu.util.ULocale;
|
||||
import android.os.Build;
|
||||
import android.os.NullVibrator;
|
||||
import android.os.Parcel;
|
||||
@@ -76,6 +77,10 @@ public final class InputDevice implements Parcelable {
|
||||
private final KeyCharacterMap mKeyCharacterMap;
|
||||
@InputDeviceCountryCode
|
||||
private final int mCountryCode;
|
||||
@Nullable
|
||||
private final String mKeyboardLanguageTag;
|
||||
@Nullable
|
||||
private final String mKeyboardLayoutType;
|
||||
private final boolean mHasVibrator;
|
||||
private final boolean mHasMicrophone;
|
||||
private final boolean mHasButtonUnderPad;
|
||||
@@ -464,6 +469,7 @@ public final class InputDevice implements Parcelable {
|
||||
private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
|
||||
int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
|
||||
KeyCharacterMap keyCharacterMap, @InputDeviceCountryCode int countryCode,
|
||||
@Nullable String keyboardLanguageTag, @Nullable String keyboardLayoutType,
|
||||
boolean hasVibrator, boolean hasMicrophone, boolean hasButtonUnderPad,
|
||||
boolean hasSensor, boolean hasBattery, boolean supportsUsi) {
|
||||
mId = id;
|
||||
@@ -478,6 +484,14 @@ public final class InputDevice implements Parcelable {
|
||||
mKeyboardType = keyboardType;
|
||||
mKeyCharacterMap = keyCharacterMap;
|
||||
mCountryCode = countryCode;
|
||||
if (keyboardLanguageTag != null) {
|
||||
mKeyboardLanguageTag = ULocale
|
||||
.createCanonical(ULocale.forLanguageTag(keyboardLanguageTag))
|
||||
.toLanguageTag();
|
||||
} else {
|
||||
mKeyboardLanguageTag = null;
|
||||
}
|
||||
mKeyboardLayoutType = keyboardLayoutType;
|
||||
mHasVibrator = hasVibrator;
|
||||
mHasMicrophone = hasMicrophone;
|
||||
mHasButtonUnderPad = hasButtonUnderPad;
|
||||
@@ -500,6 +514,8 @@ public final class InputDevice implements Parcelable {
|
||||
mSources = in.readInt();
|
||||
mKeyboardType = in.readInt();
|
||||
mCountryCode = in.readInt();
|
||||
mKeyboardLanguageTag = in.readString8();
|
||||
mKeyboardLayoutType = in.readString8();
|
||||
mHasVibrator = in.readInt() != 0;
|
||||
mHasMicrophone = in.readInt() != 0;
|
||||
mHasButtonUnderPad = in.readInt() != 0;
|
||||
@@ -521,6 +537,7 @@ public final class InputDevice implements Parcelable {
|
||||
|
||||
/**
|
||||
* InputDevice builder used to create an InputDevice for tests in Java.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@@ -543,111 +560,125 @@ public final class InputDevice implements Parcelable {
|
||||
private boolean mHasBattery = false;
|
||||
@InputDeviceCountryCode
|
||||
private int mCountryCode = InputDeviceCountryCode.INVALID;
|
||||
private String mKeyboardLanguageTag = null;
|
||||
private String mKeyboardLayoutType = null;
|
||||
private boolean mSupportsUsi = false;
|
||||
|
||||
/** @see InputDevice#getId() */
|
||||
/** @see InputDevice#getId() */
|
||||
public Builder setId(int id) {
|
||||
mId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getGeneration() */
|
||||
/** @see InputDevice#getGeneration() */
|
||||
public Builder setGeneration(int generation) {
|
||||
mGeneration = generation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getControllerNumber() */
|
||||
/** @see InputDevice#getControllerNumber() */
|
||||
public Builder setControllerNumber(int controllerNumber) {
|
||||
mControllerNumber = controllerNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getName() */
|
||||
/** @see InputDevice#getName() */
|
||||
public Builder setName(String name) {
|
||||
mName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getVendorId() */
|
||||
/** @see InputDevice#getVendorId() */
|
||||
public Builder setVendorId(int vendorId) {
|
||||
mVendorId = vendorId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getProductId() */
|
||||
/** @see InputDevice#getProductId() */
|
||||
public Builder setProductId(int productId) {
|
||||
mProductId = productId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getDescriptor() */
|
||||
/** @see InputDevice#getDescriptor() */
|
||||
public Builder setDescriptor(String descriptor) {
|
||||
mDescriptor = descriptor;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#isExternal() */
|
||||
/** @see InputDevice#isExternal() */
|
||||
public Builder setExternal(boolean external) {
|
||||
mIsExternal = external;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getSources() */
|
||||
/** @see InputDevice#getSources() */
|
||||
public Builder setSources(int sources) {
|
||||
mSources = sources;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getKeyboardType() */
|
||||
/** @see InputDevice#getKeyboardType() */
|
||||
public Builder setKeyboardType(int keyboardType) {
|
||||
mKeyboardType = keyboardType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getKeyCharacterMap() */
|
||||
/** @see InputDevice#getKeyCharacterMap() */
|
||||
public Builder setKeyCharacterMap(KeyCharacterMap keyCharacterMap) {
|
||||
mKeyCharacterMap = keyCharacterMap;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getVibrator() */
|
||||
/** @see InputDevice#getVibrator() */
|
||||
public Builder setHasVibrator(boolean hasVibrator) {
|
||||
mHasVibrator = hasVibrator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#hasMicrophone() */
|
||||
/** @see InputDevice#hasMicrophone() */
|
||||
public Builder setHasMicrophone(boolean hasMicrophone) {
|
||||
mHasMicrophone = hasMicrophone;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#hasButtonUnderPad() */
|
||||
/** @see InputDevice#hasButtonUnderPad() */
|
||||
public Builder setHasButtonUnderPad(boolean hasButtonUnderPad) {
|
||||
mHasButtonUnderPad = hasButtonUnderPad;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#hasSensor() */
|
||||
/** @see InputDevice#hasSensor() */
|
||||
public Builder setHasSensor(boolean hasSensor) {
|
||||
mHasSensor = hasSensor;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#hasBattery() */
|
||||
/** @see InputDevice#hasBattery() */
|
||||
public Builder setHasBattery(boolean hasBattery) {
|
||||
mHasBattery = hasBattery;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getCountryCode() */
|
||||
/** @see InputDevice#getCountryCode() */
|
||||
public Builder setCountryCode(@InputDeviceCountryCode int countryCode) {
|
||||
mCountryCode = countryCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#supportsUsi() () */
|
||||
/** @see InputDevice#getKeyboardLanguageTag() */
|
||||
public Builder setKeyboardLanguageTag(String keyboardLanguageTag) {
|
||||
mKeyboardLanguageTag = keyboardLanguageTag;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#getKeyboardLayoutType() */
|
||||
public Builder setKeyboardLayoutType(String keyboardLayoutType) {
|
||||
mKeyboardLayoutType = keyboardLayoutType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see InputDevice#supportsUsi() () */
|
||||
public Builder setSupportsUsi(boolean supportsUsi) {
|
||||
mSupportsUsi = supportsUsi;
|
||||
return this;
|
||||
@@ -657,8 +688,8 @@ public final class InputDevice implements Parcelable {
|
||||
public InputDevice build() {
|
||||
return new InputDevice(mId, mGeneration, mControllerNumber, mName, mVendorId,
|
||||
mProductId, mDescriptor, mIsExternal, mSources, mKeyboardType, mKeyCharacterMap,
|
||||
mCountryCode, mHasVibrator, mHasMicrophone, mHasButtonUnderPad, mHasSensor,
|
||||
mHasBattery, mSupportsUsi);
|
||||
mCountryCode, mKeyboardLanguageTag, mKeyboardLayoutType, mHasVibrator,
|
||||
mHasMicrophone, mHasButtonUnderPad, mHasSensor, mHasBattery, mSupportsUsi);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -887,8 +918,31 @@ public final class InputDevice implements Parcelable {
|
||||
return mCountryCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keyboard language as an IETF
|
||||
* <a href="https://tools.ietf.org/html/bcp47">BCP-47</a>
|
||||
* conformant tag if available.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public String getKeyboardLanguageTag() {
|
||||
return mKeyboardLanguageTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keyboard layout type if available.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public String getKeyboardLayoutType() {
|
||||
return mKeyboardLayoutType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the device is capable of producing the list of keycodes.
|
||||
*
|
||||
* @param keys The list of android keycodes to check for.
|
||||
* @return An array of booleans where each member specifies whether the device is capable of
|
||||
* generating the keycode given by the corresponding value at the same index in the keys array.
|
||||
@@ -1340,6 +1394,8 @@ public final class InputDevice implements Parcelable {
|
||||
out.writeInt(mSources);
|
||||
out.writeInt(mKeyboardType);
|
||||
out.writeInt(mCountryCode);
|
||||
out.writeString8(mKeyboardLanguageTag);
|
||||
out.writeString8(mKeyboardLayoutType);
|
||||
out.writeInt(mHasVibrator ? 1 : 0);
|
||||
out.writeInt(mHasMicrophone ? 1 : 0);
|
||||
out.writeInt(mHasButtonUnderPad ? 1 : 0);
|
||||
|
||||
@@ -48,6 +48,18 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::optional<KeyboardLayoutInfo> layoutInfo = deviceInfo.getKeyboardLayoutInfo();
|
||||
ScopedLocalRef<jstring> keyboardLanguageTagObj(env,
|
||||
env->NewStringUTF(
|
||||
layoutInfo
|
||||
? layoutInfo->languageTag.c_str()
|
||||
: NULL));
|
||||
ScopedLocalRef<jstring> keyboardLayoutTypeObj(env,
|
||||
env->NewStringUTF(
|
||||
layoutInfo
|
||||
? layoutInfo->layoutType.c_str()
|
||||
: NULL));
|
||||
|
||||
ScopedLocalRef<jobject> kcmObj(env,
|
||||
android_view_KeyCharacterMap_create(env, deviceInfo.getId(),
|
||||
deviceInfo.getKeyCharacterMap()));
|
||||
@@ -66,7 +78,8 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
|
||||
static_cast<int32_t>(ident.product), descriptorObj.get(),
|
||||
deviceInfo.isExternal(), deviceInfo.getSources(),
|
||||
deviceInfo.getKeyboardType(), kcmObj.get(),
|
||||
deviceInfo.getCountryCode(), deviceInfo.hasVibrator(),
|
||||
deviceInfo.getCountryCode(), keyboardLanguageTagObj.get(),
|
||||
keyboardLayoutTypeObj.get(), deviceInfo.hasVibrator(),
|
||||
deviceInfo.hasMic(), deviceInfo.hasButtonUnderPad(),
|
||||
deviceInfo.hasSensor(), deviceInfo.hasBattery(),
|
||||
deviceInfo.supportsUsi()));
|
||||
@@ -91,10 +104,10 @@ int register_android_view_InputDevice(JNIEnv* env)
|
||||
gInputDeviceClassInfo.clazz = FindClassOrDie(env, "android/view/InputDevice");
|
||||
gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz);
|
||||
|
||||
gInputDeviceClassInfo.ctor =
|
||||
GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
|
||||
"(IIILjava/lang/String;IILjava/lang/"
|
||||
"String;ZIILandroid/view/KeyCharacterMap;IZZZZZZ)V");
|
||||
gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
|
||||
"(IIILjava/lang/String;IILjava/lang/"
|
||||
"String;ZIILandroid/view/KeyCharacterMap;ILjava/"
|
||||
"lang/String;Ljava/lang/String;ZZZZZZ)V");
|
||||
|
||||
gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
|
||||
"addMotionRange", "(IIFFFFF)V");
|
||||
|
||||
@@ -142,17 +142,18 @@ class InputController {
|
||||
}
|
||||
}
|
||||
|
||||
void createKeyboard(@NonNull String deviceName,
|
||||
int vendorId,
|
||||
int productId,
|
||||
@NonNull IBinder deviceToken,
|
||||
int displayId) {
|
||||
void createKeyboard(@NonNull String deviceName, int vendorId, int productId,
|
||||
@NonNull IBinder deviceToken, int displayId, @NonNull String languageTag,
|
||||
@NonNull String layoutType) {
|
||||
final String phys = createPhys(PHYS_TYPE_KEYBOARD);
|
||||
mInputManagerInternal.addKeyboardLayoutAssociation(phys, languageTag,
|
||||
layoutType);
|
||||
try {
|
||||
createDeviceInternal(InputDeviceDescriptor.TYPE_KEYBOARD, deviceName, vendorId,
|
||||
productId, deviceToken, displayId, phys,
|
||||
() -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys));
|
||||
} catch (DeviceCreationException e) {
|
||||
mInputManagerInternal.removeKeyboardLayoutAssociation(phys);
|
||||
throw new RuntimeException(
|
||||
"Failed to create virtual keyboard device '" + deviceName + "'.", e);
|
||||
}
|
||||
@@ -233,12 +234,16 @@ class InputController {
|
||||
InputDeviceDescriptor inputDeviceDescriptor) {
|
||||
token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
|
||||
mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
|
||||
|
||||
InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys());
|
||||
String phys = inputDeviceDescriptor.getPhys();
|
||||
InputManager.getInstance().removeUniqueIdAssociation(phys);
|
||||
// Type associations are added in the case of navigation touchpads. Those should be removed
|
||||
// once the input device gets closed.
|
||||
if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD) {
|
||||
mInputManagerInternal.unsetTypeAssociation(inputDeviceDescriptor.getPhys());
|
||||
mInputManagerInternal.unsetTypeAssociation(phys);
|
||||
}
|
||||
|
||||
if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_KEYBOARD) {
|
||||
mInputManagerInternal.removeKeyboardLayoutAssociation(phys);
|
||||
}
|
||||
|
||||
// Reset values to the default if all virtual mice are unregistered, or set display
|
||||
|
||||
@@ -435,7 +435,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(),
|
||||
config.getProductId(), deviceToken, config.getAssociatedDisplayId());
|
||||
config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
|
||||
config.getLanguageTag(), config.getLayoutType());
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
|
||||
@@ -186,4 +186,24 @@ public abstract class InputManagerInternal {
|
||||
* @param inputPort The port of the input device.
|
||||
*/
|
||||
public abstract void unsetTypeAssociation(@NonNull String inputPort);
|
||||
|
||||
/**
|
||||
* Add a mapping from the input port and a keyboard layout, by unique id. Input
|
||||
* ports are expected to be unique.
|
||||
*
|
||||
* @param inputPort The port of the input device.
|
||||
* @param languageTag the language of the input device as an IETF
|
||||
* <a href="https://tools.ietf.org/html/bcp47">BCP-47</a>
|
||||
* conformant tag.
|
||||
* @param layoutType the layout type such as "qwerty" or "azerty".
|
||||
*/
|
||||
public abstract void addKeyboardLayoutAssociation(@NonNull String inputPort,
|
||||
@NonNull String languageTag, @NonNull String layoutType);
|
||||
|
||||
/**
|
||||
* Removes the mapping from input port to the keyboard layout identifier.
|
||||
*
|
||||
* @param inputPort The port of the input device.
|
||||
*/
|
||||
public abstract void removeKeyboardLayoutAssociation(@NonNull String inputPort);
|
||||
}
|
||||
|
||||
@@ -247,6 +247,11 @@ public class InputManagerService extends IInputManager.Stub
|
||||
private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<>();
|
||||
@GuardedBy("mAssociationsLock")
|
||||
private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
|
||||
// The map from input port (String) to the keyboard layout identifiers (comma separated string
|
||||
// containing language tag and layout type) associated with the corresponding keyboard device.
|
||||
// Currently only accessed by InputReader.
|
||||
@GuardedBy("mAssociationsLock")
|
||||
private final Map<String, String> mKeyboardLayoutAssociations = new ArrayMap<>();
|
||||
|
||||
// Stores input ports associated with device types. For example, adding an association
|
||||
// {"123", "touchNavigation"} here would mean that a touch device appearing at port "123" would
|
||||
@@ -1901,8 +1906,7 @@ public class InputManagerService extends IInputManager.Stub
|
||||
if (!checkCallingPermission(
|
||||
android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
|
||||
"removeUniqueIdAssociation()")) {
|
||||
throw new SecurityException(
|
||||
"Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
|
||||
throw new SecurityException("Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
|
||||
}
|
||||
|
||||
Objects.requireNonNull(inputPort);
|
||||
@@ -1929,6 +1933,27 @@ public class InputManagerService extends IInputManager.Stub
|
||||
mNative.changeTypeAssociation();
|
||||
}
|
||||
|
||||
private void addKeyboardLayoutAssociation(@NonNull String inputPort,
|
||||
@NonNull String languageTag, @NonNull String layoutType) {
|
||||
Objects.requireNonNull(inputPort);
|
||||
Objects.requireNonNull(languageTag);
|
||||
Objects.requireNonNull(layoutType);
|
||||
|
||||
synchronized (mAssociationsLock) {
|
||||
mKeyboardLayoutAssociations.put(inputPort,
|
||||
TextUtils.formatSimple("%s,%s", languageTag, layoutType));
|
||||
}
|
||||
mNative.changeKeyboardLayoutAssociation();
|
||||
}
|
||||
|
||||
private void removeKeyboardLayoutAssociation(@NonNull String inputPort) {
|
||||
Objects.requireNonNull(inputPort);
|
||||
synchronized (mAssociationsLock) {
|
||||
mKeyboardLayoutAssociations.remove(inputPort);
|
||||
}
|
||||
mNative.changeKeyboardLayoutAssociation();
|
||||
}
|
||||
|
||||
@Override // Binder call
|
||||
public InputSensorInfo[] getSensorList(int deviceId) {
|
||||
return mNative.getSensorList(deviceId);
|
||||
@@ -2673,6 +2698,17 @@ public class InputManagerService extends IInputManager.Stub
|
||||
return flatten(associations);
|
||||
}
|
||||
|
||||
// Native callback
|
||||
@SuppressWarnings("unused")
|
||||
@VisibleForTesting
|
||||
private String[] getKeyboardLayoutAssociations() {
|
||||
final Map<String, String> configs = new ArrayMap<>();
|
||||
synchronized (mAssociationsLock) {
|
||||
configs.putAll(mKeyboardLayoutAssociations);
|
||||
}
|
||||
return flatten(configs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if an input device could dispatch to the given display".
|
||||
* @param deviceId The input device id.
|
||||
@@ -3316,6 +3352,18 @@ public class InputManagerService extends IInputManager.Stub
|
||||
public void unsetTypeAssociation(@NonNull String inputPort) {
|
||||
unsetTypeAssociationInternal(inputPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addKeyboardLayoutAssociation(@NonNull String inputPort,
|
||||
@NonNull String languageTag, @NonNull String layoutType) {
|
||||
InputManagerService.this.addKeyboardLayoutAssociation(inputPort,
|
||||
languageTag, layoutType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeKeyboardLayoutAssociation(@NonNull String inputPort) {
|
||||
InputManagerService.this.removeKeyboardLayoutAssociation(inputPort);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -188,6 +188,8 @@ interface NativeInputManagerService {
|
||||
|
||||
void changeTypeAssociation();
|
||||
|
||||
void changeKeyboardLayoutAssociation();
|
||||
|
||||
void notifyPointerDisplayIdChanged();
|
||||
|
||||
void setDisplayEligibilityForPointerCapture(int displayId, boolean enabled);
|
||||
@@ -404,6 +406,9 @@ interface NativeInputManagerService {
|
||||
@Override
|
||||
public native void changeTypeAssociation();
|
||||
|
||||
@Override
|
||||
public native void changeKeyboardLayoutAssociation();
|
||||
|
||||
@Override
|
||||
public native void notifyPointerDisplayIdChanged();
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ static struct {
|
||||
jmethodID getInputPortAssociations;
|
||||
jmethodID getInputUniqueIdAssociations;
|
||||
jmethodID getDeviceTypeAssociations;
|
||||
jmethodID getKeyboardLayoutAssociations;
|
||||
jmethodID getKeyRepeatTimeout;
|
||||
jmethodID getKeyRepeatDelay;
|
||||
jmethodID getHoverTapTimeout;
|
||||
@@ -412,8 +413,10 @@ private:
|
||||
void ensureSpriteControllerLocked();
|
||||
sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
|
||||
static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
|
||||
std::unordered_map<std::string, std::string> readMapFromInterleavedJavaArray(
|
||||
jmethodID method, const char* methodName);
|
||||
template <typename T>
|
||||
std::unordered_map<std::string, T> readMapFromInterleavedJavaArray(
|
||||
jmethodID method, const char* methodName,
|
||||
std::function<T(std::string)> opOnValue = [](auto&& v) { return std::move(v); });
|
||||
|
||||
static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); }
|
||||
};
|
||||
@@ -588,12 +591,23 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
|
||||
}
|
||||
|
||||
outConfig->uniqueIdAssociations =
|
||||
readMapFromInterleavedJavaArray(gServiceClassInfo.getInputUniqueIdAssociations,
|
||||
"getInputUniqueIdAssociations");
|
||||
readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
|
||||
.getInputUniqueIdAssociations,
|
||||
"getInputUniqueIdAssociations");
|
||||
|
||||
outConfig->deviceTypeAssociations =
|
||||
readMapFromInterleavedJavaArray(gServiceClassInfo.getDeviceTypeAssociations,
|
||||
"getDeviceTypeAssociations");
|
||||
readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
|
||||
.getDeviceTypeAssociations,
|
||||
"getDeviceTypeAssociations");
|
||||
outConfig->keyboardLayoutAssociations = readMapFromInterleavedJavaArray<
|
||||
KeyboardLayoutInfo>(gServiceClassInfo.getKeyboardLayoutAssociations,
|
||||
"getKeyboardLayoutAssociations", [](auto&& layoutIdentifier) {
|
||||
size_t commaPos = layoutIdentifier.find(',');
|
||||
std::string languageTag = layoutIdentifier.substr(0, commaPos);
|
||||
std::string layoutType = layoutIdentifier.substr(commaPos + 1);
|
||||
return KeyboardLayoutInfo(std::move(languageTag),
|
||||
std::move(layoutType));
|
||||
});
|
||||
|
||||
jint hoverTapTimeout = env->CallIntMethod(mServiceObj,
|
||||
gServiceClassInfo.getHoverTapTimeout);
|
||||
@@ -643,16 +657,18 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
|
||||
} // release lock
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> NativeInputManager::readMapFromInterleavedJavaArray(
|
||||
jmethodID method, const char* methodName) {
|
||||
template <typename T>
|
||||
std::unordered_map<std::string, T> NativeInputManager::readMapFromInterleavedJavaArray(
|
||||
jmethodID method, const char* methodName, std::function<T(std::string)> opOnValue) {
|
||||
JNIEnv* env = jniEnv();
|
||||
jobjectArray javaArray = jobjectArray(env->CallObjectMethod(mServiceObj, method));
|
||||
std::unordered_map<std::string, std::string> map;
|
||||
std::unordered_map<std::string, T> map;
|
||||
if (!checkAndClearExceptionFromCallback(env, methodName) && javaArray) {
|
||||
jsize length = env->GetArrayLength(javaArray);
|
||||
for (jsize i = 0; i < length / 2; i++) {
|
||||
std::string key = getStringElementFromJavaArray(env, javaArray, 2 * i);
|
||||
std::string value = getStringElementFromJavaArray(env, javaArray, 2 * i + 1);
|
||||
T value =
|
||||
opOnValue(std::move(getStringElementFromJavaArray(env, javaArray, 2 * i + 1)));
|
||||
map.insert({key, value});
|
||||
}
|
||||
}
|
||||
@@ -2256,6 +2272,12 @@ static void nativeChangeTypeAssociation(JNIEnv* env, jobject nativeImplObj) {
|
||||
InputReaderConfiguration::CHANGE_DEVICE_TYPE);
|
||||
}
|
||||
|
||||
static void changeKeyboardLayoutAssociation(JNIEnv* env, jobject nativeImplObj) {
|
||||
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
|
||||
im->getInputManager()->getReader().requestRefreshConfiguration(
|
||||
InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION);
|
||||
}
|
||||
|
||||
static void nativeSetMotionClassifierEnabled(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
|
||||
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
|
||||
|
||||
@@ -2445,6 +2467,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
|
||||
{"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
|
||||
{"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
|
||||
{"changeTypeAssociation", "()V", (void*)nativeChangeTypeAssociation},
|
||||
{"changeKeyboardLayoutAssociation", "()V", (void*)changeKeyboardLayoutAssociation},
|
||||
{"setDisplayEligibilityForPointerCapture", "(IZ)V",
|
||||
(void*)nativeSetDisplayEligibilityForPointerCapture},
|
||||
{"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
|
||||
@@ -2569,6 +2592,9 @@ int register_android_server_InputManager(JNIEnv* env) {
|
||||
GET_METHOD_ID(gServiceClassInfo.getDeviceTypeAssociations, clazz, "getDeviceTypeAssociations",
|
||||
"()[Ljava/lang/String;");
|
||||
|
||||
GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutAssociations, clazz,
|
||||
"getKeyboardLayoutAssociations", "()[Ljava/lang/String;");
|
||||
|
||||
GET_METHOD_ID(gServiceClassInfo.getKeyRepeatTimeout, clazz,
|
||||
"getKeyRepeatTimeout", "()I");
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@ import org.mockito.MockitoAnnotations;
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
public class InputControllerTest {
|
||||
private static final String LANGUAGE_TAG = "en-US";
|
||||
private static final String LAYOUT_TYPE = "qwerty";
|
||||
|
||||
@Mock
|
||||
private InputManagerInternal mInputManagerInternalMock;
|
||||
@@ -100,7 +102,7 @@ public class InputControllerTest {
|
||||
|
||||
final IBinder device2Token = new Binder("device2");
|
||||
mInputController.createKeyboard("keyboard", /*vendorId= */2, /*productId= */ 2,
|
||||
device2Token, 2);
|
||||
device2Token, 2, LANGUAGE_TAG, LAYOUT_TYPE);
|
||||
int device2Id = mInputController.getInputDeviceId(device2Token);
|
||||
|
||||
assertWithMessage("Different devices should have different id").that(
|
||||
@@ -178,4 +180,17 @@ public class InputControllerTest {
|
||||
verify(mInputManagerInternalMock).unsetTypeAssociation(
|
||||
startsWith("virtualNavigationTouchpad:"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createKeyboard_addAndRemoveKeyboardLayoutAssociation() {
|
||||
final IBinder deviceToken = new Binder("device");
|
||||
|
||||
mInputController.createKeyboard("keyboard", /*vendorId= */2, /*productId= */ 2, deviceToken,
|
||||
2, LANGUAGE_TAG, LAYOUT_TYPE);
|
||||
verify(mInputManagerInternalMock).addKeyboardLayoutAssociation(anyString(),
|
||||
eq(LANGUAGE_TAG), eq(LAYOUT_TYPE));
|
||||
|
||||
mInputController.unregisterInputDevice(deviceToken);
|
||||
verify(mInputManagerInternalMock).removeKeyboardLayoutAssociation(anyString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +159,8 @@ public class VirtualDeviceManagerServiceTest {
|
||||
.setProductId(PRODUCT_ID)
|
||||
.setInputDeviceName(DEVICE_NAME)
|
||||
.setAssociatedDisplayId(DISPLAY_ID)
|
||||
.setLanguageTag(VirtualKeyboardConfig.DEFAULT_LANGUAGE_TAG)
|
||||
.setLayoutType(VirtualKeyboardConfig.DEFAULT_LAYOUT_TYPE)
|
||||
.build();
|
||||
private static final VirtualMouseConfig MOUSE_CONFIG =
|
||||
new VirtualMouseConfig.Builder()
|
||||
|
||||
@@ -27,8 +27,6 @@ import android.view.Display
|
||||
import android.view.PointerIcon
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
@@ -38,6 +36,7 @@ import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.doAnswer
|
||||
import org.mockito.Mockito.never
|
||||
@@ -45,8 +44,9 @@ import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.verifyNoMoreInteractions
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Tests for {@link InputManagerService}.
|
||||
@@ -311,6 +311,18 @@ class InputManagerServiceTests {
|
||||
assertTrue(service.getDeviceTypeAssociations().isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddAndRemoveVirtualmKeyboardLayoutAssociation() {
|
||||
val inputPort = "input port"
|
||||
val languageTag = "language"
|
||||
val layoutType = "layoutType"
|
||||
localService.addKeyboardLayoutAssociation(inputPort, languageTag, layoutType)
|
||||
verify(native).changeKeyboardLayoutAssociation()
|
||||
|
||||
localService.removeKeyboardLayoutAssociation(inputPort)
|
||||
verify(native, times(2)).changeKeyboardLayoutAssociation()
|
||||
}
|
||||
|
||||
private fun setVirtualMousePointerDisplayIdAndVerify(overrideDisplayId: Int) {
|
||||
val thread = Thread { localService.setVirtualMousePointerDisplayId(overrideDisplayId) }
|
||||
thread.start()
|
||||
|
||||
@@ -56,6 +56,8 @@ public class InputDeviceTest {
|
||||
assertEquals(device.getSources(), outDevice.getSources());
|
||||
assertEquals(device.getKeyboardType(), outDevice.getKeyboardType());
|
||||
assertEquals(device.getCountryCode(), outDevice.getCountryCode());
|
||||
assertEquals(device.getKeyboardLanguageTag(), outDevice.getKeyboardLanguageTag());
|
||||
assertEquals(device.getKeyboardLayoutType(), outDevice.getKeyboardLayoutType());
|
||||
assertEquals(device.getMotionRanges().size(), outDevice.getMotionRanges().size());
|
||||
|
||||
KeyCharacterMap keyCharacterMap = device.getKeyCharacterMap();
|
||||
@@ -87,6 +89,8 @@ public class InputDeviceTest {
|
||||
.setHasSensor(true)
|
||||
.setHasBattery(true)
|
||||
.setCountryCode(InputDeviceCountryCode.INTERNATIONAL)
|
||||
.setKeyboardLanguageTag("en-US")
|
||||
.setKeyboardLayoutType("qwerty")
|
||||
.setSupportsUsi(true)
|
||||
.build();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user