diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index b8f464d76b0df..5d969b196492c 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -49,11 +49,12 @@ interface IInputManager {
// Keyboard layouts configuration.
KeyboardLayout[] getKeyboardLayouts();
+ KeyboardLayout[] getKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
String getCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier);
void setCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor);
- String[] getKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
+ String[] getEnabledKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
void addKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor);
void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 16b872233f066..9972f49aab1b5 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -473,6 +473,29 @@ public final class InputManager {
}
}
+ /**
+ * Gets information about all supported keyboard layouts appropriate
+ * for a specific input device.
+ *
+ * The input manager consults the built-in keyboard layouts as well
+ * as all keyboard layouts advertised by applications using a
+ * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
+ *
+ *
+ * @return A list of all supported keyboard layouts for a specific
+ * input device.
+ *
+ * @hide
+ */
+ public KeyboardLayout[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
+ try {
+ return mIm.getKeyboardLayoutsForInputDevice(identifier);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not get list of keyboard layouts for input device.", ex);
+ return new KeyboardLayout[0];
+ }
+ }
+
/**
* Gets the keyboard layout with the specified descriptor.
*
@@ -551,13 +574,13 @@ public final class InputManager {
* @return The keyboard layout descriptors.
* @hide
*/
- public String[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
+ public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
if (identifier == null) {
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
try {
- return mIm.getKeyboardLayoutsForInputDevice(identifier);
+ return mIm.getEnabledKeyboardLayoutsForInputDevice(identifier);
} catch (RemoteException ex) {
Log.w(TAG, "Could not get keyboard layouts for input device.", ex);
return ArrayUtils.emptyArray(String.class);
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index ed51402bdac8e..584008c1db330 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -19,6 +19,8 @@ package android.hardware.input;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Locale;
+
/**
* Describes a keyboard layout.
*
@@ -30,6 +32,9 @@ public final class KeyboardLayout implements Parcelable,
private final String mLabel;
private final String mCollection;
private final int mPriority;
+ private final Locale[] mLocales;
+ private final int mVendorId;
+ private final int mProductId;
public static final Parcelable.Creator CREATOR =
new Parcelable.Creator() {
@@ -41,11 +46,19 @@ public final class KeyboardLayout implements Parcelable,
}
};
- public KeyboardLayout(String descriptor, String label, String collection, int priority) {
+ public KeyboardLayout(String descriptor, String label, String collection, int priority,
+ Locale[] locales, int vid, int pid) {
mDescriptor = descriptor;
mLabel = label;
mCollection = collection;
mPriority = priority;
+ if (locales != null) {
+ mLocales = locales;
+ } else {
+ mLocales = new Locale[0];
+ }
+ mVendorId = vid;
+ mProductId = pid;
}
private KeyboardLayout(Parcel source) {
@@ -53,6 +66,13 @@ public final class KeyboardLayout implements Parcelable,
mLabel = source.readString();
mCollection = source.readString();
mPriority = source.readInt();
+ int N = source.readInt();
+ mLocales = new Locale[N];
+ for (int i = 0; i < N; i++) {
+ mLocales[i] = Locale.forLanguageTag(source.readString());
+ }
+ mVendorId = source.readInt();
+ mProductId = source.readInt();
}
/**
@@ -83,6 +103,33 @@ public final class KeyboardLayout implements Parcelable,
return mCollection;
}
+ /**
+ * Gets the locales that this keyboard layout is intended for.
+ * This may be empty if a locale has not been assigned to this keyboard layout.
+ * @return The keyboard layout's intended locale.
+ */
+ public Locale[] getLocales() {
+ return mLocales;
+ }
+
+ /**
+ * Gets the vendor ID of the hardware device this keyboard layout is intended for.
+ * Returns -1 if this is not specific to any piece of hardware.
+ * @return The hardware vendor ID of the keyboard layout's intended device.
+ */
+ public int getVendorId() {
+ return mVendorId;
+ }
+
+ /**
+ * Gets the product ID of the hardware device this keyboard layout is intended for.
+ * Returns -1 if this is not specific to any piece of hardware.
+ * @return The hardware product ID of the keyboard layout's intended device.
+ */
+ public int getProductId() {
+ return mProductId;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -94,6 +141,16 @@ public final class KeyboardLayout implements Parcelable,
dest.writeString(mLabel);
dest.writeString(mCollection);
dest.writeInt(mPriority);
+ if (mLocales != null) {
+ dest.writeInt(mLocales.length);
+ for (Locale l : mLocales) {
+ dest.writeString(l.toLanguageTag());
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mVendorId);
+ dest.writeInt(mProductId);
}
@Override
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9f135651cb67a..260cab14bab22 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7836,6 +7836,12 @@ i
+
+
+
+
+
+
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index ee8aab6c6d3d3..8d0dac96c0830 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -70,7 +70,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
-import android.util.Log;
+import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
@@ -97,9 +97,11 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import libcore.io.Streams;
import libcore.util.Objects;
@@ -720,40 +722,35 @@ public class InputManagerService extends IInputManager.Stub
mTempInputDevicesChangedListenersToNotify.clear();
// Check for missing keyboard layouts.
- if (mNotificationManager != null) {
- final int numFullKeyboards = mTempFullKeyboards.size();
- boolean missingLayoutForExternalKeyboard = false;
- boolean missingLayoutForExternalKeyboardAdded = false;
- boolean multipleMissingLayoutsForExternalKeyboardsAdded = false;
- InputDevice keyboardMissingLayout = null;
- synchronized (mDataStore) {
- for (int i = 0; i < numFullKeyboards; i++) {
- final InputDevice inputDevice = mTempFullKeyboards.get(i);
- final String layout =
- getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
- if (layout == null) {
- missingLayoutForExternalKeyboard = true;
- if (i < numFullKeyboardsAdded) {
- missingLayoutForExternalKeyboardAdded = true;
- if (keyboardMissingLayout == null) {
- keyboardMissingLayout = inputDevice;
- } else {
- multipleMissingLayoutsForExternalKeyboardsAdded = true;
- }
- }
+ List keyboardsMissingLayout = new ArrayList<>();
+ final int numFullKeyboards = mTempFullKeyboards.size();
+ synchronized (mDataStore) {
+ for (int i = 0; i < numFullKeyboards; i++) {
+ final InputDevice inputDevice = mTempFullKeyboards.get(i);
+ String layout =
+ getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
+ if (layout == null) {
+ layout = getDefaultKeyboardLayout(inputDevice);
+ if (layout != null) {
+ setCurrentKeyboardLayoutForInputDevice(
+ inputDevice.getIdentifier(), layout);
}
}
+ if (layout == null) {
+ keyboardsMissingLayout.add(inputDevice);
+ }
}
- if (missingLayoutForExternalKeyboard) {
- if (missingLayoutForExternalKeyboardAdded) {
- if (multipleMissingLayoutsForExternalKeyboardsAdded) {
- // We have more than one keyboard missing a layout, so drop the
- // user at the generic input methods page so they can pick which
- // one to set.
- showMissingKeyboardLayoutNotification(null);
- } else {
- showMissingKeyboardLayoutNotification(keyboardMissingLayout);
- }
+ }
+
+ if (mNotificationManager != null) {
+ if (!keyboardsMissingLayout.isEmpty()) {
+ if (keyboardsMissingLayout.size() > 1) {
+ // We have more than one keyboard missing a layout, so drop the
+ // user at the generic input methods page so they can pick which
+ // one to set.
+ showMissingKeyboardLayoutNotification(null);
+ } else {
+ showMissingKeyboardLayoutNotification(keyboardsMissingLayout.get(0));
}
} else if (mKeyboardLayoutNotificationShown) {
hideMissingKeyboardLayoutNotification();
@@ -762,6 +759,78 @@ public class InputManagerService extends IInputManager.Stub
mTempFullKeyboards.clear();
}
+ private String getDefaultKeyboardLayout(final InputDevice d) {
+ final Locale systemLocale = mContext.getResources().getConfiguration().locale;
+ // If our locale doesn't have a language for some reason, then we don't really have a
+ // reasonable default.
+ if (TextUtils.isEmpty(systemLocale.getLanguage())) {
+ return null;
+ }
+ final List layouts = new ArrayList<>();
+ visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+ @Override
+ public void visitKeyboardLayout(Resources resources,
+ int keyboardLayoutResId, KeyboardLayout layout) {
+ // Only select a default when we know the layout is appropriate. For now, this
+ // means its a custom layout for a specific keyboard.
+ if (layout.getVendorId() != d.getVendorId()
+ || layout.getProductId() != d.getProductId()) {
+ return;
+ }
+ for (Locale l : layout.getLocales()) {
+ if (isCompatibleLocale(systemLocale, l)) {
+ layouts.add(layout);
+ break;
+ }
+ }
+ }
+ });
+
+ if (layouts.isEmpty()) {
+ return null;
+ }
+
+ // First sort so that ones with higher priority are listed at the top
+ Collections.sort(layouts);
+ // Next we want to try to find an exact match of language, country and variant.
+ final int N = layouts.size();
+ for (int i = 0; i < N; i++) {
+ KeyboardLayout layout = layouts.get(i);
+ for (Locale l : layout.getLocales()) {
+ if (l.getCountry().equals(systemLocale.getCountry())
+ && l.getVariant().equals(systemLocale.getVariant())) {
+ return layout.getDescriptor();
+ }
+ }
+ }
+ // Then try an exact match of language and country
+ for (int i = 0; i < N; i++) {
+ KeyboardLayout layout = layouts.get(i);
+ for (Locale l : layout.getLocales()) {
+ if (l.getCountry().equals(systemLocale.getCountry())) {
+ return layout.getDescriptor();
+ }
+ }
+ }
+
+ // Give up and just use the highest priority layout with matching language
+ return layouts.get(0).getDescriptor();
+ }
+
+ private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
+ // Different languages are never compatible
+ if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
+ return false;
+ }
+ // If both the system and the keyboard layout have a country specifier, they must be equal.
+ if (!TextUtils.isEmpty(systemLocale.getCountry())
+ && !TextUtils.isEmpty(keyboardLocale.getCountry())
+ && !systemLocale.getCountry().equals(keyboardLocale.getCountry())) {
+ return false;
+ }
+ return true;
+ }
+
@Override // Binder call & native callback
public TouchCalibration getTouchCalibrationForInputDevice(String inputDeviceDescriptor,
int surfaceRotation) {
@@ -911,9 +980,9 @@ public class InputManagerService extends IInputManager.Stub
final HashSet availableKeyboardLayouts = new HashSet();
visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
@Override
- public void visitKeyboardLayout(Resources resources, String descriptor, String label,
- String collection, int keyboardLayoutResId, int priority) {
- availableKeyboardLayouts.add(descriptor);
+ public void visitKeyboardLayout(Resources resources,
+ int keyboardLayoutResId, KeyboardLayout layout) {
+ availableKeyboardLayouts.add(layout.getDescriptor());
}
});
synchronized (mDataStore) {
@@ -945,9 +1014,35 @@ public class InputManagerService extends IInputManager.Stub
final ArrayList list = new ArrayList();
visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
@Override
- public void visitKeyboardLayout(Resources resources, String descriptor, String label,
- String collection, int keyboardLayoutResId, int priority) {
- list.add(new KeyboardLayout(descriptor, label, collection, priority));
+ public void visitKeyboardLayout(Resources resources,
+ int keyboardLayoutResId, KeyboardLayout layout) {
+ list.add(layout);
+ }
+ });
+ return list.toArray(new KeyboardLayout[list.size()]);
+ }
+
+ @Override
+ public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
+ final InputDeviceIdentifier identifier) {
+ final ArrayList list = new ArrayList();
+ visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+ boolean mHasSeenDeviceSpecificLayout;
+
+ @Override
+ public void visitKeyboardLayout(Resources resources,
+ int keyboardLayoutResId, KeyboardLayout layout) {
+ if (layout.getVendorId() == identifier.getVendorId()
+ && layout.getProductId() == identifier.getProductId()) {
+ if (!mHasSeenDeviceSpecificLayout) {
+ mHasSeenDeviceSpecificLayout = true;
+ list.clear();
+ }
+ list.add(layout);
+ } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
+ && !mHasSeenDeviceSpecificLayout) {
+ list.add(layout);
+ }
}
});
return list.toArray(new KeyboardLayout[list.size()]);
@@ -962,13 +1057,13 @@ public class InputManagerService extends IInputManager.Stub
final KeyboardLayout[] result = new KeyboardLayout[1];
visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() {
@Override
- public void visitKeyboardLayout(Resources resources, String descriptor,
- String label, String collection, int keyboardLayoutResId, int priority) {
- result[0] = new KeyboardLayout(descriptor, label, collection, priority);
+ public void visitKeyboardLayout(Resources resources,
+ int keyboardLayoutResId, KeyboardLayout layout) {
+ result[0] = layout;
}
});
if (result[0] == null) {
- Log.w(TAG, "Could not get keyboard layout with descriptor '"
+ Slog.w(TAG, "Could not get keyboard layout with descriptor '"
+ keyboardLayoutDescriptor + "'.");
}
return result[0];
@@ -1010,7 +1105,7 @@ public class InputManagerService extends IInputManager.Stub
int configResId = metaData.getInt(InputManager.META_DATA_KEYBOARD_LAYOUTS);
if (configResId == 0) {
- Log.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_LAYOUTS
+ Slog.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_LAYOUTS
+ "' on receiver " + receiver.packageName + "/" + receiver.name);
return;
}
@@ -1047,8 +1142,16 @@ public class InputManagerService extends IInputManager.Stub
int keyboardLayoutResId = a.getResourceId(
com.android.internal.R.styleable.KeyboardLayout_keyboardLayout,
0);
+ String languageTags = a.getString(
+ com.android.internal.R.styleable.KeyboardLayout_locale);
+ Locale[] locales = getLocalesFromLanguageTags(languageTags);
+ int vid = a.getInt(
+ com.android.internal.R.styleable.KeyboardLayout_vendorId, -1);
+ int pid = a.getInt(
+ com.android.internal.R.styleable.KeyboardLayout_productId, -1);
+
if (name == null || label == null || keyboardLayoutResId == 0) {
- Log.w(TAG, "Missing required 'name', 'label' or 'keyboardLayout' "
+ Slog.w(TAG, "Missing required 'name', 'label' or 'keyboardLayout' "
+ "attributes in keyboard layout "
+ "resource from receiver "
+ receiver.packageName + "/" + receiver.name);
@@ -1056,15 +1159,18 @@ public class InputManagerService extends IInputManager.Stub
String descriptor = KeyboardLayoutDescriptor.format(
receiver.packageName, receiver.name, name);
if (keyboardName == null || name.equals(keyboardName)) {
- visitor.visitKeyboardLayout(resources, descriptor,
- label, collection, keyboardLayoutResId, priority);
+ KeyboardLayout layout = new KeyboardLayout(
+ descriptor, label, collection, priority,
+ locales, vid, pid);
+ visitor.visitKeyboardLayout(
+ resources, keyboardLayoutResId, layout);
}
}
} finally {
a.recycle();
}
} else {
- Log.w(TAG, "Skipping unrecognized element '" + element
+ Slog.w(TAG, "Skipping unrecognized element '" + element
+ "' in keyboard layout resource from receiver "
+ receiver.packageName + "/" + receiver.name);
}
@@ -1073,11 +1179,23 @@ public class InputManagerService extends IInputManager.Stub
parser.close();
}
} catch (Exception ex) {
- Log.w(TAG, "Could not parse keyboard layout resource from receiver "
+ Slog.w(TAG, "Could not parse keyboard layout resource from receiver "
+ receiver.packageName + "/" + receiver.name, ex);
}
}
+ private static Locale[] getLocalesFromLanguageTags(String languageTags) {
+ if (TextUtils.isEmpty(languageTags)) {
+ return new Locale[0];
+ }
+ String[] tags = languageTags.split("\\|");
+ Locale[] locales = new Locale[tags.length];
+ for (int i = 0; i < tags.length; i++) {
+ locales[i] = Locale.forLanguageTag(tags[i]);
+ }
+ return locales;
+ }
+
/**
* Builds a layout descriptor for the vendor/product. This returns the
* descriptor for ids that aren't useful (such as the default 0, 0).
@@ -1143,7 +1261,7 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call
- public String[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
+ public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
String key = getLayoutDescriptor(identifier);
synchronized (mDataStore) {
String[] layouts = mDataStore.getKeyboardLayouts(key);
@@ -1718,10 +1836,10 @@ public class InputManagerService extends IInputManager.Stub
final String[] result = new String[2];
visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() {
@Override
- public void visitKeyboardLayout(Resources resources, String descriptor, String label,
- String collection, int keyboardLayoutResId, int priority) {
+ public void visitKeyboardLayout(Resources resources,
+ int keyboardLayoutResId, KeyboardLayout layout) {
try {
- result[0] = descriptor;
+ result[0] = layout.getDescriptor();
result[1] = Streams.readFully(new InputStreamReader(
resources.openRawResource(keyboardLayoutResId)));
} catch (IOException ex) {
@@ -1730,7 +1848,7 @@ public class InputManagerService extends IInputManager.Stub
}
});
if (result[0] == null) {
- Log.w(TAG, "Could not get keyboard layout with descriptor '"
+ Slog.w(TAG, "Could not get keyboard layout with descriptor '"
+ keyboardLayoutDescriptor + "'.");
return null;
}
@@ -1883,8 +2001,8 @@ public class InputManagerService extends IInputManager.Stub
}
private interface KeyboardLayoutVisitor {
- void visitKeyboardLayout(Resources resources, String descriptor, String label,
- String collection, int keyboardLayoutResId, int priority);
+ void visitKeyboardLayout(Resources resources,
+ int keyboardLayoutResId, KeyboardLayout layout);
}
private final class InputDevicesChangedListenerRecord implements DeathRecipient {