Add persistence for selected keyboard layout.
Bug: 6110399
Change-Id: I99544bf05e9755385bee478b5f047ccec2e5cae3
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index e819432..7640eff 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -16,11 +16,16 @@
package com.android.server.input;
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import com.android.server.Watchdog;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -63,15 +68,23 @@
import android.view.WindowManagerPolicy;
import android.view.KeyCharacterMap.UnavailableException;
+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.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+
+import libcore.io.IoUtils;
+import libcore.util.Objects;
/*
* Wraps the C++ InputManager and provides its callbacks.
@@ -91,9 +104,8 @@
private final Callbacks mCallbacks;
private final InputManagerHandler mHandler;
- // Used to simulate a persistent data store for keyboard layouts.
- // TODO: Replace with the real thing.
- private final HashMap<String, String> mFakeRegistry = new HashMap<String, String>();
+ // Persistent data store. Must be locked each time during use.
+ private final PersistentDataStore mDataStore = new PersistentDataStore();
// List of currently registered input devices changed listeners by process id.
private Object mInputDevicesLock = new Object();
@@ -635,7 +647,9 @@
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
- return mFakeRegistry.get(inputDeviceDescriptor);
+ synchronized (mDataStore) {
+ return mDataStore.getKeyboardLayout(inputDeviceDescriptor);
+ }
}
@Override // Binder call
@@ -650,7 +664,13 @@
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
- mFakeRegistry.put(inputDeviceDescriptor, keyboardLayoutDescriptor);
+ synchronized (mDataStore) {
+ try {
+ mDataStore.setKeyboardLayout(inputDeviceDescriptor, keyboardLayoutDescriptor);
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
}
/**
@@ -862,7 +882,7 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mContext.checkCallingOrSelfPermission("android.permission.DUMP")
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump InputManager from from pid="
+ Binder.getCallingPid()
@@ -1198,4 +1218,186 @@
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;
+ }
}