summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl9
-rwxr-xr-xcore/java/android/hardware/input/InputManager.java140
-rw-r--r--core/java/android/view/WindowManagerPolicy.java6
-rwxr-xr-xcore/res/res/drawable-hdpi/ic_settings_language.pngbin0 -> 986 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_settings_language.pngbin0 -> 756 bytes
-rw-r--r--core/res/res/drawable-xhdpi/ic_settings_language.pngbin0 -> 1267 bytes
-rw-r--r--core/res/res/values/public.xml3
-rwxr-xr-xcore/res/res/values/strings.xml5
-rw-r--r--data/keyboards/Generic.kcm1
-rw-r--r--data/keyboards/Virtual.kcm1
-rwxr-xr-xpolicy/src/com/android/internal/policy/impl/PhoneWindowManager.java17
-rw-r--r--services/java/com/android/server/input/InputManagerService.java496
-rw-r--r--services/java/com/android/server/input/PersistentDataStore.java416
-rwxr-xr-xservices/java/com/android/server/wm/WindowManagerService.java6
14 files changed, 869 insertions, 231 deletions
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 3137947..9b6f82a 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -41,8 +41,13 @@ interface IInputManager {
// Keyboard layouts configuration.
KeyboardLayout[] getKeyboardLayouts();
KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
- String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
- void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
+ void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor);
+ String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor);
+ void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor);
+ void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
String keyboardLayoutDescriptor);
// Registers an input devices changed listener.
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index dfd35e1..262d87d 100755
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,6 +16,8 @@
package android.hardware.input;
+import com.android.internal.util.ArrayUtils;
+
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
@@ -217,6 +219,41 @@ public final class InputManager {
}
/**
+ * Gets information about the input device with the specified descriptor.
+ * @param descriptor The input device descriptor.
+ * @return The input device or null if not found.
+ * @hide
+ */
+ public InputDevice getInputDeviceByDescriptor(String descriptor) {
+ if (descriptor == null) {
+ throw new IllegalArgumentException("descriptor must not be null.");
+ }
+
+ synchronized (mInputDevicesLock) {
+ populateInputDevicesLocked();
+
+ int numDevices = mInputDevices.size();
+ for (int i = 0; i < numDevices; i++) {
+ InputDevice inputDevice = mInputDevices.valueAt(i);
+ if (inputDevice == null) {
+ int id = mInputDevices.keyAt(i);
+ try {
+ inputDevice = mIm.getInputDevice(id);
+ } catch (RemoteException ex) {
+ // Ignore the problem for the purposes of this method.
+ continue;
+ }
+ mInputDevices.setValueAt(i, inputDevice);
+ }
+ if (descriptor.equals(inputDevice.getDescriptor())) {
+ return inputDevice;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
* Gets the ids of all input devices in the system.
* @return The input device ids.
*/
@@ -332,50 +369,129 @@ public final class InputManager {
}
/**
- * Gets the keyboard layout descriptor for the specified input device.
+ * Gets the current keyboard layout descriptor for the specified input device.
*
* @param inputDeviceDescriptor The input device descriptor.
- * @return The keyboard layout descriptor, or null if unknown or if the default
- * keyboard layout will be used.
+ * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
*
* @hide
*/
- public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
+ public String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
if (inputDeviceDescriptor == null) {
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
try {
- return mIm.getKeyboardLayoutForInputDevice(inputDeviceDescriptor);
+ return mIm.getCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor);
} catch (RemoteException ex) {
- Log.w(TAG, "Could not get keyboard layout for input device.", ex);
+ Log.w(TAG, "Could not get current keyboard layout for input device.", ex);
return null;
}
}
/**
- * Sets the keyboard layout descriptor for the specified input device.
+ * Sets the current keyboard layout descriptor for the specified input device.
+ * <p>
+ * This method may have the side-effect of causing the input device in question
+ * to be reconfigured.
+ * </p>
+ *
+ * @param inputDeviceDescriptor The input device descriptor.
+ * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
+ *
+ * @hide
+ */
+ public void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
+
+ try {
+ mIm.setCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor,
+ keyboardLayoutDescriptor);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not set current keyboard layout for input device.", ex);
+ }
+ }
+
+ /**
+ * Gets all keyboard layout descriptors that are enabled for the specified input device.
+ *
+ * @param inputDeviceDescriptor The input device descriptor.
+ * @return The keyboard layout descriptors.
+ *
+ * @hide
+ */
+ public String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor) {
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+
+ try {
+ return mIm.getKeyboardLayoutsForInputDevice(inputDeviceDescriptor);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not get keyboard layouts for input device.", ex);
+ return ArrayUtils.emptyArray(String.class);
+ }
+ }
+
+ /**
+ * Adds the keyboard layout descriptor for the specified input device.
* <p>
* This method may have the side-effect of causing the input device in question
* to be reconfigured.
* </p>
*
* @param inputDeviceDescriptor The input device descriptor.
- * @param keyboardLayoutDescriptor The keyboard layout descriptor, or null to remove
- * the mapping so that the default keyboard layout will be used for the input device.
+ * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add.
*
* @hide
*/
- public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ public void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
String keyboardLayoutDescriptor) {
if (inputDeviceDescriptor == null) {
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
+
+ try {
+ mIm.addKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not add keyboard layout for input device.", ex);
+ }
+ }
+
+ /**
+ * Removes the keyboard layout descriptor for the specified input device.
+ * <p>
+ * This method may have the side-effect of causing the input device in question
+ * to be reconfigured.
+ * </p>
+ *
+ * @param inputDeviceDescriptor The input device descriptor.
+ * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove.
+ *
+ * @hide
+ */
+ public void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
try {
- mIm.setKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
+ mIm.removeKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
} catch (RemoteException ex) {
- Log.w(TAG, "Could not set keyboard layout for input device.", ex);
+ Log.w(TAG, "Could not remove keyboard layout for input device.", ex);
}
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 0c5d6ea..ceb9fe6 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -386,6 +386,12 @@ public interface WindowManagerPolicy {
*/
public InputChannel monitorInput(String name);
+ /**
+ * Switch the keyboard layout for the given device.
+ * Direction should be +1 or -1 to go to the next or previous keyboard layout.
+ */
+ public void switchKeyboardLayout(int deviceId, int direction);
+
public void shutdown();
public void rebootSafeMode();
}
diff --git a/core/res/res/drawable-hdpi/ic_settings_language.png b/core/res/res/drawable-hdpi/ic_settings_language.png
new file mode 100755
index 0000000..f635b2e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_settings_language.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_settings_language.png b/core/res/res/drawable-mdpi/ic_settings_language.png
new file mode 100644
index 0000000..f8aca67
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_settings_language.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_settings_language.png b/core/res/res/drawable-xhdpi/ic_settings_language.png
new file mode 100644
index 0000000..2c42db3
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_settings_language.png
Binary files differ
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index a24e345..83d3141 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1489,6 +1489,8 @@
<java-symbol type="string" name="low_internal_storage_view_title" />
<java-symbol type="string" name="report" />
<java-symbol type="string" name="select_input_method" />
+ <java-symbol type="string" name="select_keyboard_layout_notification_title" />
+ <java-symbol type="string" name="select_keyboard_layout_notification_message" />
<java-symbol type="string" name="smv_application" />
<java-symbol type="string" name="smv_process" />
<java-symbol type="string" name="tethered_notification_message" />
@@ -1580,6 +1582,7 @@
<java-symbol type="drawable" name="expander_ic_minimized" />
<java-symbol type="drawable" name="ic_menu_archive" />
<java-symbol type="drawable" name="ic_menu_goto" />
+ <java-symbol type="drawable" name="ic_settings_language" />
<java-symbol type="drawable" name="title_bar_medium" />
<java-symbol type="id" name="body" />
<java-symbol type="string" name="fast_scroll_alphabet" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 38ce854..d50e2de 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3092,6 +3092,11 @@
<!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=10] -->
<string name="hardware">Hardware</string>
+ <!-- Title of the notification to prompt the user to select a keyboard layout. -->
+ <string name="select_keyboard_layout_notification_title">Select keyboard layout</string>
+ <!-- Message of the notification to prompt the user to select a keyboard layout. -->
+ <string name="select_keyboard_layout_notification_message">Touch to select a keyboard layout.</string>
+
<string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
<string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index 544076f..782a701 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -252,6 +252,7 @@ key SPACE {
label: ' '
base: ' '
alt, meta: fallback SEARCH
+ ctrl: fallback LANGUAGE_SWITCH
}
key ENTER {
diff --git a/data/keyboards/Virtual.kcm b/data/keyboards/Virtual.kcm
index e592013..d90b790 100644
--- a/data/keyboards/Virtual.kcm
+++ b/data/keyboards/Virtual.kcm
@@ -249,6 +249,7 @@ key SPACE {
label: ' '
base: ' '
alt, meta: fallback SEARCH
+ ctrl: fallback LANGUAGE_SWITCH
}
key ENTER {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index cee01ac..29de5c1 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -326,6 +326,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
RecentApplicationsDialog mRecentAppsDialog;
int mRecentAppsDialogHeldModifiers;
+ boolean mLanguageSwitchKeyPressed;
int mLidState = LID_ABSENT;
boolean mHaveBuiltInKeyboard;
@@ -1943,6 +1944,22 @@ public class PhoneWindowManager implements WindowManagerPolicy {
RECENT_APPS_BEHAVIOR_DISMISS_AND_SWITCH);
}
+ // Handle keyboard language switching.
+ if (down && repeatCount == 0
+ && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
+ || (keyCode == KeyEvent.KEYCODE_SPACE
+ && (metaState & KeyEvent.META_CTRL_MASK) != 0))) {
+ int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
+ mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
+ return -1;
+ }
+ if (mLanguageSwitchKeyPressed && !down
+ && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
+ || keyCode == KeyEvent.KEYCODE_SPACE)) {
+ mLanguageSwitchKeyPressed = false;
+ return -1;
+ }
+
// Let the application handle the key.
return 0;
}
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index 4f1f76f..bc946b5 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -16,8 +16,7 @@
package com.android.server.input;
-import com.android.internal.os.AtomicFile;
-import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.R;
import com.android.internal.util.XmlUtils;
import com.android.server.Watchdog;
@@ -26,6 +25,9 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
@@ -70,23 +72,20 @@ import android.view.PointerIcon;
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
+import android.widget.Toast;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
-import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.Map;
+import java.util.HashSet;
-import libcore.io.IoUtils;
import libcore.io.Streams;
import libcore.util.Objects;
@@ -100,6 +99,10 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
+ 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_RELOAD_DEVICE_ALIASES = 5;
// Pointer to native input manager service object.
private final int mPtr;
@@ -109,6 +112,7 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
private final InputManagerHandler mHandler;
private boolean mSystemReady;
private BluetoothService mBluetoothService;
+ private NotificationManager mNotificationManager;
// Persistent data store. Must be locked each time during use.
private final PersistentDataStore mDataStore = new PersistentDataStore();
@@ -122,6 +126,11 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
private final ArrayList<InputDevicesChangedListenerRecord>
mTempInputDevicesChangedListenersToNotify =
new ArrayList<InputDevicesChangedListenerRecord>(); // handler thread only
+ private final ArrayList<InputDevice>
+ mTempFullKeyboards = new ArrayList<InputDevice>(); // handler thread only
+ private boolean mKeyboardLayoutNotificationShown;
+ private PendingIntent mKeyboardLayoutIntent;
+ private Toast mSwitchedKeyboardLayoutToast;
// State for vibrator tokens.
private Object mVibratorLock = new Object();
@@ -235,6 +244,8 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
Slog.d(TAG, "System ready.");
}
mBluetoothService = bluetoothService;
+ mNotificationManager = (NotificationManager)mContext.getSystemService(
+ Context.NOTIFICATION_SERVICE);
mSystemReady = true;
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
@@ -244,10 +255,7 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Slog.d(TAG, "Packages changed, reloading keyboard layouts.");
- }
- reloadKeyboardLayouts();
+ updateKeyboardLayouts();
}
}, filter, null, mHandler);
@@ -255,22 +263,25 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Slog.d(TAG, "Bluetooth alias changed, reloading device names.");
- }
reloadDeviceAliases();
}
}, filter, null, mHandler);
- reloadKeyboardLayouts();
- reloadDeviceAliases();
+ mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);
+ mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);
}
private void reloadKeyboardLayouts() {
+ if (DEBUG) {
+ Slog.d(TAG, "Reloading keyboard layouts.");
+ }
nativeReloadKeyboardLayouts(mPtr);
}
private void reloadDeviceAliases() {
+ if (DEBUG) {
+ Slog.d(TAG, "Reloading device names.");
+ }
nativeReloadDeviceAliases(mPtr);
}
@@ -558,9 +569,11 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
}
// Must be called on handler.
- private void deliverInputDevicesChanged() {
+ private void deliverInputDevicesChanged(InputDevice[] oldInputDevices) {
+ // Scan for changes.
+ int numFullKeyboardsAdded = 0;
mTempInputDevicesChangedListenersToNotify.clear();
-
+ mTempFullKeyboards.clear();
final int numListeners;
final int[] deviceIdAndGeneration;
synchronized (mInputDevicesLock) {
@@ -581,13 +594,126 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
final InputDevice inputDevice = mInputDevices[i];
deviceIdAndGeneration[i * 2] = inputDevice.getId();
deviceIdAndGeneration[i * 2 + 1] = inputDevice.getGeneration();
+
+ if (isFullKeyboard(inputDevice)) {
+ if (!containsInputDeviceWithDescriptor(oldInputDevices,
+ inputDevice.getDescriptor())) {
+ mTempFullKeyboards.add(numFullKeyboardsAdded++, inputDevice);
+ } else {
+ mTempFullKeyboards.add(inputDevice);
+ }
+ }
}
}
+ // Notify listeners.
for (int i = 0; i < numListeners; i++) {
mTempInputDevicesChangedListenersToNotify.get(i).notifyInputDevicesChanged(
deviceIdAndGeneration);
}
+ mTempInputDevicesChangedListenersToNotify.clear();
+
+ // Check for missing keyboard layouts.
+ if (mNotificationManager != null) {
+ final int numFullKeyboards = mTempFullKeyboards.size();
+ boolean missingLayoutForExternalKeyboard = false;
+ boolean missingLayoutForExternalKeyboardAdded = false;
+ synchronized (mDataStore) {
+ for (int i = 0; i < numFullKeyboards; i++) {
+ final InputDevice inputDevice = mTempFullKeyboards.get(i);
+ if (mDataStore.getCurrentKeyboardLayout(inputDevice.getDescriptor()) == null) {
+ missingLayoutForExternalKeyboard = true;
+ if (i < numFullKeyboardsAdded) {
+ missingLayoutForExternalKeyboardAdded = true;
+ }
+ }
+ }
+ }
+ if (missingLayoutForExternalKeyboard) {
+ if (missingLayoutForExternalKeyboardAdded) {
+ showMissingKeyboardLayoutNotification();
+ }
+ } else if (mKeyboardLayoutNotificationShown) {
+ hideMissingKeyboardLayoutNotification();
+ }
+ }
+ mTempFullKeyboards.clear();
+ }
+
+ // Must be called on handler.
+ private void showMissingKeyboardLayoutNotification() {
+ if (!mKeyboardLayoutNotificationShown) {
+ if (mKeyboardLayoutIntent == null) {
+ final Intent intent = new Intent("android.settings.INPUT_METHOD_SETTINGS");
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mKeyboardLayoutIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ }
+
+ Resources r = mContext.getResources();
+ Notification notification = new Notification.Builder(mContext)
+ .setContentTitle(r.getString(
+ R.string.select_keyboard_layout_notification_title))
+ .setContentText(r.getString(
+ R.string.select_keyboard_layout_notification_message))
+ .setContentIntent(mKeyboardLayoutIntent)
+ .setSmallIcon(R.drawable.ic_settings_language)
+ .setPriority(Notification.PRIORITY_LOW)
+ .build();
+ mNotificationManager.notify(R.string.select_keyboard_layout_notification_title,
+ notification);
+ mKeyboardLayoutNotificationShown = true;
+ }
+ }
+
+ // Must be called on handler.
+ private void hideMissingKeyboardLayoutNotification() {
+ if (mKeyboardLayoutNotificationShown) {
+ mKeyboardLayoutNotificationShown = false;
+ mNotificationManager.cancel(R.string.select_keyboard_layout_notification_title);
+ }
+ }
+
+ // Must be called on handler.
+ private void updateKeyboardLayouts() {
+ // Scan all input devices state for keyboard layouts that have been uninstalled.
+ final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
+ visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+ @Override
+ public void visitKeyboardLayout(Resources resources,
+ String descriptor, String label, String collection, int keyboardLayoutResId) {
+ availableKeyboardLayouts.add(descriptor);
+ }
+ });
+ synchronized (mDataStore) {
+ try {
+ mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+
+ // Reload keyboard layouts.
+ reloadKeyboardLayouts();
+ }
+
+ private static boolean isFullKeyboard(InputDevice inputDevice) {
+ return !inputDevice.isVirtual()
+ && (inputDevice.getSources() & InputDevice.SOURCE_KEYBOARD) != 0
+ && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC;
+ }
+
+ private static boolean containsInputDeviceWithDescriptor(InputDevice[] inputDevices,
+ String descriptor) {
+ final int numDevices = inputDevices.length;
+ for (int i = 0; i < numDevices; i++) {
+ final InputDevice inputDevice = inputDevices[i];
+ if (inputDevice.getDescriptor().equals(descriptor)) {
+ return true;
+ }
+ }
+ return false;
}
@Override // Binder call
@@ -720,43 +846,147 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
}
@Override // Binder call
- public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
+ public String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
if (inputDeviceDescriptor == null) {
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
synchronized (mDataStore) {
- return mDataStore.getKeyboardLayout(inputDeviceDescriptor);
+ return mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
}
}
@Override // Binder call
- public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ public void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
String keyboardLayoutDescriptor) {
if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
- "setKeyboardLayoutForInputDevice()")) {
+ "setCurrentKeyboardLayoutForInputDevice()")) {
throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
}
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
+
+ synchronized (mDataStore) {
+ try {
+ if (mDataStore.setCurrentKeyboardLayout(
+ inputDeviceDescriptor, keyboardLayoutDescriptor)) {
+ mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+ }
+ @Override // Binder call
+ public String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor) {
if (inputDeviceDescriptor == null) {
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
- final boolean changed;
+ synchronized (mDataStore) {
+ return mDataStore.getKeyboardLayouts(inputDeviceDescriptor);
+ }
+ }
+
+ @Override // Binder call
+ public void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+ "addKeyboardLayoutForInputDevice()")) {
+ throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+ }
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
+
synchronized (mDataStore) {
try {
- changed = mDataStore.setKeyboardLayout(
- inputDeviceDescriptor, keyboardLayoutDescriptor);
+ String oldLayout = mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
+ if (mDataStore.addKeyboardLayout(inputDeviceDescriptor, keyboardLayoutDescriptor)
+ && !Objects.equal(oldLayout,
+ mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor))) {
+ mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+ }
} finally {
mDataStore.saveIfNeeded();
}
}
+ }
- if (changed) {
- if (DEBUG) {
- Slog.d(TAG, "Keyboard layout changed, reloading keyboard layouts.");
+ @Override // Binder call
+ public void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+ "removeKeyboardLayoutForInputDevice()")) {
+ throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+ }
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
+
+ synchronized (mDataStore) {
+ try {
+ String oldLayout = mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
+ if (mDataStore.removeKeyboardLayout(inputDeviceDescriptor,
+ keyboardLayoutDescriptor)
+ && !Objects.equal(oldLayout,
+ mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor))) {
+ mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+ }
+
+ public void switchKeyboardLayout(int deviceId, int direction) {
+ mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
+ }
+
+ // Must be called on handler.
+ private void handleSwitchKeyboardLayout(int deviceId, int direction) {
+ final InputDevice device = getInputDevice(deviceId);
+ final String inputDeviceDescriptor = device.getDescriptor();
+ if (device != null) {
+ final boolean changed;
+ final String keyboardLayoutDescriptor;
+ synchronized (mDataStore) {
+ try {
+ changed = mDataStore.switchKeyboardLayout(inputDeviceDescriptor, direction);
+ keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
+ inputDeviceDescriptor);
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+
+ if (changed) {
+ if (mSwitchedKeyboardLayoutToast != null) {
+ mSwitchedKeyboardLayoutToast.cancel();
+ mSwitchedKeyboardLayoutToast = null;
+ }
+ if (keyboardLayoutDescriptor != null) {
+ KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
+ if (keyboardLayout != null) {
+ mSwitchedKeyboardLayoutToast = Toast.makeText(
+ mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
+ mSwitchedKeyboardLayoutToast.show();
+ }
+ }
+
+ reloadKeyboardLayouts();
}
- reloadKeyboardLayouts();
}
}
@@ -978,12 +1208,13 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
// Native callback.
private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
synchronized (mInputDevicesLock) {
- mInputDevices = inputDevices;
-
if (!mInputDevicesChangedPending) {
mInputDevicesChangedPending = true;
- mHandler.sendEmptyMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED);
+ mHandler.obtainMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED,
+ mInputDevices).sendToTarget();
}
+
+ mInputDevices = inputDevices;
}
}
@@ -1132,7 +1363,8 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
return null;
}
- String keyboardLayoutDescriptor = getKeyboardLayoutForInputDevice(inputDeviceDescriptor);
+ String keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(
+ inputDeviceDescriptor);
if (keyboardLayoutDescriptor == null) {
return null;
}
@@ -1203,7 +1435,19 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DELIVER_INPUT_DEVICES_CHANGED:
- deliverInputDevicesChanged();
+ deliverInputDevicesChanged((InputDevice[])msg.obj);
+ break;
+ case MSG_SWITCH_KEYBOARD_LAYOUT:
+ handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+ break;
+ case MSG_RELOAD_KEYBOARD_LAYOUTS:
+ reloadKeyboardLayouts();
+ break;
+ case MSG_UPDATE_KEYBOARD_LAYOUTS:
+ updateKeyboardLayouts();
+ break;
+ case MSG_RELOAD_DEVICE_ALIASES:
+ reloadDeviceAliases();
break;
}
}
@@ -1316,186 +1560,4 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
onVibratorTokenDied(this);
}
}
-
- /**
- * Manages persistent state recorded by the input manager service as an XML file.
- * Caller must acquire lock on the data store before accessing it.
- *
- * File format:
- * <code>
- * &lt;input-mananger-state>
- * &lt;input-devices>
- * &lt;input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
- * &gt;input-devices>
- * &gt;/input-manager-state>
- * </code>
- */
- private static final class PersistentDataStore {
- // Input device state by descriptor.
- private final HashMap<String, InputDeviceState> mInputDevices =
- new HashMap<String, InputDeviceState>();
- private final AtomicFile mAtomicFile;
-
- // True if the data has been loaded.
- private boolean mLoaded;
-
- // True if there are changes to be saved.
- private boolean mDirty;
-
- public PersistentDataStore() {
- mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"));
- }
-
- public void saveIfNeeded() {
- if (mDirty) {
- save();
- mDirty = false;
- }
- }
-
- public String getKeyboardLayout(String inputDeviceDescriptor) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
- return state != null ? state.keyboardLayoutDescriptor : null;
- }
-
- public boolean setKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
- if (!Objects.equal(state.keyboardLayoutDescriptor, keyboardLayoutDescriptor)) {
- state.keyboardLayoutDescriptor = keyboardLayoutDescriptor;
- setDirty();
- return true;
- }
- return false;
- }
-
- private InputDeviceState getInputDeviceState(String inputDeviceDescriptor,
- boolean createIfAbsent) {
- loadIfNeeded();
- InputDeviceState state = mInputDevices.get(inputDeviceDescriptor);
- if (state == null && createIfAbsent) {
- state = new InputDeviceState();
- mInputDevices.put(inputDeviceDescriptor, state);
- setDirty();
- }
- return state;
- }
-
- private void loadIfNeeded() {
- if (!mLoaded) {
- load();
- mLoaded = true;
- }
- }
-
- private void setDirty() {
- mDirty = true;
- }
-
- private void clearState() {
- mInputDevices.clear();
- }
-
- private void load() {
- clearState();
-
- final InputStream is;
- try {
- is = mAtomicFile.openRead();
- } catch (FileNotFoundException ex) {
- return;
- }
-
- XmlPullParser parser;
- try {
- parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(is), null);
- loadFromXml(parser);
- } catch (IOException ex) {
- Slog.w(TAG, "Failed to load input manager persistent store data.", ex);
- clearState();
- } catch (XmlPullParserException ex) {
- Slog.w(TAG, "Failed to load input manager persistent store data.", ex);
- clearState();
- } finally {
- IoUtils.closeQuietly(is);
- }
- }
-
- private void save() {
- final FileOutputStream os;
- try {
- os = mAtomicFile.startWrite();
- boolean success = false;
- try {
- XmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(new BufferedOutputStream(os), "utf-8");
- saveToXml(serializer);
- serializer.flush();
- success = true;
- } finally {
- if (success) {
- mAtomicFile.finishWrite(os);
- } else {
- mAtomicFile.failWrite(os);
- }
- }
- } catch (IOException ex) {
- Slog.w(TAG, "Failed to save input manager persistent store data.", ex);
- }
- }
-
- private void loadFromXml(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- XmlUtils.beginDocument(parser, "input-manager-state");
- final int outerDepth = parser.getDepth();
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals("input-devices")) {
- loadInputDevicesFromXml(parser);
- }
- }
- }
-
- private void loadInputDevicesFromXml(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- final int outerDepth = parser.getDepth();
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals("input-device")) {
- String descriptor = parser.getAttributeValue(null, "descriptor");
- if (descriptor == null) {
- throw new XmlPullParserException(
- "Missing descriptor attribute on input-device");
- }
- InputDeviceState state = new InputDeviceState();
- state.keyboardLayoutDescriptor =
- parser.getAttributeValue(null, "keyboard-layout");
- mInputDevices.put(descriptor, state);
- }
- }
- }
-
- private void saveToXml(XmlSerializer serializer) throws IOException {
- serializer.startDocument(null, true);
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- serializer.startTag(null, "input-manager-state");
- serializer.startTag(null, "input-devices");
- for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
- final String descriptor = entry.getKey();
- final InputDeviceState state = entry.getValue();
- serializer.startTag(null, "input-device");
- serializer.attribute(null, "descriptor", descriptor);
- if (state.keyboardLayoutDescriptor != null) {
- serializer.attribute(null, "keyboard-layout", state.keyboardLayoutDescriptor);
- }
- serializer.endTag(null, "input-device");
- }
- serializer.endTag(null, "input-devices");
- serializer.endTag(null, "input-manager-state");
- serializer.endDocument();
- }
- }
-
- private static final class InputDeviceState {
- public String keyboardLayoutDescriptor;
- }
}
diff --git a/services/java/com/android/server/input/PersistentDataStore.java b/services/java/com/android/server/input/PersistentDataStore.java
new file mode 100644
index 0000000..fbe3e8b
--- /dev/null
+++ b/services/java/com/android/server/input/PersistentDataStore.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Slog;
+import android.util.Xml;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import libcore.io.IoUtils;
+import libcore.util.Objects;
+
+/**
+ * Manages persistent state recorded by the input manager service as an XML file.
+ * Caller must acquire lock on the data store before accessing it.
+ *
+ * File format:
+ * <code>
+ * &lt;input-mananger-state>
+ * &lt;input-devices>
+ * &lt;input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
+ * &gt;input-devices>
+ * &gt;/input-manager-state>
+ * </code>
+ */
+final class PersistentDataStore {
+ static final String TAG = "InputManager";
+
+ // Input device state by descriptor.
+ private final HashMap<String, InputDeviceState> mInputDevices =
+ new HashMap<String, InputDeviceState>();
+ private final AtomicFile mAtomicFile;
+
+ // True if the data has been loaded.
+ private boolean mLoaded;
+
+ // True if there are changes to be saved.
+ private boolean mDirty;
+
+ public PersistentDataStore() {
+ mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"));
+ }
+
+ public void saveIfNeeded() {
+ if (mDirty) {
+ save();
+ mDirty = false;
+ }
+ }
+
+ public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+ return state != null ? state.getCurrentKeyboardLayout() : null;
+ }
+
+ public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+ if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+ if (state == null) {
+ return (String[])ArrayUtils.emptyArray(String.class);
+ }
+ return state.getKeyboardLayouts();
+ }
+
+ public boolean addKeyboardLayout(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+ if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean removeKeyboardLayout(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+ if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+ if (state != null && state.switchKeyboardLayout(direction)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
+ boolean changed = false;
+ for (InputDeviceState state : mInputDevices.values()) {
+ if (state.removeUninstalledKeyboardLayouts(availableKeyboardLayouts)) {
+ changed = true;
+ }
+ }
+ if (changed) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ private InputDeviceState getInputDeviceState(String inputDeviceDescriptor,
+ boolean createIfAbsent) {
+ loadIfNeeded();
+ InputDeviceState state = mInputDevices.get(inputDeviceDescriptor);
+ if (state == null && createIfAbsent) {
+ state = new InputDeviceState();
+ mInputDevices.put(inputDeviceDescriptor, state);
+ setDirty();
+ }
+ return state;
+ }
+
+ private void loadIfNeeded() {
+ if (!mLoaded) {
+ load();
+ mLoaded = true;
+ }
+ }
+
+ private void setDirty() {
+ mDirty = true;
+ }
+
+ private void clearState() {
+ mInputDevices.clear();
+ }
+
+ private void load() {
+ clearState();
+
+ final InputStream is;
+ try {
+ is = mAtomicFile.openRead();
+ } catch (FileNotFoundException ex) {
+ return;
+ }
+
+ XmlPullParser parser;
+ try {
+ parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(is), null);
+ loadFromXml(parser);
+ } catch (IOException ex) {
+ Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
+ clearState();
+ } catch (XmlPullParserException ex) {
+ Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
+ clearState();
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+
+ private void save() {
+ final FileOutputStream os;
+ try {
+ os = mAtomicFile.startWrite();
+ boolean success = false;
+ try {
+ XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(new BufferedOutputStream(os), "utf-8");
+ saveToXml(serializer);
+ serializer.flush();
+ success = true;
+ } finally {
+ if (success) {
+ mAtomicFile.finishWrite(os);
+ } else {
+ mAtomicFile.failWrite(os);
+ }
+ }
+ } catch (IOException ex) {
+ Slog.w(InputManagerService.TAG, "Failed to save input manager persistent store data.", ex);
+ }
+ }
+
+ private void loadFromXml(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ XmlUtils.beginDocument(parser, "input-manager-state");
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (parser.getName().equals("input-devices")) {
+ loadInputDevicesFromXml(parser);
+ }
+ }
+ }
+
+ private void loadInputDevicesFromXml(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (parser.getName().equals("input-device")) {
+ String descriptor = parser.getAttributeValue(null, "descriptor");
+ if (descriptor == null) {
+ throw new XmlPullParserException(
+ "Missing descriptor attribute on input-device.");
+ }
+ if (mInputDevices.containsKey(descriptor)) {
+ throw new XmlPullParserException("Found duplicate input device.");
+ }
+
+ InputDeviceState state = new InputDeviceState();
+ state.loadFromXml(parser);
+ mInputDevices.put(descriptor, state);
+ }
+ }
+ }
+
+ private void saveToXml(XmlSerializer serializer) throws IOException {
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.startTag(null, "input-manager-state");
+ serializer.startTag(null, "input-devices");
+ for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
+ final String descriptor = entry.getKey();
+ final InputDeviceState state = entry.getValue();
+ serializer.startTag(null, "input-device");
+ serializer.attribute(null, "descriptor", descriptor);
+ state.saveToXml(serializer);
+ serializer.endTag(null, "input-device");
+ }
+ serializer.endTag(null, "input-devices");
+ serializer.endTag(null, "input-manager-state");
+ serializer.endDocument();
+ }
+
+ private static final class InputDeviceState {
+ private String mCurrentKeyboardLayout;
+ private ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
+
+ public String getCurrentKeyboardLayout() {
+ return mCurrentKeyboardLayout;
+ }
+
+ public boolean setCurrentKeyboardLayout(String keyboardLayout) {
+ if (Objects.equal(mCurrentKeyboardLayout, keyboardLayout)) {
+ return false;
+ }
+ addKeyboardLayout(keyboardLayout);
+ mCurrentKeyboardLayout = keyboardLayout;
+ return true;
+ }
+
+ public String[] getKeyboardLayouts() {
+ if (mKeyboardLayouts.isEmpty()) {
+ return (String[])ArrayUtils.emptyArray(String.class);
+ }
+ return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
+ }
+
+ public boolean addKeyboardLayout(String keyboardLayout) {
+ int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+ if (index >= 0) {
+ return false;
+ }
+ mKeyboardLayouts.add(-index - 1, keyboardLayout);
+ if (mCurrentKeyboardLayout == null) {
+ mCurrentKeyboardLayout = keyboardLayout;
+ }
+ return true;
+ }
+
+ public boolean removeKeyboardLayout(String keyboardLayout) {
+ int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+ if (index < 0) {
+ return false;
+ }
+ mKeyboardLayouts.remove(index);
+ updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
+ return true;
+ }
+
+ private void updateCurrentKeyboardLayoutIfRemoved(
+ String removedKeyboardLayout, int removedIndex) {
+ if (Objects.equal(mCurrentKeyboardLayout, removedKeyboardLayout)) {
+ if (!mKeyboardLayouts.isEmpty()) {
+ int index = removedIndex;
+ if (index == mKeyboardLayouts.size()) {
+ index = 0;
+ }
+ mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
+ } else {
+ mCurrentKeyboardLayout = null;
+ }
+ }
+ }
+
+ public boolean switchKeyboardLayout(int direction) {
+ final int size = mKeyboardLayouts.size();
+ if (size < 2) {
+ return false;
+ }
+ int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
+ assert index >= 0;
+ if (direction > 0) {
+ index = (index + 1) % size;
+ } else {
+ index = (index + size - 1) % size;
+ }
+ mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
+ return true;
+ }
+
+ public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
+ boolean changed = false;
+ for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
+ String keyboardLayout = mKeyboardLayouts.get(i);
+ if (!availableKeyboardLayouts.contains(keyboardLayout)) {
+ Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
+ mKeyboardLayouts.remove(i);
+ updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ public void loadFromXml(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (parser.getName().equals("keyboard-layout")) {
+ String descriptor = parser.getAttributeValue(null, "descriptor");
+ if (descriptor == null) {
+ throw new XmlPullParserException(
+ "Missing descriptor attribute on keyboard-layout.");
+ }
+ String current = parser.getAttributeValue(null, "current");
+ if (mKeyboardLayouts.contains(descriptor)) {
+ throw new XmlPullParserException(
+ "Found duplicate keyboard layout.");
+ }
+
+ mKeyboardLayouts.add(descriptor);
+ if (current != null && current.equals("true")) {
+ if (mCurrentKeyboardLayout != null) {
+ throw new XmlPullParserException(
+ "Found multiple current keyboard layouts.");
+ }
+ mCurrentKeyboardLayout = descriptor;
+ }
+ }
+ }
+
+ // Maintain invariant that layouts are sorted.
+ Collections.sort(mKeyboardLayouts);
+
+ // Maintain invariant that there is always a current keyboard layout unless
+ // there are none installed.
+ if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
+ mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
+ }
+ }
+
+ public void saveToXml(XmlSerializer serializer) throws IOException {
+ for (String layout : mKeyboardLayouts) {
+ serializer.startTag(null, "keyboard-layout");
+ serializer.attribute(null, "descriptor", layout);
+ if (layout.equals(mCurrentKeyboardLayout)) {
+ serializer.attribute(null, "current", "true");
+ }
+ serializer.endTag(null, "keyboard-layout");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 076ba9a..885ec96 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -5007,6 +5007,12 @@ public class WindowManagerService extends IWindowManager.Stub
// Called by window manager policy. Not exposed externally.
@Override
+ public void switchKeyboardLayout(int deviceId, int direction) {
+ mInputManager.switchKeyboardLayout(deviceId, direction);
+ }
+
+ // Called by window manager policy. Not exposed externally.
+ @Override
public void shutdown() {
ShutdownThread.shutdown(mContext, true);
}