Merge "Specify language tag and layout type when creating virtual keyboard."

This commit is contained in:
Zixuan Qu
2022-12-17 05:01:18 +00:00
committed by Android (Google) Code Review
14 changed files with 352 additions and 50 deletions

View File

@@ -4716,13 +4716,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 {

View File

@@ -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.
*/

View File

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

View File

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

View File

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

View File

@@ -439,7 +439,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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -164,6 +164,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()

View File

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

View File

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