Add support for switching between multiple keyboard layouts.

Also show a notification when an external keyboard is connected
and does not have a keyboard layout selected yet.

Bug: 6405203
Change-Id: Id0ac6d83b3b381f8a236b2244a04c9acb203db3c
This commit is contained in:
Jeff Brown
2012-05-18 14:41:19 -07:00
parent 51f2430217
commit cf39bdf3df
14 changed files with 870 additions and 232 deletions

View File

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

View File

@ -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;
@ -216,6 +218,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, or null to remove
* the mapping so that the default keyboard layout will be used for the input device.
* @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
*
* @hide
*/
public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
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 {
mIm.setKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
return mIm.getKeyboardLayoutsForInputDevice(inputDeviceDescriptor);
} catch (RemoteException ex) {
Log.w(TAG, "Could not set keyboard layout for input device.", 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 descriptor of the keyboard layout to add.
*
* @hide
*/
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.removeKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
} catch (RemoteException ex) {
Log.w(TAG, "Could not remove keyboard layout for input device.", ex);
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

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

View File

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

View File

@ -252,6 +252,7 @@ key SPACE {
label: ' '
base: ' '
alt, meta: fallback SEARCH
ctrl: fallback LANGUAGE_SWITCH
}
key ENTER {

View File

@ -249,6 +249,7 @@ key SPACE {
label: ' '
base: ' '
alt, meta: fallback SEARCH
ctrl: fallback LANGUAGE_SWITCH
}
key ENTER {

View File

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

View File

@ -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");
}
final boolean changed;
synchronized (mDataStore) {
try {
changed = mDataStore.setKeyboardLayout(
inputDeviceDescriptor, keyboardLayoutDescriptor);
if (mDataStore.setCurrentKeyboardLayout(
inputDeviceDescriptor, keyboardLayoutDescriptor)) {
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 String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor) {
if (inputDeviceDescriptor == null) {
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
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 {
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();
}
}
}
@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;
}
}

View File

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

View File

@ -5005,6 +5005,12 @@ public class WindowManagerService extends IWindowManager.Stub
return mInputManager.monitorInput(inputChannelName);
}
// 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() {