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.inputmethod.InputMethodSubtypeHandle;
     20 import com.android.internal.util.ArrayUtils;
     21 import com.android.internal.util.FastXmlSerializer;
     22 import com.android.internal.util.XmlUtils;
     23 
     24 import org.xmlpull.v1.XmlPullParser;
     25 import org.xmlpull.v1.XmlPullParserException;
     26 import org.xmlpull.v1.XmlSerializer;
     27 
     28 import android.annotation.Nullable;
     29 import android.view.Surface;
     30 import android.hardware.input.TouchCalibration;
     31 import android.text.TextUtils;
     32 import android.util.ArrayMap;
     33 import android.util.AtomicFile;
     34 import android.util.Slog;
     35 import android.util.Xml;
     36 
     37 import java.io.BufferedInputStream;
     38 import java.io.BufferedOutputStream;
     39 import java.io.File;
     40 import java.io.FileNotFoundException;
     41 import java.io.FileOutputStream;
     42 import java.io.IOException;
     43 import java.io.InputStream;
     44 import java.io.PrintWriter;
     45 import java.nio.charset.StandardCharsets;
     46 import java.util.ArrayList;
     47 import java.util.Arrays;
     48 import java.util.Collections;
     49 import java.util.HashMap;
     50 import java.util.List;
     51 import java.util.Map;
     52 import java.util.Set;
     53 
     54 import libcore.io.IoUtils;
     55 import libcore.util.Objects;
     56 
     57 /**
     58  * Manages persistent state recorded by the input manager service as an XML file.
     59  * Caller must acquire lock on the data store before accessing it.
     60  *
     61  * File format:
     62  * <code>
     63  * &lt;input-mananger-state>
     64  *   &lt;input-devices>
     65  *     &lt;input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
     66  *   &gt;input-devices>
     67  * &gt;/input-manager-state>
     68  * </code>
     69  */
     70 final class PersistentDataStore {
     71     static final String TAG = "InputManager";
     72 
     73     // Input device state by descriptor.
     74     private final HashMap<String, InputDeviceState> mInputDevices =
     75             new HashMap<String, InputDeviceState>();
     76     private final AtomicFile mAtomicFile;
     77 
     78     // True if the data has been loaded.
     79     private boolean mLoaded;
     80 
     81     // True if there are changes to be saved.
     82     private boolean mDirty;
     83 
     84     public PersistentDataStore() {
     85         mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"));
     86     }
     87 
     88     public void saveIfNeeded() {
     89         if (mDirty) {
     90             save();
     91             mDirty = false;
     92         }
     93     }
     94 
     95     public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
     96         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
     97         if (state == null) {
     98             return TouchCalibration.IDENTITY;
     99         }
    100 
    101         TouchCalibration cal = state.getTouchCalibration(surfaceRotation);
    102         if (cal == null) {
    103             return TouchCalibration.IDENTITY;
    104         }
    105         return cal;
    106     }
    107 
    108     public boolean setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation, TouchCalibration calibration) {
    109         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
    110 
    111         if (state.setTouchCalibration(surfaceRotation, calibration)) {
    112             setDirty();
    113             return true;
    114         }
    115 
    116         return false;
    117     }
    118 
    119     public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
    120         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
    121         return state != null ? state.getCurrentKeyboardLayout() : null;
    122     }
    123 
    124     public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
    125             String keyboardLayoutDescriptor) {
    126         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
    127         if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
    128             setDirty();
    129             return true;
    130         }
    131         return false;
    132     }
    133 
    134     public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
    135         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
    136         if (state == null) {
    137             return (String[])ArrayUtils.emptyArray(String.class);
    138         }
    139         return state.getKeyboardLayouts();
    140     }
    141     public String getKeyboardLayout(String inputDeviceDescriptor,
    142             InputMethodSubtypeHandle imeHandle) {
    143         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
    144         if (state == null) {
    145             return null;
    146         }
    147         return state.getKeyboardLayout(imeHandle);
    148     }
    149 
    150     public boolean setKeyboardLayout(String inputDeviceDescriptor,
    151             InputMethodSubtypeHandle imeHandle, String keyboardLayoutDescriptor) {
    152         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
    153         if (state.setKeyboardLayout(imeHandle, keyboardLayoutDescriptor)) {
    154             setDirty();
    155             return true;
    156         }
    157         return false;
    158     }
    159 
    160     public boolean addKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor) {
    161         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
    162         if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
    163             setDirty();
    164             return true;
    165         }
    166         return false;
    167     }
    168 
    169     public boolean removeKeyboardLayout(String inputDeviceDescriptor,
    170             String keyboardLayoutDescriptor) {
    171         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
    172         if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
    173             setDirty();
    174             return true;
    175         }
    176         return false;
    177     }
    178 
    179     public boolean switchKeyboardLayout(String inputDeviceDescriptor,
    180             InputMethodSubtypeHandle imeHandle) {
    181         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
    182         if (state != null && state.switchKeyboardLayout(imeHandle)) {
    183             setDirty();
    184             return true;
    185         }
    186         return false;
    187     }
    188 
    189     public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
    190         boolean changed = false;
    191         for (InputDeviceState state : mInputDevices.values()) {
    192             if (state.removeUninstalledKeyboardLayouts(availableKeyboardLayouts)) {
    193                 changed = true;
    194             }
    195         }
    196         if (changed) {
    197             setDirty();
    198             return true;
    199         }
    200         return false;
    201     }
    202 
    203     private InputDeviceState getInputDeviceState(String inputDeviceDescriptor,
    204             boolean createIfAbsent) {
    205         loadIfNeeded();
    206         InputDeviceState state = mInputDevices.get(inputDeviceDescriptor);
    207         if (state == null && createIfAbsent) {
    208             state = new InputDeviceState();
    209             mInputDevices.put(inputDeviceDescriptor, state);
    210             setDirty();
    211         }
    212         return state;
    213     }
    214 
    215     private void loadIfNeeded() {
    216         if (!mLoaded) {
    217             load();
    218             mLoaded = true;
    219         }
    220     }
    221 
    222     private void setDirty() {
    223         mDirty = true;
    224     }
    225 
    226     private void clearState() {
    227         mInputDevices.clear();
    228     }
    229 
    230     private void load() {
    231         clearState();
    232 
    233         final InputStream is;
    234         try {
    235             is = mAtomicFile.openRead();
    236         } catch (FileNotFoundException ex) {
    237             return;
    238         }
    239 
    240         XmlPullParser parser;
    241         try {
    242             parser = Xml.newPullParser();
    243             parser.setInput(new BufferedInputStream(is), StandardCharsets.UTF_8.name());
    244             loadFromXml(parser);
    245         } catch (IOException ex) {
    246             Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
    247             clearState();
    248         } catch (XmlPullParserException ex) {
    249             Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
    250             clearState();
    251         } finally {
    252             IoUtils.closeQuietly(is);
    253         }
    254     }
    255 
    256     private void save() {
    257         final FileOutputStream os;
    258         try {
    259             os = mAtomicFile.startWrite();
    260             boolean success = false;
    261             try {
    262                 XmlSerializer serializer = new FastXmlSerializer();
    263                 serializer.setOutput(new BufferedOutputStream(os), StandardCharsets.UTF_8.name());
    264                 saveToXml(serializer);
    265                 serializer.flush();
    266                 success = true;
    267             } finally {
    268                 if (success) {
    269                     mAtomicFile.finishWrite(os);
    270                 } else {
    271                     mAtomicFile.failWrite(os);
    272                 }
    273             }
    274         } catch (IOException ex) {
    275             Slog.w(InputManagerService.TAG, "Failed to save input manager persistent store data.", ex);
    276         }
    277     }
    278 
    279     private void loadFromXml(XmlPullParser parser)
    280             throws IOException, XmlPullParserException {
    281         XmlUtils.beginDocument(parser, "input-manager-state");
    282         final int outerDepth = parser.getDepth();
    283         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
    284             if (parser.getName().equals("input-devices")) {
    285                 loadInputDevicesFromXml(parser);
    286             }
    287         }
    288     }
    289 
    290     private void loadInputDevicesFromXml(XmlPullParser parser)
    291             throws IOException, XmlPullParserException {
    292         final int outerDepth = parser.getDepth();
    293         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
    294             if (parser.getName().equals("input-device")) {
    295                 String descriptor = parser.getAttributeValue(null, "descriptor");
    296                 if (descriptor == null) {
    297                     throw new XmlPullParserException(
    298                             "Missing descriptor attribute on input-device.");
    299                 }
    300                 if (mInputDevices.containsKey(descriptor)) {
    301                     throw new XmlPullParserException("Found duplicate input device.");
    302                 }
    303 
    304                 InputDeviceState state = new InputDeviceState();
    305                 state.loadFromXml(parser);
    306                 mInputDevices.put(descriptor, state);
    307             }
    308         }
    309     }
    310 
    311     private void saveToXml(XmlSerializer serializer) throws IOException {
    312         serializer.startDocument(null, true);
    313         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
    314         serializer.startTag(null, "input-manager-state");
    315         serializer.startTag(null, "input-devices");
    316         for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
    317             final String descriptor = entry.getKey();
    318             final InputDeviceState state = entry.getValue();
    319             serializer.startTag(null, "input-device");
    320             serializer.attribute(null, "descriptor", descriptor);
    321             state.saveToXml(serializer);
    322             serializer.endTag(null, "input-device");
    323         }
    324         serializer.endTag(null, "input-devices");
    325         serializer.endTag(null, "input-manager-state");
    326         serializer.endDocument();
    327     }
    328 
    329     public void dump(PrintWriter pw, String prefix) {
    330         pw.println(prefix + "PersistentDataStore");
    331         pw.println(prefix + "  mLoaded=" + mLoaded);
    332         pw.println(prefix + "  mDirty=" + mDirty);
    333         pw.println(prefix + "  InputDeviceStates:");
    334         int i = 0;
    335         for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
    336             pw.println(prefix + "    " + i++ + ": " + entry.getKey());
    337             entry.getValue().dump(pw, prefix + "      ");
    338         }
    339     }
    340 
    341     private static final class InputDeviceState {
    342         private static final String[] CALIBRATION_NAME = { "x_scale",
    343                 "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
    344 
    345         private TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
    346         @Nullable
    347         private String mCurrentKeyboardLayout;
    348         private List<String> mUnassociatedKeyboardLayouts = new ArrayList<>();
    349         private ArrayMap<InputMethodSubtypeHandle, String> mKeyboardLayouts = new ArrayMap<>();
    350 
    351         public TouchCalibration getTouchCalibration(int surfaceRotation) {
    352             try {
    353                 return mTouchCalibration[surfaceRotation];
    354             } catch (ArrayIndexOutOfBoundsException ex) {
    355                 Slog.w(InputManagerService.TAG, "Cannot get touch calibration.", ex);
    356                 return null;
    357             }
    358         }
    359 
    360         public boolean setTouchCalibration(int surfaceRotation, TouchCalibration calibration) {
    361             try {
    362                 if (!calibration.equals(mTouchCalibration[surfaceRotation])) {
    363                     mTouchCalibration[surfaceRotation] = calibration;
    364                     return true;
    365                 }
    366                 return false;
    367             } catch (ArrayIndexOutOfBoundsException ex) {
    368                 Slog.w(InputManagerService.TAG, "Cannot set touch calibration.", ex);
    369                 return false;
    370             }
    371         }
    372 
    373         @Nullable
    374         public String getCurrentKeyboardLayout() {
    375             return mCurrentKeyboardLayout;
    376         }
    377 
    378         public boolean setCurrentKeyboardLayout(String keyboardLayout) {
    379             if (Objects.equal(mCurrentKeyboardLayout, keyboardLayout)) {
    380                 return false;
    381             }
    382             addKeyboardLayout(keyboardLayout);
    383             mCurrentKeyboardLayout = keyboardLayout;
    384             return true;
    385         }
    386 
    387         public String[] getKeyboardLayouts() {
    388             if (mUnassociatedKeyboardLayouts.isEmpty()) {
    389                 return (String[])ArrayUtils.emptyArray(String.class);
    390             }
    391             return mUnassociatedKeyboardLayouts.toArray(
    392                     new String[mUnassociatedKeyboardLayouts.size()]);
    393         }
    394 
    395         public String getKeyboardLayout(InputMethodSubtypeHandle handle) {
    396             return mKeyboardLayouts.get(handle);
    397         }
    398 
    399         public boolean setKeyboardLayout(InputMethodSubtypeHandle imeHandle,
    400                 String keyboardLayout) {
    401             String existingLayout = mKeyboardLayouts.get(imeHandle);
    402             if (TextUtils.equals(existingLayout, keyboardLayout)) {
    403                 return false;
    404             }
    405             mKeyboardLayouts.put(imeHandle, keyboardLayout);
    406             return true;
    407         }
    408 
    409         public boolean addKeyboardLayout(String keyboardLayout) {
    410             int index = Collections.binarySearch(
    411                     mUnassociatedKeyboardLayouts, keyboardLayout);
    412             if (index >= 0) {
    413                 return false;
    414             }
    415             mUnassociatedKeyboardLayouts.add(-index - 1, keyboardLayout);
    416             if (mCurrentKeyboardLayout == null) {
    417                 mCurrentKeyboardLayout = keyboardLayout;
    418             }
    419             return true;
    420         }
    421 
    422         public boolean removeKeyboardLayout(String keyboardLayout) {
    423             int index = Collections.binarySearch(mUnassociatedKeyboardLayouts, keyboardLayout);
    424             if (index < 0) {
    425                 return false;
    426             }
    427             mUnassociatedKeyboardLayouts.remove(index);
    428             updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
    429             return true;
    430         }
    431 
    432         private void updateCurrentKeyboardLayoutIfRemoved(
    433                 String removedKeyboardLayout, int removedIndex) {
    434             if (Objects.equal(mCurrentKeyboardLayout, removedKeyboardLayout)) {
    435                 if (!mUnassociatedKeyboardLayouts.isEmpty()) {
    436                     int index = removedIndex;
    437                     if (index == mUnassociatedKeyboardLayouts.size()) {
    438                         index = 0;
    439                     }
    440                     mCurrentKeyboardLayout = mUnassociatedKeyboardLayouts.get(index);
    441                 } else {
    442                     mCurrentKeyboardLayout = null;
    443                 }
    444             }
    445         }
    446 
    447         public boolean switchKeyboardLayout(InputMethodSubtypeHandle imeHandle) {
    448             final String layout = mKeyboardLayouts.get(imeHandle);
    449             if (!TextUtils.equals(mCurrentKeyboardLayout, layout)) {
    450                 mCurrentKeyboardLayout = layout;
    451                 return true;
    452             }
    453             return false;
    454         }
    455 
    456         public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
    457             boolean changed = false;
    458             for (int i = mUnassociatedKeyboardLayouts.size(); i-- > 0; ) {
    459                 String keyboardLayout = mUnassociatedKeyboardLayouts.get(i);
    460                 if (!availableKeyboardLayouts.contains(keyboardLayout)) {
    461                     Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
    462                     mUnassociatedKeyboardLayouts.remove(i);
    463                     updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
    464                     changed = true;
    465                 }
    466             }
    467             return changed;
    468         }
    469 
    470         public void loadFromXml(XmlPullParser parser)
    471                 throws IOException, XmlPullParserException {
    472             final int outerDepth = parser.getDepth();
    473             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
    474                 if (parser.getName().equals("keyboard-layout")) {
    475                     String descriptor = parser.getAttributeValue(null, "descriptor");
    476                     if (descriptor == null) {
    477                         throw new XmlPullParserException(
    478                                 "Missing descriptor attribute on keyboard-layout.");
    479                     }
    480 
    481                     String current = parser.getAttributeValue(null, "current");
    482                     if (current != null && current.equals("true")) {
    483                         if (mCurrentKeyboardLayout != null) {
    484                             throw new XmlPullParserException(
    485                                     "Found multiple current keyboard layouts.");
    486                         }
    487                         mCurrentKeyboardLayout = descriptor;
    488                     }
    489 
    490                     String inputMethodId = parser.getAttributeValue(null, "input-method-id");
    491                     String inputMethodSubtypeId =
    492                         parser.getAttributeValue(null, "input-method-subtype-id");
    493                     if (inputMethodId == null && inputMethodSubtypeId != null
    494                             || inputMethodId != null && inputMethodSubtypeId == null) {
    495                         throw new XmlPullParserException(
    496                                 "Found an incomplete input method description");
    497                     }
    498 
    499                     if (inputMethodSubtypeId != null) {
    500                         InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(
    501                                 inputMethodId, Integer.parseInt(inputMethodSubtypeId));
    502                         if (mKeyboardLayouts.containsKey(handle)) {
    503                             throw new XmlPullParserException(
    504                                     "Found duplicate subtype to keyboard layout mapping: "
    505                                     + handle);
    506                         }
    507                         mKeyboardLayouts.put(handle, descriptor);
    508                     } else {
    509                         if (mUnassociatedKeyboardLayouts.contains(descriptor)) {
    510                             throw new XmlPullParserException(
    511                                     "Found duplicate unassociated keyboard layout: " + descriptor);
    512                         }
    513                         mUnassociatedKeyboardLayouts.add(descriptor);
    514                     }
    515                 } else if (parser.getName().equals("calibration")) {
    516                     String format = parser.getAttributeValue(null, "format");
    517                     String rotation = parser.getAttributeValue(null, "rotation");
    518                     int r = -1;
    519 
    520                     if (format == null) {
    521                         throw new XmlPullParserException(
    522                                 "Missing format attribute on calibration.");
    523                     }
    524                     if (!format.equals("affine")) {
    525                         throw new XmlPullParserException(
    526                                 "Unsupported format for calibration.");
    527                     }
    528                     if (rotation != null) {
    529                         try {
    530                             r = stringToSurfaceRotation(rotation);
    531                         } catch (IllegalArgumentException e) {
    532                             throw new XmlPullParserException(
    533                                     "Unsupported rotation for calibration.");
    534                         }
    535                     }
    536 
    537                     float[] matrix = TouchCalibration.IDENTITY.getAffineTransform();
    538                     int depth = parser.getDepth();
    539                     while (XmlUtils.nextElementWithin(parser, depth)) {
    540                         String tag = parser.getName().toLowerCase();
    541                         String value = parser.nextText();
    542 
    543                         for (int i = 0; i < matrix.length && i < CALIBRATION_NAME.length; i++) {
    544                             if (tag.equals(CALIBRATION_NAME[i])) {
    545                                 matrix[i] = Float.parseFloat(value);
    546                                 break;
    547                             }
    548                         }
    549                     }
    550 
    551                     if (r == -1) {
    552                         // Assume calibration applies to all rotations
    553                         for (r = 0; r < mTouchCalibration.length; r++) {
    554                             mTouchCalibration[r] = new TouchCalibration(matrix[0],
    555                                 matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
    556                         }
    557                     } else {
    558                         mTouchCalibration[r] = new TouchCalibration(matrix[0],
    559                             matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
    560                     }
    561                 }
    562             }
    563 
    564             // Maintain invariant that layouts are sorted.
    565             Collections.sort(mUnassociatedKeyboardLayouts);
    566 
    567             // Maintain invariant that there is always a current keyboard layout unless
    568             // there are none installed.
    569             if (mCurrentKeyboardLayout == null && !mUnassociatedKeyboardLayouts.isEmpty()) {
    570                 mCurrentKeyboardLayout = mUnassociatedKeyboardLayouts.get(0);
    571             }
    572         }
    573 
    574         public void saveToXml(XmlSerializer serializer) throws IOException {
    575             for (String layout : mUnassociatedKeyboardLayouts) {
    576                 serializer.startTag(null, "keyboard-layout");
    577                 serializer.attribute(null, "descriptor", layout);
    578                 serializer.endTag(null, "keyboard-layout");
    579             }
    580 
    581             final int N = mKeyboardLayouts.size();
    582             for (int i = 0; i < N; i++) {
    583                 final InputMethodSubtypeHandle handle = mKeyboardLayouts.keyAt(i);
    584                 final String layout = mKeyboardLayouts.valueAt(i);
    585                 serializer.startTag(null, "keyboard-layout");
    586                 serializer.attribute(null, "descriptor", layout);
    587                 serializer.attribute(null, "input-method-id", handle.getInputMethodId());
    588                 serializer.attribute(null, "input-method-subtype-id",
    589                         Integer.toString(handle.getSubtypeId()));
    590                 if (layout.equals(mCurrentKeyboardLayout)) {
    591                     serializer.attribute(null, "current", "true");
    592                 }
    593                 serializer.endTag(null, "keyboard-layout");
    594             }
    595 
    596             for (int i = 0; i < mTouchCalibration.length; i++) {
    597                 if (mTouchCalibration[i] != null) {
    598                     String rotation = surfaceRotationToString(i);
    599                     float[] transform = mTouchCalibration[i].getAffineTransform();
    600 
    601                     serializer.startTag(null, "calibration");
    602                     serializer.attribute(null, "format", "affine");
    603                     serializer.attribute(null, "rotation", rotation);
    604                     for (int j = 0; j < transform.length && j < CALIBRATION_NAME.length; j++) {
    605                         serializer.startTag(null, CALIBRATION_NAME[j]);
    606                         serializer.text(Float.toString(transform[j]));
    607                         serializer.endTag(null, CALIBRATION_NAME[j]);
    608                     }
    609                     serializer.endTag(null, "calibration");
    610                 }
    611             }
    612         }
    613 
    614         private void dump(final PrintWriter pw, final String prefix) {
    615             pw.println(prefix + "CurrentKeyboardLayout=" + mCurrentKeyboardLayout);
    616             pw.println(prefix + "UnassociatedKeyboardLayouts=" + mUnassociatedKeyboardLayouts);
    617             pw.println(prefix + "TouchCalibration=" + Arrays.toString(mTouchCalibration));
    618             pw.println(prefix + "Subtype to Layout Mappings:");
    619             final int N = mKeyboardLayouts.size();
    620             if (N != 0) {
    621                 for (int i = 0; i < N; i++) {
    622                     pw.println(prefix + "  " + mKeyboardLayouts.keyAt(i) + ": "
    623                             + mKeyboardLayouts.valueAt(i));
    624                 }
    625             } else {
    626                 pw.println(prefix + "  <none>");
    627             }
    628         }
    629 
    630         private static String surfaceRotationToString(int surfaceRotation) {
    631             switch (surfaceRotation) {
    632                 case Surface.ROTATION_0:   return "0";
    633                 case Surface.ROTATION_90:  return "90";
    634                 case Surface.ROTATION_180: return "180";
    635                 case Surface.ROTATION_270: return "270";
    636             }
    637             throw new IllegalArgumentException("Unsupported surface rotation value" + surfaceRotation);
    638         }
    639 
    640         private static int stringToSurfaceRotation(String s) {
    641             if ("0".equals(s)) {
    642                 return Surface.ROTATION_0;
    643             }
    644             if ("90".equals(s)) {
    645                 return Surface.ROTATION_90;
    646             }
    647             if ("180".equals(s)) {
    648                 return Surface.ROTATION_180;
    649             }
    650             if ("270".equals(s)) {
    651                 return Surface.ROTATION_270;
    652             }
    653             throw new IllegalArgumentException("Unsupported surface rotation string '" + s + "'");
    654         }
    655     }
    656 }
    657