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:
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
BIN
core/res/res/drawable-hdpi/ic_settings_language.png
Executable file
BIN
core/res/res/drawable-hdpi/ic_settings_language.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 986 B |
BIN
core/res/res/drawable-mdpi/ic_settings_language.png
Normal file
BIN
core/res/res/drawable-mdpi/ic_settings_language.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 756 B |
BIN
core/res/res/drawable-xhdpi/ic_settings_language.png
Normal file
BIN
core/res/res/drawable-xhdpi/ic_settings_language.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@ -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" />
|
||||
|
@ -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>
|
||||
|
||||
|
@ -252,6 +252,7 @@ key SPACE {
|
||||
label: ' '
|
||||
base: ' '
|
||||
alt, meta: fallback SEARCH
|
||||
ctrl: fallback LANGUAGE_SWITCH
|
||||
}
|
||||
|
||||
key ENTER {
|
||||
|
@ -249,6 +249,7 @@ key SPACE {
|
||||
label: ' '
|
||||
base: ' '
|
||||
alt, meta: fallback SEARCH
|
||||
ctrl: fallback LANGUAGE_SWITCH
|
||||
}
|
||||
|
||||
key ENTER {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
* <input-mananger-state>
|
||||
* <input-devices>
|
||||
* <input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
|
||||
* >input-devices>
|
||||
* >/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;
|
||||
}
|
||||
}
|
||||
|
416
services/java/com/android/server/input/PersistentDataStore.java
Normal file
416
services/java/com/android/server/input/PersistentDataStore.java
Normal 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>
|
||||
* <input-mananger-state>
|
||||
* <input-devices>
|
||||
* <input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
|
||||
* >input-devices>
|
||||
* >/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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
Reference in New Issue
Block a user