Home | History | Annotate | Download | only in input
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.input;
     18 
     19 import com.android.internal.util.ArrayUtils;
     20 import com.android.internal.util.FastXmlSerializer;
     21 import com.android.internal.util.XmlUtils;
     22 
     23 import org.xmlpull.v1.XmlPullParser;
     24 import org.xmlpull.v1.XmlPullParserException;
     25 import org.xmlpull.v1.XmlSerializer;
     26 
     27 import android.view.Surface;
     28 import android.hardware.input.TouchCalibration;
     29 import android.util.AtomicFile;
     30 import android.util.Slog;
     31 import android.util.Xml;
     32 
     33 import java.io.BufferedInputStream;
     34 import java.io.BufferedOutputStream;
     35 import java.io.File;
     36 import java.io.FileNotFoundException;
     37 import java.io.FileOutputStream;
     38 import java.io.IOException;
     39 import java.io.InputStream;
     40 import java.util.ArrayList;
     41 import java.util.Collections;
     42 import java.util.HashMap;
     43 import java.util.Map;
     44 import java.util.Set;
     45 
     46 import libcore.io.IoUtils;
     47 import libcore.util.Objects;
     48 
     49 /**
     50  * Manages persistent state recorded by the input manager service as an XML file.
     51  * Caller must acquire lock on the data store before accessing it.
     52  *
     53  * File format:
     54  * <code>
     55  * &lt;input-mananger-state>
     56  *   &lt;input-devices>
     57  *     &lt;input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
     58  *   &gt;input-devices>
     59  * &gt;/input-manager-state>
     60  * </code>
     61  */
     62 final class PersistentDataStore {
     63     static final String TAG = "InputManager";
     64 
     65     // Input device state by descriptor.
     66     private final HashMap<String, InputDeviceState> mInputDevices =
     67             new HashMap<String, InputDeviceState>();
     68     private final AtomicFile mAtomicFile;
     69 
     70     // True if the data has been loaded.
     71     private boolean mLoaded;
     72 
     73     // True if there are changes to be saved.
     74     private boolean mDirty;
     75 
     76     public PersistentDataStore() {
     77         mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"));
     78     }
     79 
     80     public void saveIfNeeded() {
     81         if (mDirty) {
     82             save();
     83             mDirty = false;
     84         }
     85     }
     86 
     87     public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
     88         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
     89         if (state == null) {
     90             return TouchCalibration.IDENTITY;
     91         }
     92 
     93         TouchCalibration cal = state.getTouchCalibration(surfaceRotation);
     94         if (cal == null) {
     95             return TouchCalibration.IDENTITY;
     96         }
     97         return cal;
     98     }
     99 
    100     public boolean setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation, TouchCalibration calibration) {
    101         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
    102 
    103         if (state.setTouchCalibration(surfaceRotation, calibration)) {
    104             setDirty();
    105             return true;
    106         }
    107 
    108         return false;
    109     }
    110 
    111     public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
    112         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
    113         return state != null ? state.getCurrentKeyboardLayout() : null;
    114     }
    115 
    116     public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
    117             String keyboardLayoutDescriptor) {
    118         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
    119         if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
    120             setDirty();
    121             return true;
    122         }
    123         return false;
    124     }
    125 
    126     public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
    127         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
    128         if (state == null) {
    129             return (String[])ArrayUtils.emptyArray(String.class);
    130         }
    131         return state.getKeyboardLayouts();
    132     }
    133 
    134     public boolean addKeyboardLayout(String inputDeviceDescriptor,
    135             String keyboardLayoutDescriptor) {
    136         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
    137         if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
    138             setDirty();
    139             return true;
    140         }
    141         return false;
    142     }
    143 
    144     public boolean removeKeyboardLayout(String inputDeviceDescriptor,
    145             String keyboardLayoutDescriptor) {
    146         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
    147         if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
    148             setDirty();
    149             return true;
    150         }
    151         return false;
    152     }
    153 
    154     public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
    155         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
    156         if (state != null && state.switchKeyboardLayout(direction)) {
    157             setDirty();
    158             return true;
    159         }
    160         return false;
    161     }
    162 
    163     public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
    164         boolean changed = false;
    165         for (InputDeviceState state : mInputDevices.values()) {
    166             if (state.removeUninstalledKeyboardLayouts(availableKeyboardLayouts)) {
    167                 changed = true;
    168             }
    169         }
    170         if (changed) {
    171             setDirty();
    172             return true;
    173         }
    174         return false;
    175     }
    176 
    177     private InputDeviceState getInputDeviceState(String inputDeviceDescriptor,
    178             boolean createIfAbsent) {
    179         loadIfNeeded();
    180         InputDeviceState state = mInputDevices.get(inputDeviceDescriptor);
    181         if (state == null && createIfAbsent) {
    182             state = new InputDeviceState();
    183             mInputDevices.put(inputDeviceDescriptor, state);
    184             setDirty();
    185         }
    186         return state;
    187     }
    188 
    189     private void loadIfNeeded() {
    190         if (!mLoaded) {
    191             load();
    192             mLoaded = true;
    193         }
    194     }
    195 
    196     private void setDirty() {
    197         mDirty = true;
    198     }
    199 
    200     private void clearState() {
    201         mInputDevices.clear();
    202     }
    203 
    204     private void load() {
    205         clearState();
    206 
    207         final InputStream is;
    208         try {
    209             is = mAtomicFile.openRead();
    210         } catch (FileNotFoundException ex) {
    211             return;
    212         }
    213 
    214         XmlPullParser parser;
    215         try {
    216             parser = Xml.newPullParser();
    217             parser.setInput(new BufferedInputStream(is), null);
    218             loadFromXml(parser);
    219         } catch (IOException ex) {
    220             Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
    221             clearState();
    222         } catch (XmlPullParserException ex) {
    223             Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
    224             clearState();
    225         } finally {
    226             IoUtils.closeQuietly(is);
    227         }
    228     }
    229 
    230     private void save() {
    231         final FileOutputStream os;
    232         try {
    233             os = mAtomicFile.startWrite();
    234             boolean success = false;
    235             try {
    236                 XmlSerializer serializer = new FastXmlSerializer();
    237                 serializer.setOutput(new BufferedOutputStream(os), "utf-8");
    238                 saveToXml(serializer);
    239                 serializer.flush();
    240                 success = true;
    241             } finally {
    242                 if (success) {
    243                     mAtomicFile.finishWrite(os);
    244                 } else {
    245                     mAtomicFile.failWrite(os);
    246                 }
    247             }
    248         } catch (IOException ex) {
    249             Slog.w(InputManagerService.TAG, "Failed to save input manager persistent store data.", ex);
    250         }
    251     }
    252 
    253     private void loadFromXml(XmlPullParser parser)
    254             throws IOException, XmlPullParserException {
    255         XmlUtils.beginDocument(parser, "input-manager-state");
    256         final int outerDepth = parser.getDepth();
    257         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
    258             if (parser.getName().equals("input-devices")) {
    259                 loadInputDevicesFromXml(parser);
    260             }
    261         }
    262     }
    263 
    264     private void loadInputDevicesFromXml(XmlPullParser parser)
    265             throws IOException, XmlPullParserException {
    266         final int outerDepth = parser.getDepth();
    267         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
    268             if (parser.getName().equals("input-device")) {
    269                 String descriptor = parser.getAttributeValue(null, "descriptor");
    270                 if (descriptor == null) {
    271                     throw new XmlPullParserException(
    272                             "Missing descriptor attribute on input-device.");
    273                 }
    274                 if (mInputDevices.containsKey(descriptor)) {
    275                     throw new XmlPullParserException("Found duplicate input device.");
    276                 }
    277 
    278                 InputDeviceState state = new InputDeviceState();
    279                 state.loadFromXml(parser);
    280                 mInputDevices.put(descriptor, state);
    281             }
    282         }
    283     }
    284 
    285     private void saveToXml(XmlSerializer serializer) throws IOException {
    286         serializer.startDocument(null, true);
    287         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
    288         serializer.startTag(null, "input-manager-state");
    289         serializer.startTag(null, "input-devices");
    290         for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
    291             final String descriptor = entry.getKey();
    292             final InputDeviceState state = entry.getValue();
    293             serializer.startTag(null, "input-device");
    294             serializer.attribute(null, "descriptor", descriptor);
    295             state.saveToXml(serializer);
    296             serializer.endTag(null, "input-device");
    297         }
    298         serializer.endTag(null, "input-devices");
    299         serializer.endTag(null, "input-manager-state");
    300         serializer.endDocument();
    301     }
    302 
    303     private static final class InputDeviceState {
    304         private static final String[] CALIBRATION_NAME = { "x_scale",
    305                 "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
    306 
    307         private TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
    308         private String mCurrentKeyboardLayout;
    309         private ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
    310 
    311         public TouchCalibration getTouchCalibration(int surfaceRotation) {
    312             try {
    313                 return mTouchCalibration[surfaceRotation];
    314             } catch (ArrayIndexOutOfBoundsException ex) {
    315                 Slog.w(InputManagerService.TAG, "Cannot get touch calibration.", ex);
    316                 return null;
    317             }
    318         }
    319 
    320         public boolean setTouchCalibration(int surfaceRotation, TouchCalibration calibration) {
    321             try {
    322                 if (!calibration.equals(mTouchCalibration[surfaceRotation])) {
    323                     mTouchCalibration[surfaceRotation] = calibration;
    324                     return true;
    325                 }
    326                 return false;
    327             } catch (ArrayIndexOutOfBoundsException ex) {
    328                 Slog.w(InputManagerService.TAG, "Cannot set touch calibration.", ex);
    329                 return false;
    330             }
    331         }
    332 
    333         public String getCurrentKeyboardLayout() {
    334             return mCurrentKeyboardLayout;
    335         }
    336 
    337         public boolean setCurrentKeyboardLayout(String keyboardLayout) {
    338             if (Objects.equal(mCurrentKeyboardLayout, keyboardLayout)) {
    339                 return false;
    340             }
    341             addKeyboardLayout(keyboardLayout);
    342             mCurrentKeyboardLayout = keyboardLayout;
    343             return true;
    344         }
    345 
    346         public String[] getKeyboardLayouts() {
    347             if (mKeyboardLayouts.isEmpty()) {
    348                 return (String[])ArrayUtils.emptyArray(String.class);
    349             }
    350             return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
    351         }
    352 
    353         public boolean addKeyboardLayout(String keyboardLayout) {
    354             int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
    355             if (index >= 0) {
    356                 return false;
    357             }
    358             mKeyboardLayouts.add(-index - 1, keyboardLayout);
    359             if (mCurrentKeyboardLayout == null) {
    360                 mCurrentKeyboardLayout = keyboardLayout;
    361             }
    362             return true;
    363         }
    364 
    365         public boolean removeKeyboardLayout(String keyboardLayout) {
    366             int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
    367             if (index < 0) {
    368                 return false;
    369             }
    370             mKeyboardLayouts.remove(index);
    371             updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
    372             return true;
    373         }
    374 
    375         private void updateCurrentKeyboardLayoutIfRemoved(
    376                 String removedKeyboardLayout, int removedIndex) {
    377             if (Objects.equal(mCurrentKeyboardLayout, removedKeyboardLayout)) {
    378                 if (!mKeyboardLayouts.isEmpty()) {
    379                     int index = removedIndex;
    380                     if (index == mKeyboardLayouts.size()) {
    381                         index = 0;
    382                     }
    383                     mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
    384                 } else {
    385                     mCurrentKeyboardLayout = null;
    386                 }
    387             }
    388         }
    389 
    390         public boolean switchKeyboardLayout(int direction) {
    391             final int size = mKeyboardLayouts.size();
    392             if (size < 2) {
    393                 return false;
    394             }
    395             int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
    396             assert index >= 0;
    397             if (direction > 0) {
    398                 index = (index + 1) % size;
    399             } else {
    400                 index = (index + size - 1) % size;
    401             }
    402             mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
    403             return true;
    404         }
    405 
    406         public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
    407             boolean changed = false;
    408             for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
    409                 String keyboardLayout = mKeyboardLayouts.get(i);
    410                 if (!availableKeyboardLayouts.contains(keyboardLayout)) {
    411                     Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
    412                     mKeyboardLayouts.remove(i);
    413                     updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
    414                     changed = true;
    415                 }
    416             }
    417             return changed;
    418         }
    419 
    420         public void loadFromXml(XmlPullParser parser)
    421                 throws IOException, XmlPullParserException {
    422             final int outerDepth = parser.getDepth();
    423             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
    424                 if (parser.getName().equals("keyboard-layout")) {
    425                     String descriptor = parser.getAttributeValue(null, "descriptor");
    426                     if (descriptor == null) {
    427                         throw new XmlPullParserException(
    428                                 "Missing descriptor attribute on keyboard-layout.");
    429                     }
    430                     String current = parser.getAttributeValue(null, "current");
    431                     if (mKeyboardLayouts.contains(descriptor)) {
    432                         throw new XmlPullParserException(
    433                                 "Found duplicate keyboard layout.");
    434                     }
    435 
    436                     mKeyboardLayouts.add(descriptor);
    437                     if (current != null && current.equals("true")) {
    438                         if (mCurrentKeyboardLayout != null) {
    439                             throw new XmlPullParserException(
    440                                     "Found multiple current keyboard layouts.");
    441                         }
    442                         mCurrentKeyboardLayout = descriptor;
    443                     }
    444                 } else if (parser.getName().equals("calibration")) {
    445                     String format = parser.getAttributeValue(null, "format");
    446                     String rotation = parser.getAttributeValue(null, "rotation");
    447                     int r = -1;
    448 
    449                     if (format == null) {
    450                         throw new XmlPullParserException(
    451                                 "Missing format attribute on calibration.");
    452                     }
    453                     if (!format.equals("affine")) {
    454                         throw new XmlPullParserException(
    455                                 "Unsupported format for calibration.");
    456                     }
    457                     if (rotation != null) {
    458                         try {
    459                             r = stringToSurfaceRotation(rotation);
    460                         } catch (IllegalArgumentException e) {
    461                             throw new XmlPullParserException(
    462                                     "Unsupported rotation for calibration.");
    463                         }
    464                     }
    465 
    466                     float[] matrix = TouchCalibration.IDENTITY.getAffineTransform();
    467                     int depth = parser.getDepth();
    468                     while (XmlUtils.nextElementWithin(parser, depth)) {
    469                         String tag = parser.getName().toLowerCase();
    470                         String value = parser.nextText();
    471 
    472                         for (int i = 0; i < matrix.length && i < CALIBRATION_NAME.length; i++) {
    473                             if (tag.equals(CALIBRATION_NAME[i])) {
    474                                 matrix[i] = Float.parseFloat(value);
    475                                 break;
    476                             }
    477                         }
    478                     }
    479 
    480                     if (r == -1) {
    481                         // Assume calibration applies to all rotations
    482                         for (r = 0; r < mTouchCalibration.length; r++) {
    483                             mTouchCalibration[r] = new TouchCalibration(matrix[0],
    484                                 matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
    485                         }
    486                     } else {
    487                         mTouchCalibration[r] = new TouchCalibration(matrix[0],
    488                             matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
    489                     }
    490                 }
    491             }
    492 
    493             // Maintain invariant that layouts are sorted.
    494             Collections.sort(mKeyboardLayouts);
    495 
    496             // Maintain invariant that there is always a current keyboard layout unless
    497             // there are none installed.
    498             if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
    499                 mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
    500             }
    501         }
    502 
    503         public void saveToXml(XmlSerializer serializer) throws IOException {
    504             for (String layout : mKeyboardLayouts) {
    505                 serializer.startTag(null, "keyboard-layout");
    506                 serializer.attribute(null, "descriptor", layout);
    507                 if (layout.equals(mCurrentKeyboardLayout)) {
    508                     serializer.attribute(null, "current", "true");
    509                 }
    510                 serializer.endTag(null, "keyboard-layout");
    511             }
    512 
    513             for (int i = 0; i < mTouchCalibration.length; i++) {
    514                 if (mTouchCalibration[i] != null) {
    515                     String rotation = surfaceRotationToString(i);
    516                     float[] transform = mTouchCalibration[i].getAffineTransform();
    517 
    518                     serializer.startTag(null, "calibration");
    519                     serializer.attribute(null, "format", "affine");
    520                     serializer.attribute(null, "rotation", rotation);
    521                     for (int j = 0; j < transform.length && j < CALIBRATION_NAME.length; j++) {
    522                         serializer.startTag(null, CALIBRATION_NAME[j]);
    523                         serializer.text(Float.toString(transform[j]));
    524                         serializer.endTag(null, CALIBRATION_NAME[j]);
    525                     }
    526                     serializer.endTag(null, "calibration");
    527                 }
    528             }
    529         }
    530 
    531         private static String surfaceRotationToString(int surfaceRotation) {
    532             switch (surfaceRotation) {
    533                 case Surface.ROTATION_0:   return "0";
    534                 case Surface.ROTATION_90:  return "90";
    535                 case Surface.ROTATION_180: return "180";
    536                 case Surface.ROTATION_270: return "270";
    537             }
    538             throw new IllegalArgumentException("Unsupported surface rotation value" + surfaceRotation);
    539         }
    540 
    541         private static int stringToSurfaceRotation(String s) {
    542             if ("0".equals(s)) {
    543                 return Surface.ROTATION_0;
    544             }
    545             if ("90".equals(s)) {
    546                 return Surface.ROTATION_90;
    547             }
    548             if ("180".equals(s)) {
    549                 return Surface.ROTATION_180;
    550             }
    551             if ("270".equals(s)) {
    552                 return Surface.ROTATION_270;
    553             }
    554             throw new IllegalArgumentException("Unsupported surface rotation string '" + s + "'");
    555         }
    556     }
    557 }
    558