summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Vaibhav Devmurari <vdevmurari@google.com> 2023-04-24 11:01:10 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-04-24 11:01:10 +0000
commitec725e14e8c5bbe66e6ad2a2cb7ff6b617abf7fb (patch)
tree0d15a4024a44ebf9dee4db57da8c39f3fadd08b2
parent78a9212630ce455f4908269f5a3ee4479b604eb4 (diff)
parenteaed646cb9c86e283ccb1cfa4998fcb8796a3564 (diff)
Merge "Maintain keyboard configuration and reload layouts if changed" into udc-dev
-rw-r--r--services/core/java/com/android/server/input/KeyboardLayoutManager.java224
1 files changed, 154 insertions, 70 deletions
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 72c7dadac271..b4c12a400f96 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,6 +16,8 @@
package com.android.server.input;
+import android.annotation.AnyThread;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -99,6 +101,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
+ private static final int MSG_CURRENT_IME_INFO_CHANGED = 5;
private final Context mContext;
private final NativeInputManagerService mNative;
@@ -108,16 +111,17 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
private final Handler mHandler;
// Connected keyboards with associated keyboard layouts (either auto-detected or manually
- // selected layout). If the mapped value is null/empty, it means that no layout has been
- // configured for the keyboard and user might need to manually configure it from the Settings.
- private final SparseArray<Set<String>> mConfiguredKeyboards = new SparseArray<>();
+ // selected layout).
+ private final SparseArray<KeyboardConfiguration> mConfiguredKeyboards = new SparseArray<>();
private Toast mSwitchedKeyboardLayoutToast;
// This cache stores "best-matched" layouts so that we don't need to run the matching
// algorithm repeatedly.
@GuardedBy("mKeyboardLayoutCache")
private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>();
+ private final Object mImeInfoLock = new Object();
@Nullable
+ @GuardedBy("mImeInfoLock")
private ImeInfo mCurrentImeInfo;
KeyboardLayoutManager(Context context, NativeInputManagerService nativeService,
@@ -155,26 +159,32 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
@Override
+ @MainThread
public void onInputDeviceAdded(int deviceId) {
onInputDeviceChanged(deviceId);
- if (useNewSettingsUi()) {
- // Force native callback to set up keyboard layout overlay for newly added keyboards
- reloadKeyboardLayouts();
- }
}
@Override
+ @MainThread
public void onInputDeviceRemoved(int deviceId) {
mConfiguredKeyboards.remove(deviceId);
maybeUpdateNotification();
}
@Override
+ @MainThread
public void onInputDeviceChanged(int deviceId) {
final InputDevice inputDevice = getInputDevice(deviceId);
if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
return;
}
+ KeyboardConfiguration config = mConfiguredKeyboards.get(deviceId);
+ if (config == null) {
+ config = new KeyboardConfiguration();
+ mConfiguredKeyboards.put(deviceId, config);
+ }
+
+ boolean needToShowNotification = false;
if (!useNewSettingsUi()) {
synchronized (mDataStore) {
String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
@@ -182,54 +192,66 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
layout = getDefaultKeyboardLayout(inputDevice);
if (layout != null) {
setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
- } else {
- mConfiguredKeyboards.put(inputDevice.getId(), new HashSet<>());
}
}
+ config.setCurrentLayout(layout);
+ if (layout == null) {
+ // In old settings show notification always until user manually selects a
+ // layout in the settings.
+ needToShowNotification = true;
+ }
}
} else {
final InputDeviceIdentifier identifier = inputDevice.getIdentifier();
final String key = getLayoutDescriptor(identifier);
Set<String> selectedLayouts = new HashSet<>();
- boolean needToShowMissingLayoutNotification = false;
for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) {
// Check if the layout has been previously configured
String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle,
imeInfo.mImeSubtype));
if (layout == null) {
- needToShowMissingLayoutNotification = true;
- continue;
+ // If even one layout not configured properly, we need to ask user to configure
+ // the keyboard properly from the Settings.
+ selectedLayouts.clear();
+ break;
}
selectedLayouts.add(layout);
}
- if (needToShowMissingLayoutNotification) {
- // If even one layout not configured properly we will show configuration
- // notification allowing user to set the keyboard layout.
- selectedLayouts.clear();
- }
-
if (DEBUG) {
Slog.d(TAG,
"Layouts selected for input device: " + identifier + " -> selectedLayouts: "
+ selectedLayouts);
}
- mConfiguredKeyboards.set(inputDevice.getId(), selectedLayouts);
+
+ config.setConfiguredLayouts(selectedLayouts);
+
+ // Update current layout: If there is a change then need to reload.
+ synchronized (mImeInfoLock) {
+ String layout = getKeyboardLayoutForInputDeviceInternal(
+ inputDevice.getIdentifier(), mCurrentImeInfo);
+ if (!Objects.equals(layout, config.getCurrentLayout())) {
+ config.setCurrentLayout(layout);
+ mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+ }
+ }
synchronized (mDataStore) {
try {
- if (!mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
- // No need to show the notification only if layout selection didn't change
+ if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
+ // Need to show the notification only if layout selection changed
// from the previous configuration
- return;
+ needToShowNotification = true;
}
} finally {
mDataStore.saveIfNeeded();
}
}
}
- maybeUpdateNotification();
+ if (needToShowNotification) {
+ maybeUpdateNotification();
+ }
}
private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
@@ -323,12 +345,14 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
reloadKeyboardLayouts();
}
+ @AnyThread
public KeyboardLayout[] getKeyboardLayouts() {
final ArrayList<KeyboardLayout> list = new ArrayList<>();
visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> list.add(layout));
return list.toArray(new KeyboardLayout[0]);
}
+ @AnyThread
public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
final InputDeviceIdentifier identifier) {
if (useNewSettingsUi()) {
@@ -375,6 +399,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
KeyboardLayout[]::new);
}
+ @AnyThread
@Nullable
public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
Objects.requireNonNull(keyboardLayoutDescriptor,
@@ -543,6 +568,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
return key.toString();
}
+ @AnyThread
@Nullable
public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
if (useNewSettingsUi()) {
@@ -566,6 +592,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
+ @AnyThread
public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor) {
if (useNewSettingsUi()) {
@@ -592,6 +619,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
+ @AnyThread
public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
if (useNewSettingsUi()) {
Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported");
@@ -608,6 +636,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
+ @AnyThread
public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor) {
if (useNewSettingsUi()) {
@@ -635,6 +664,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
+ @AnyThread
public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor) {
if (useNewSettingsUi()) {
@@ -667,6 +697,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
+ @AnyThread
public void switchKeyboardLayout(int deviceId, int direction) {
if (useNewSettingsUi()) {
Slog.e(TAG, "switchKeyboardLayout API not supported");
@@ -675,7 +706,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
}
- // Must be called on handler.
+ @MainThread
private void handleSwitchKeyboardLayout(int deviceId, int direction) {
final InputDevice device = getInputDevice(deviceId);
if (device != null) {
@@ -713,23 +744,14 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
@Nullable
+ @AnyThread
public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
String keyboardLayoutDescriptor;
if (useNewSettingsUi()) {
- InputDevice inputDevice = getInputDevice(identifier);
- if (inputDevice == null) {
- // getKeyboardLayoutOverlay() called before input device added completely. Need
- // to wait till the device is added which will call reloadKeyboardLayouts()
- return null;
+ synchronized (mImeInfoLock) {
+ keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
+ mCurrentImeInfo);
}
- if (mCurrentImeInfo == null) {
- // Haven't received onInputMethodSubtypeChanged() callback from IMMS. Will reload
- // keyboard layouts once we receive the callback.
- return null;
- }
-
- keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
- mCurrentImeInfo);
} else {
keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
}
@@ -755,6 +777,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
return result;
}
+ @AnyThread
@Nullable
public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
@UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@@ -773,6 +796,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
return layout;
}
+ @AnyThread
public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
@UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@Nullable InputMethodSubtype imeSubtype,
@@ -783,8 +807,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
Objects.requireNonNull(keyboardLayoutDescriptor,
"keyboardLayoutDescriptor must not be null");
- String key = createLayoutKey(identifier, userId,
- InputMethodSubtypeHandle.of(imeInfo, imeSubtype));
+ String key = createLayoutKey(identifier,
+ new ImeInfo(userId, InputMethodSubtypeHandle.of(imeInfo, imeSubtype), imeSubtype));
synchronized (mDataStore) {
try {
// Key for storing into data store = <device descriptor>,<userId>,<subtypeHandle>
@@ -803,6 +827,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
+ @AnyThread
public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
@UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@Nullable InputMethodSubtype imeSubtype) {
@@ -815,8 +840,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
private KeyboardLayout[] getKeyboardLayoutListForInputDeviceInternal(
- InputDeviceIdentifier identifier, ImeInfo imeInfo) {
- String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
+ InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) {
+ String key = createLayoutKey(identifier, imeInfo);
// Fetch user selected layout and always include it in layout list.
String userSelectedLayout;
@@ -826,7 +851,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
String imeLanguageTag;
- if (imeInfo.mImeSubtype == null) {
+ if (imeInfo == null || imeInfo.mImeSubtype == null) {
imeLanguageTag = "";
} else {
ULocale imeLocale = imeInfo.mImeSubtype.getPhysicalKeyboardHintLanguageTag();
@@ -866,6 +891,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
return potentialLayouts.toArray(new KeyboardLayout[0]);
}
+ @AnyThread
public void onInputMethodSubtypeChanged(@UserIdInt int userId,
@Nullable InputMethodSubtypeHandle subtypeHandle,
@Nullable InputMethodSubtype subtype) {
@@ -879,25 +905,45 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
return;
}
- if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle)
- || mCurrentImeInfo.mUserId != userId) {
- mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype);
- mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
- if (DEBUG) {
- Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId
- + " subtypeHandle=" + subtypeHandle);
+ synchronized (mImeInfoLock) {
+ if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle)
+ || mCurrentImeInfo.mUserId != userId) {
+ mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype);
+ mHandler.sendEmptyMessage(MSG_CURRENT_IME_INFO_CHANGED);
+ if (DEBUG) {
+ Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId
+ + " subtypeHandle=" + subtypeHandle);
+ }
+ }
+ }
+ }
+
+ @MainThread
+ private void onCurrentImeInfoChanged() {
+ synchronized (mImeInfoLock) {
+ for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
+ InputDevice inputDevice = Objects.requireNonNull(
+ getInputDevice(mConfiguredKeyboards.keyAt(i)));
+ String layout = getKeyboardLayoutForInputDeviceInternal(inputDevice.getIdentifier(),
+ mCurrentImeInfo);
+ KeyboardConfiguration config = mConfiguredKeyboards.valueAt(i);
+ if (!Objects.equals(layout, config.getCurrentLayout())) {
+ config.setCurrentLayout(layout);
+ mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+ return;
+ }
}
}
}
@Nullable
private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier,
- ImeInfo imeInfo) {
+ @Nullable ImeInfo imeInfo) {
InputDevice inputDevice = getInputDevice(identifier);
if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
return null;
}
- String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
+ String key = createLayoutKey(identifier, imeInfo);
String layout;
synchronized (mDataStore) {
layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
@@ -923,11 +969,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
@Nullable
private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice,
- ImeInfo imeInfo, KeyboardLayout[] layoutList) {
- if (imeInfo.mImeSubtypeHandle == null) {
- return null;
- }
-
+ @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) {
Arrays.sort(layoutList);
// Check <VendorID, ProductID> matching for explicitly declared custom KCM files.
@@ -961,12 +1003,12 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
- InputMethodSubtype subtype = imeInfo.mImeSubtype;
- // Can't auto select layout based on IME if subtype or language tag is null
- if (subtype == null) {
+ if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) {
+ // Can't auto select layout based on IME info is null
return null;
}
+ InputMethodSubtype subtype = imeInfo.mImeSubtype;
// Check layout type, language tag information from IME for matching
ULocale pkLocale = subtype.getPhysicalKeyboardHintLanguageTag();
String pkLanguageTag =
@@ -1043,6 +1085,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
mNative.reloadKeyboardLayouts();
}
+ @MainThread
private void maybeUpdateNotification() {
if (mConfiguredKeyboards.size() == 0) {
hideKeyboardLayoutNotification();
@@ -1051,7 +1094,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
// If we have a keyboard with no selected layouts, we should always show missing
// layout notification even if there are other keyboards that are configured properly.
- if (mConfiguredKeyboards.valueAt(i).isEmpty()) {
+ if (!mConfiguredKeyboards.valueAt(i).hasConfiguredLayouts()) {
showMissingKeyboardLayoutNotification();
return;
}
@@ -1059,7 +1102,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
showConfiguredKeyboardLayoutNotification();
}
- // Must be called on handler.
+ @MainThread
private void showMissingKeyboardLayoutNotification() {
final Resources r = mContext.getResources();
final String missingKeyboardLayoutNotificationContent = r.getString(
@@ -1084,6 +1127,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
+ @MainThread
private void showKeyboardLayoutNotification(@NonNull String intentTitle,
@NonNull String intentContent, @Nullable InputDevice targetDevice) {
final NotificationManager notificationManager = mContext.getSystemService(
@@ -1119,7 +1163,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
notification, UserHandle.ALL);
}
- // Must be called on handler.
+ @MainThread
private void hideKeyboardLayoutNotification() {
NotificationManager notificationManager = mContext.getSystemService(
NotificationManager.class);
@@ -1132,6 +1176,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
UserHandle.ALL);
}
+ @MainThread
private void showConfiguredKeyboardLayoutNotification() {
final Resources r = mContext.getResources();
@@ -1144,8 +1189,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0));
- final Set<String> selectedLayouts = mConfiguredKeyboards.valueAt(0);
- if (inputDevice == null || selectedLayouts == null || selectedLayouts.isEmpty()) {
+ final KeyboardConfiguration config = mConfiguredKeyboards.valueAt(0);
+ if (inputDevice == null || !config.hasConfiguredLayouts()) {
return;
}
@@ -1153,10 +1198,11 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
r.getString(
R.string.keyboard_layout_notification_selected_title,
inputDevice.getName()),
- createConfiguredNotificationText(mContext, selectedLayouts),
+ createConfiguredNotificationText(mContext, config.getConfiguredLayouts()),
inputDevice);
}
+ @MainThread
private String createConfiguredNotificationText(@NonNull Context context,
@NonNull Set<String> selectedLayouts) {
final Resources r = context.getResources();
@@ -1199,6 +1245,9 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
case MSG_UPDATE_KEYBOARD_LAYOUTS:
updateKeyboardLayouts();
return true;
+ case MSG_CURRENT_IME_INFO_CHANGED:
+ onCurrentImeInfoChanged();
+ return true;
default:
return false;
}
@@ -1252,11 +1301,13 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
return imeInfoList;
}
- private String createLayoutKey(InputDeviceIdentifier identifier, int userId,
- @NonNull InputMethodSubtypeHandle subtypeHandle) {
- Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null");
- return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + userId
- + ",subtypeHandle:" + subtypeHandle.toStringHandle();
+ private String createLayoutKey(InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) {
+ if (imeInfo == null) {
+ return getLayoutDescriptor(identifier);
+ }
+ Objects.requireNonNull(imeInfo.mImeSubtypeHandle, "subtypeHandle must not be null");
+ return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + imeInfo.mUserId
+ + ",subtypeHandle:" + imeInfo.mImeSubtypeHandle.toStringHandle();
}
private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout,
@@ -1350,6 +1401,39 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
+ private static class KeyboardConfiguration {
+ // If null or empty, it means no layout is configured for the device. And user needs to
+ // manually set up the device.
+ @Nullable
+ private Set<String> mConfiguredLayouts;
+
+ // If null, it means no layout is selected for the device.
+ @Nullable
+ private String mCurrentLayout;
+
+ private boolean hasConfiguredLayouts() {
+ return mConfiguredLayouts != null && !mConfiguredLayouts.isEmpty();
+ }
+
+ @Nullable
+ private Set<String> getConfiguredLayouts() {
+ return mConfiguredLayouts;
+ }
+
+ private void setConfiguredLayouts(Set<String> configuredLayouts) {
+ mConfiguredLayouts = configuredLayouts;
+ }
+
+ @Nullable
+ private String getCurrentLayout() {
+ return mCurrentLayout;
+ }
+
+ private void setCurrentLayout(String currentLayout) {
+ mCurrentLayout = currentLayout;
+ }
+ }
+
private interface KeyboardLayoutVisitor {
void visitKeyboardLayout(Resources resources,
int keyboardLayoutResId, KeyboardLayout layout);