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.
|
// Keyboard layouts configuration.
|
||||||
KeyboardLayout[] getKeyboardLayouts();
|
KeyboardLayout[] getKeyboardLayouts();
|
||||||
KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
|
KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
|
||||||
String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
|
String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
|
||||||
void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
|
void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
|
||||||
|
String keyboardLayoutDescriptor);
|
||||||
|
String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor);
|
||||||
|
void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
|
||||||
|
String keyboardLayoutDescriptor);
|
||||||
|
void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
|
||||||
String keyboardLayoutDescriptor);
|
String keyboardLayoutDescriptor);
|
||||||
|
|
||||||
// Registers an input devices changed listener.
|
// Registers an input devices changed listener.
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package android.hardware.input;
|
package android.hardware.input;
|
||||||
|
|
||||||
|
import com.android.internal.util.ArrayUtils;
|
||||||
|
|
||||||
import android.annotation.SdkConstant;
|
import android.annotation.SdkConstant;
|
||||||
import android.annotation.SdkConstant.SdkConstantType;
|
import android.annotation.SdkConstant.SdkConstantType;
|
||||||
import android.content.Context;
|
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.
|
* Gets the ids of all input devices in the system.
|
||||||
* @return The input device ids.
|
* @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.
|
* @param inputDeviceDescriptor The input device descriptor.
|
||||||
* @return The keyboard layout descriptor, or null if unknown or if the default
|
* @return The keyboard layout descriptor, or null if no keyboard layout has been set.
|
||||||
* keyboard layout will be used.
|
|
||||||
*
|
*
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
|
public String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
|
||||||
if (inputDeviceDescriptor == null) {
|
if (inputDeviceDescriptor == null) {
|
||||||
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
|
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return mIm.getKeyboardLayoutForInputDevice(inputDeviceDescriptor);
|
return mIm.getCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor);
|
||||||
} catch (RemoteException ex) {
|
} 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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the keyboard layout descriptor for the specified input device.
|
* Sets the current keyboard layout descriptor for the specified input device.
|
||||||
* <p>
|
* <p>
|
||||||
* This method may have the side-effect of causing the input device in question
|
* This method may have the side-effect of causing the input device in question
|
||||||
* to be reconfigured.
|
* to be reconfigured.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param inputDeviceDescriptor The input device descriptor.
|
* @param inputDeviceDescriptor The input device descriptor.
|
||||||
* @param keyboardLayoutDescriptor The keyboard layout descriptor, or null to remove
|
* @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
|
||||||
* the mapping so that the default keyboard layout will be used for the input device.
|
|
||||||
*
|
*
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
|
public void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
|
||||||
String keyboardLayoutDescriptor) {
|
String keyboardLayoutDescriptor) {
|
||||||
if (inputDeviceDescriptor == null) {
|
if (inputDeviceDescriptor == null) {
|
||||||
throw new IllegalArgumentException("inputDeviceDescriptor must not be 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 {
|
try {
|
||||||
mIm.setKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
|
return mIm.getKeyboardLayoutsForInputDevice(inputDeviceDescriptor);
|
||||||
} catch (RemoteException ex) {
|
} 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);
|
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 shutdown();
|
||||||
public void rebootSafeMode();
|
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="low_internal_storage_view_title" />
|
||||||
<java-symbol type="string" name="report" />
|
<java-symbol type="string" name="report" />
|
||||||
<java-symbol type="string" name="select_input_method" />
|
<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_application" />
|
||||||
<java-symbol type="string" name="smv_process" />
|
<java-symbol type="string" name="smv_process" />
|
||||||
<java-symbol type="string" name="tethered_notification_message" />
|
<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="expander_ic_minimized" />
|
||||||
<java-symbol type="drawable" name="ic_menu_archive" />
|
<java-symbol type="drawable" name="ic_menu_archive" />
|
||||||
<java-symbol type="drawable" name="ic_menu_goto" />
|
<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="drawable" name="title_bar_medium" />
|
||||||
<java-symbol type="id" name="body" />
|
<java-symbol type="id" name="body" />
|
||||||
<java-symbol type="string" name="fast_scroll_alphabet" />
|
<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] -->
|
<!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=10] -->
|
||||||
<string name="hardware">Hardware</string>
|
<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_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
|
||||||
<string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
|
<string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
|
||||||
|
|
||||||
|
@ -252,6 +252,7 @@ key SPACE {
|
|||||||
label: ' '
|
label: ' '
|
||||||
base: ' '
|
base: ' '
|
||||||
alt, meta: fallback SEARCH
|
alt, meta: fallback SEARCH
|
||||||
|
ctrl: fallback LANGUAGE_SWITCH
|
||||||
}
|
}
|
||||||
|
|
||||||
key ENTER {
|
key ENTER {
|
||||||
|
@ -249,6 +249,7 @@ key SPACE {
|
|||||||
label: ' '
|
label: ' '
|
||||||
base: ' '
|
base: ' '
|
||||||
alt, meta: fallback SEARCH
|
alt, meta: fallback SEARCH
|
||||||
|
ctrl: fallback LANGUAGE_SWITCH
|
||||||
}
|
}
|
||||||
|
|
||||||
key ENTER {
|
key ENTER {
|
||||||
|
@ -326,6 +326,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
|
|
||||||
RecentApplicationsDialog mRecentAppsDialog;
|
RecentApplicationsDialog mRecentAppsDialog;
|
||||||
int mRecentAppsDialogHeldModifiers;
|
int mRecentAppsDialogHeldModifiers;
|
||||||
|
boolean mLanguageSwitchKeyPressed;
|
||||||
|
|
||||||
int mLidState = LID_ABSENT;
|
int mLidState = LID_ABSENT;
|
||||||
boolean mHaveBuiltInKeyboard;
|
boolean mHaveBuiltInKeyboard;
|
||||||
@ -1943,6 +1944,22 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|
|||||||
RECENT_APPS_BEHAVIOR_DISMISS_AND_SWITCH);
|
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.
|
// Let the application handle the key.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
package com.android.server.input;
|
package com.android.server.input;
|
||||||
|
|
||||||
import com.android.internal.os.AtomicFile;
|
import com.android.internal.R;
|
||||||
import com.android.internal.util.FastXmlSerializer;
|
|
||||||
import com.android.internal.util.XmlUtils;
|
import com.android.internal.util.XmlUtils;
|
||||||
import com.android.server.Watchdog;
|
import com.android.server.Watchdog;
|
||||||
|
|
||||||
@ -26,6 +25,9 @@ import org.xmlpull.v1.XmlPullParserException;
|
|||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
@ -70,23 +72,20 @@ import android.view.PointerIcon;
|
|||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
import android.view.WindowManagerPolicy;
|
import android.view.WindowManagerPolicy;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.HashSet;
|
||||||
|
|
||||||
import libcore.io.IoUtils;
|
|
||||||
import libcore.io.Streams;
|
import libcore.io.Streams;
|
||||||
import libcore.util.Objects;
|
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 String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
|
||||||
|
|
||||||
private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
|
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.
|
// Pointer to native input manager service object.
|
||||||
private final int mPtr;
|
private final int mPtr;
|
||||||
@ -109,6 +112,7 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
|
|||||||
private final InputManagerHandler mHandler;
|
private final InputManagerHandler mHandler;
|
||||||
private boolean mSystemReady;
|
private boolean mSystemReady;
|
||||||
private BluetoothService mBluetoothService;
|
private BluetoothService mBluetoothService;
|
||||||
|
private NotificationManager mNotificationManager;
|
||||||
|
|
||||||
// Persistent data store. Must be locked each time during use.
|
// Persistent data store. Must be locked each time during use.
|
||||||
private final PersistentDataStore mDataStore = new PersistentDataStore();
|
private final PersistentDataStore mDataStore = new PersistentDataStore();
|
||||||
@ -122,6 +126,11 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
|
|||||||
private final ArrayList<InputDevicesChangedListenerRecord>
|
private final ArrayList<InputDevicesChangedListenerRecord>
|
||||||
mTempInputDevicesChangedListenersToNotify =
|
mTempInputDevicesChangedListenersToNotify =
|
||||||
new ArrayList<InputDevicesChangedListenerRecord>(); // handler thread only
|
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.
|
// State for vibrator tokens.
|
||||||
private Object mVibratorLock = new Object();
|
private Object mVibratorLock = new Object();
|
||||||
@ -235,6 +244,8 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
|
|||||||
Slog.d(TAG, "System ready.");
|
Slog.d(TAG, "System ready.");
|
||||||
}
|
}
|
||||||
mBluetoothService = bluetoothService;
|
mBluetoothService = bluetoothService;
|
||||||
|
mNotificationManager = (NotificationManager)mContext.getSystemService(
|
||||||
|
Context.NOTIFICATION_SERVICE);
|
||||||
mSystemReady = true;
|
mSystemReady = true;
|
||||||
|
|
||||||
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
||||||
@ -244,10 +255,7 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
|
|||||||
mContext.registerReceiver(new BroadcastReceiver() {
|
mContext.registerReceiver(new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
if (DEBUG) {
|
updateKeyboardLayouts();
|
||||||
Slog.d(TAG, "Packages changed, reloading keyboard layouts.");
|
|
||||||
}
|
|
||||||
reloadKeyboardLayouts();
|
|
||||||
}
|
}
|
||||||
}, filter, null, mHandler);
|
}, filter, null, mHandler);
|
||||||
|
|
||||||
@ -255,22 +263,25 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
|
|||||||
mContext.registerReceiver(new BroadcastReceiver() {
|
mContext.registerReceiver(new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
if (DEBUG) {
|
|
||||||
Slog.d(TAG, "Bluetooth alias changed, reloading device names.");
|
|
||||||
}
|
|
||||||
reloadDeviceAliases();
|
reloadDeviceAliases();
|
||||||
}
|
}
|
||||||
}, filter, null, mHandler);
|
}, filter, null, mHandler);
|
||||||
|
|
||||||
reloadKeyboardLayouts();
|
mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);
|
||||||
reloadDeviceAliases();
|
mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reloadKeyboardLayouts() {
|
private void reloadKeyboardLayouts() {
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Reloading keyboard layouts.");
|
||||||
|
}
|
||||||
nativeReloadKeyboardLayouts(mPtr);
|
nativeReloadKeyboardLayouts(mPtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reloadDeviceAliases() {
|
private void reloadDeviceAliases() {
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Reloading device names.");
|
||||||
|
}
|
||||||
nativeReloadDeviceAliases(mPtr);
|
nativeReloadDeviceAliases(mPtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -558,9 +569,11 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Must be called on handler.
|
// Must be called on handler.
|
||||||
private void deliverInputDevicesChanged() {
|
private void deliverInputDevicesChanged(InputDevice[] oldInputDevices) {
|
||||||
|
// Scan for changes.
|
||||||
|
int numFullKeyboardsAdded = 0;
|
||||||
mTempInputDevicesChangedListenersToNotify.clear();
|
mTempInputDevicesChangedListenersToNotify.clear();
|
||||||
|
mTempFullKeyboards.clear();
|
||||||
final int numListeners;
|
final int numListeners;
|
||||||
final int[] deviceIdAndGeneration;
|
final int[] deviceIdAndGeneration;
|
||||||
synchronized (mInputDevicesLock) {
|
synchronized (mInputDevicesLock) {
|
||||||
@ -581,13 +594,126 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
|
|||||||
final InputDevice inputDevice = mInputDevices[i];
|
final InputDevice inputDevice = mInputDevices[i];
|
||||||
deviceIdAndGeneration[i * 2] = inputDevice.getId();
|
deviceIdAndGeneration[i * 2] = inputDevice.getId();
|
||||||
deviceIdAndGeneration[i * 2 + 1] = inputDevice.getGeneration();
|
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++) {
|
for (int i = 0; i < numListeners; i++) {
|
||||||
mTempInputDevicesChangedListenersToNotify.get(i).notifyInputDevicesChanged(
|
mTempInputDevicesChangedListenersToNotify.get(i).notifyInputDevicesChanged(
|
||||||
deviceIdAndGeneration);
|
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
|
@Override // Binder call
|
||||||
@ -720,43 +846,147 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override // Binder call
|
@Override // Binder call
|
||||||
public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
|
public String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
|
||||||
if (inputDeviceDescriptor == null) {
|
if (inputDeviceDescriptor == null) {
|
||||||
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
|
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (mDataStore) {
|
synchronized (mDataStore) {
|
||||||
return mDataStore.getKeyboardLayout(inputDeviceDescriptor);
|
return mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // Binder call
|
@Override // Binder call
|
||||||
public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
|
public void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
|
||||||
String keyboardLayoutDescriptor) {
|
String keyboardLayoutDescriptor) {
|
||||||
if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
|
if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
|
||||||
"setKeyboardLayoutForInputDevice()")) {
|
"setCurrentKeyboardLayoutForInputDevice()")) {
|
||||||
throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
|
throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputDeviceDescriptor == null) {
|
if (inputDeviceDescriptor == null) {
|
||||||
throw new IllegalArgumentException("inputDeviceDescriptor must not be 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) {
|
synchronized (mDataStore) {
|
||||||
try {
|
try {
|
||||||
changed = mDataStore.setKeyboardLayout(
|
if (mDataStore.setCurrentKeyboardLayout(
|
||||||
inputDeviceDescriptor, keyboardLayoutDescriptor);
|
inputDeviceDescriptor, keyboardLayoutDescriptor)) {
|
||||||
|
mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
mDataStore.saveIfNeeded();
|
mDataStore.saveIfNeeded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (changed) {
|
@Override // Binder call
|
||||||
if (DEBUG) {
|
public String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor) {
|
||||||
Slog.d(TAG, "Keyboard layout changed, reloading keyboard layouts.");
|
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.
|
// Native callback.
|
||||||
private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
|
private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
|
||||||
synchronized (mInputDevicesLock) {
|
synchronized (mInputDevicesLock) {
|
||||||
mInputDevices = inputDevices;
|
|
||||||
|
|
||||||
if (!mInputDevicesChangedPending) {
|
if (!mInputDevicesChangedPending) {
|
||||||
mInputDevicesChangedPending = true;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String keyboardLayoutDescriptor = getKeyboardLayoutForInputDevice(inputDeviceDescriptor);
|
String keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(
|
||||||
|
inputDeviceDescriptor);
|
||||||
if (keyboardLayoutDescriptor == null) {
|
if (keyboardLayoutDescriptor == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -1203,7 +1435,19 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
|
|||||||
public void handleMessage(Message msg) {
|
public void handleMessage(Message msg) {
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case MSG_DELIVER_INPUT_DEVICES_CHANGED:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1316,186 +1560,4 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
|
|||||||
onVibratorTokenDied(this);
|
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);
|
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.
|
// Called by window manager policy. Not exposed externally.
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
|
Reference in New Issue
Block a user