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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.hardware.input.TouchCalibration;
22 import android.util.ArrayMap;
23 import android.util.AtomicFile;
24 import android.util.Slog;
25 import android.util.SparseIntArray;
26 import android.util.Xml;
27 import android.view.Surface;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.internal.util.ArrayUtils;
31 import com.android.internal.util.XmlUtils;
32 import com.android.modules.utils.TypedXmlPullParser;
33 import com.android.modules.utils.TypedXmlSerializer;
34 
35 import libcore.io.IoUtils;
36 
37 import org.xmlpull.v1.XmlPullParserException;
38 
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.util.ArrayList;
45 import java.util.Collections;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Objects;
51 import java.util.OptionalInt;
52 import java.util.Set;
53 
54 /**
55  * Manages persistent state recorded by the input manager service as an XML file.
56  * Caller must acquire lock on the data store before accessing it.
57  *
58  * File format:
59  * <code>
60  * &lt;input-mananger-state>
61  *   &lt;input-devices>
62  *     &lt;input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
63  *   &gt;input-devices>
64  * &gt;/input-manager-state>
65  * </code>
66  */
67 final class PersistentDataStore {
68     static final String TAG = "InputManager";
69 
70     private static final int INVALID_VALUE = -1;
71 
72     // Input device state by descriptor.
73     private final HashMap<String, InputDeviceState> mInputDevices =
74             new HashMap<String, InputDeviceState>();
75 
76     // The interface for methods which should be replaced by the test harness.
77     private Injector mInjector;
78 
79     // True if the data has been loaded.
80     private boolean mLoaded;
81 
82     // True if there are changes to be saved.
83     private boolean mDirty;
84 
85     // Storing key remapping
86     private Map<Integer, Integer> mKeyRemapping = new HashMap<>();
87 
PersistentDataStore()88     public PersistentDataStore() {
89         this(new Injector());
90     }
91 
92     @VisibleForTesting
PersistentDataStore(Injector injector)93     PersistentDataStore(Injector injector) {
94         mInjector = injector;
95     }
96 
saveIfNeeded()97     public void saveIfNeeded() {
98         if (mDirty) {
99             save();
100             mDirty = false;
101         }
102     }
103 
hasInputDeviceEntry(String inputDeviceDescriptor)104     public boolean hasInputDeviceEntry(String inputDeviceDescriptor) {
105         return getInputDeviceState(inputDeviceDescriptor) != null;
106     }
107 
getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation)108     public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
109         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
110         if (state == null) {
111             return TouchCalibration.IDENTITY;
112         }
113 
114         TouchCalibration cal = state.getTouchCalibration(surfaceRotation);
115         if (cal == null) {
116             return TouchCalibration.IDENTITY;
117         }
118         return cal;
119     }
120 
setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation, TouchCalibration calibration)121     public boolean setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation, TouchCalibration calibration) {
122         InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
123 
124         if (state.setTouchCalibration(surfaceRotation, calibration)) {
125             setDirty();
126             return true;
127         }
128 
129         return false;
130     }
131 
132     @Nullable
getCurrentKeyboardLayout(String inputDeviceDescriptor)133     public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
134         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
135         return state != null ? state.getCurrentKeyboardLayout() : null;
136     }
137 
setCurrentKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor)138     public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
139             String keyboardLayoutDescriptor) {
140         InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
141         if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
142             setDirty();
143             return true;
144         }
145         return false;
146     }
147 
148     @Nullable
getKeyboardLayout(String inputDeviceDescriptor, String key)149     public String getKeyboardLayout(String inputDeviceDescriptor, String key) {
150         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
151         return state != null ? state.getKeyboardLayout(key) : null;
152     }
153 
setKeyboardLayout(String inputDeviceDescriptor, String key, String keyboardLayoutDescriptor)154     public boolean setKeyboardLayout(String inputDeviceDescriptor, String key,
155             String keyboardLayoutDescriptor) {
156         InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
157         if (state.setKeyboardLayout(key, keyboardLayoutDescriptor)) {
158             setDirty();
159             return true;
160         }
161         return false;
162     }
163 
setSelectedKeyboardLayouts(String inputDeviceDescriptor, @NonNull Set<String> selectedLayouts)164     public boolean setSelectedKeyboardLayouts(String inputDeviceDescriptor,
165             @NonNull Set<String> selectedLayouts) {
166         InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
167         if (state.setSelectedKeyboardLayouts(selectedLayouts)) {
168             setDirty();
169             return true;
170         }
171         return false;
172     }
173 
getKeyboardLayouts(String inputDeviceDescriptor)174     public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
175         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
176         if (state == null) {
177             return (String[])ArrayUtils.emptyArray(String.class);
178         }
179         return state.getKeyboardLayouts();
180     }
181 
addKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor)182     public boolean addKeyboardLayout(String inputDeviceDescriptor,
183             String keyboardLayoutDescriptor) {
184         InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
185         if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
186             setDirty();
187             return true;
188         }
189         return false;
190     }
191 
removeKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor)192     public boolean removeKeyboardLayout(String inputDeviceDescriptor,
193             String keyboardLayoutDescriptor) {
194         InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
195         if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
196             setDirty();
197             return true;
198         }
199         return false;
200     }
201 
switchKeyboardLayout(String inputDeviceDescriptor, int direction)202     public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
203         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
204         if (state != null && state.switchKeyboardLayout(direction)) {
205             setDirty();
206             return true;
207         }
208         return false;
209     }
210 
setKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId, int brightness)211     public boolean setKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId,
212             int brightness) {
213         InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
214         if (state.setKeyboardBacklightBrightness(lightId, brightness)) {
215             setDirty();
216             return true;
217         }
218         return false;
219     }
220 
getKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId)221     public OptionalInt getKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId) {
222         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
223         if (state == null) {
224             return OptionalInt.empty();
225         }
226         return state.getKeyboardBacklightBrightness(lightId);
227     }
228 
remapKey(int fromKey, int toKey)229     public boolean remapKey(int fromKey, int toKey) {
230         loadIfNeeded();
231         if (mKeyRemapping.getOrDefault(fromKey, INVALID_VALUE) == toKey) {
232             return false;
233         }
234         mKeyRemapping.put(fromKey, toKey);
235         setDirty();
236         return true;
237     }
238 
clearMappedKey(int key)239     public boolean clearMappedKey(int key) {
240         loadIfNeeded();
241         if (mKeyRemapping.containsKey(key)) {
242             mKeyRemapping.remove(key);
243             setDirty();
244         }
245         return true;
246     }
247 
getKeyRemapping()248     public Map<Integer, Integer> getKeyRemapping() {
249         loadIfNeeded();
250         return new HashMap<>(mKeyRemapping);
251     }
252 
removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts)253     public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
254         boolean changed = false;
255         for (InputDeviceState state : mInputDevices.values()) {
256             if (state.removeUninstalledKeyboardLayouts(availableKeyboardLayouts)) {
257                 changed = true;
258             }
259         }
260         if (changed) {
261             setDirty();
262             return true;
263         }
264         return false;
265     }
266 
getInputDeviceState(String inputDeviceDescriptor)267     private InputDeviceState getInputDeviceState(String inputDeviceDescriptor) {
268         loadIfNeeded();
269         return mInputDevices.get(inputDeviceDescriptor);
270     }
271 
getOrCreateInputDeviceState(String inputDeviceDescriptor)272     private InputDeviceState getOrCreateInputDeviceState(String inputDeviceDescriptor) {
273         loadIfNeeded();
274         InputDeviceState state = mInputDevices.get(inputDeviceDescriptor);
275         if (state == null) {
276             state = new InputDeviceState();
277             mInputDevices.put(inputDeviceDescriptor, state);
278             setDirty();
279         }
280         return state;
281     }
282 
loadIfNeeded()283     private void loadIfNeeded() {
284         if (!mLoaded) {
285             load();
286             mLoaded = true;
287         }
288     }
289 
setDirty()290     private void setDirty() {
291         mDirty = true;
292     }
293 
clearState()294     private void clearState() {
295         mKeyRemapping.clear();
296         mInputDevices.clear();
297     }
298 
load()299     private void load() {
300         clearState();
301 
302         final InputStream is;
303         try {
304             is = mInjector.openRead();
305         } catch (FileNotFoundException ex) {
306             return;
307         }
308 
309         TypedXmlPullParser parser;
310         try {
311             parser = Xml.resolvePullParser(is);
312             loadFromXml(parser);
313         } catch (IOException ex) {
314             Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
315             clearState();
316         } catch (XmlPullParserException ex) {
317             Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
318             clearState();
319         } finally {
320             IoUtils.closeQuietly(is);
321         }
322     }
323 
save()324     private void save() {
325         final FileOutputStream os;
326         try {
327             os = mInjector.startWrite();
328             boolean success = false;
329             try {
330                 TypedXmlSerializer serializer = Xml.resolveSerializer(os);
331                 saveToXml(serializer);
332                 serializer.flush();
333                 success = true;
334             } finally {
335                 mInjector.finishWrite(os, success);
336             }
337         } catch (IOException ex) {
338             Slog.w(InputManagerService.TAG, "Failed to save input manager persistent store data.", ex);
339         }
340     }
341 
loadFromXml(TypedXmlPullParser parser)342     private void loadFromXml(TypedXmlPullParser parser)
343             throws IOException, XmlPullParserException {
344         XmlUtils.beginDocument(parser, "input-manager-state");
345         final int outerDepth = parser.getDepth();
346         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
347             if (parser.getName().equals("key-remapping")) {
348                 loadKeyRemappingFromXml(parser);
349             } else if (parser.getName().equals("input-devices")) {
350                 loadInputDevicesFromXml(parser);
351             }
352         }
353     }
354 
loadInputDevicesFromXml(TypedXmlPullParser parser)355     private void loadInputDevicesFromXml(TypedXmlPullParser parser)
356             throws IOException, XmlPullParserException {
357         final int outerDepth = parser.getDepth();
358         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
359             if (parser.getName().equals("input-device")) {
360                 String descriptor = parser.getAttributeValue(null, "descriptor");
361                 if (descriptor == null) {
362                     throw new XmlPullParserException(
363                             "Missing descriptor attribute on input-device.");
364                 }
365                 if (mInputDevices.containsKey(descriptor)) {
366                     throw new XmlPullParserException("Found duplicate input device.");
367                 }
368 
369                 InputDeviceState state = new InputDeviceState();
370                 state.loadFromXml(parser);
371                 mInputDevices.put(descriptor, state);
372             }
373         }
374     }
375 
loadKeyRemappingFromXml(TypedXmlPullParser parser)376     private void loadKeyRemappingFromXml(TypedXmlPullParser parser)
377             throws IOException, XmlPullParserException {
378         final int outerDepth = parser.getDepth();
379         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
380             if (parser.getName().equals("remap")) {
381                 int fromKey = parser.getAttributeInt(null, "from-key");
382                 int toKey = parser.getAttributeInt(null, "to-key");
383                 mKeyRemapping.put(fromKey, toKey);
384             }
385         }
386     }
387 
saveToXml(TypedXmlSerializer serializer)388     private void saveToXml(TypedXmlSerializer serializer) throws IOException {
389         serializer.startDocument(null, true);
390         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
391         serializer.startTag(null, "input-manager-state");
392         serializer.startTag(null, "key-remapping");
393         for (int fromKey : mKeyRemapping.keySet()) {
394             int toKey = mKeyRemapping.get(fromKey);
395             serializer.startTag(null, "remap");
396             serializer.attributeInt(null, "from-key", fromKey);
397             serializer.attributeInt(null, "to-key", toKey);
398             serializer.endTag(null, "remap");
399         }
400         serializer.endTag(null, "key-remapping");
401         serializer.startTag(null, "input-devices");
402         for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
403             final String descriptor = entry.getKey();
404             final InputDeviceState state = entry.getValue();
405             serializer.startTag(null, "input-device");
406             serializer.attribute(null, "descriptor", descriptor);
407             state.saveToXml(serializer);
408             serializer.endTag(null, "input-device");
409         }
410         serializer.endTag(null, "input-devices");
411         serializer.endTag(null, "input-manager-state");
412         serializer.endDocument();
413     }
414 
415     private static final class InputDeviceState {
416         private static final String[] CALIBRATION_NAME = { "x_scale",
417                 "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
418 
419         private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
420         @Nullable
421         private String mCurrentKeyboardLayout;
422         private final ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
423         private final SparseIntArray mKeyboardBacklightBrightnessMap = new SparseIntArray();
424 
425         private final Map<String, String> mKeyboardLayoutMap = new ArrayMap<>();
426 
427         private Set<String> mSelectedKeyboardLayouts;
428 
getTouchCalibration(int surfaceRotation)429         public TouchCalibration getTouchCalibration(int surfaceRotation) {
430             try {
431                 return mTouchCalibration[surfaceRotation];
432             } catch (ArrayIndexOutOfBoundsException ex) {
433                 Slog.w(InputManagerService.TAG, "Cannot get touch calibration.", ex);
434                 return null;
435             }
436         }
437 
setTouchCalibration(int surfaceRotation, TouchCalibration calibration)438         public boolean setTouchCalibration(int surfaceRotation, TouchCalibration calibration) {
439             try {
440                 if (!calibration.equals(mTouchCalibration[surfaceRotation])) {
441                     mTouchCalibration[surfaceRotation] = calibration;
442                     return true;
443                 }
444                 return false;
445             } catch (ArrayIndexOutOfBoundsException ex) {
446                 Slog.w(InputManagerService.TAG, "Cannot set touch calibration.", ex);
447                 return false;
448             }
449         }
450 
451         @Nullable
getKeyboardLayout(String key)452         public String getKeyboardLayout(String key) {
453             return mKeyboardLayoutMap.get(key);
454         }
455 
setKeyboardLayout(String key, String keyboardLayout)456         public boolean setKeyboardLayout(String key, String keyboardLayout) {
457             return !Objects.equals(mKeyboardLayoutMap.put(key, keyboardLayout), keyboardLayout);
458         }
459 
setSelectedKeyboardLayouts(@onNull Set<String> selectedLayouts)460         public boolean setSelectedKeyboardLayouts(@NonNull Set<String> selectedLayouts) {
461             if (Objects.equals(mSelectedKeyboardLayouts, selectedLayouts)) {
462                 return false;
463             }
464             mSelectedKeyboardLayouts = new HashSet<>(selectedLayouts);
465             return true;
466         }
467 
468         @Nullable
getCurrentKeyboardLayout()469         public String getCurrentKeyboardLayout() {
470             return mCurrentKeyboardLayout;
471         }
472 
setCurrentKeyboardLayout(String keyboardLayout)473         public boolean setCurrentKeyboardLayout(String keyboardLayout) {
474             if (Objects.equals(mCurrentKeyboardLayout, keyboardLayout)) {
475                 return false;
476             }
477             addKeyboardLayout(keyboardLayout);
478             mCurrentKeyboardLayout = keyboardLayout;
479             return true;
480         }
481 
getKeyboardLayouts()482         public String[] getKeyboardLayouts() {
483             if (mKeyboardLayouts.isEmpty()) {
484                 return (String[])ArrayUtils.emptyArray(String.class);
485             }
486             return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
487         }
488 
addKeyboardLayout(String keyboardLayout)489         public boolean addKeyboardLayout(String keyboardLayout) {
490             int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
491             if (index >= 0) {
492                 return false;
493             }
494             mKeyboardLayouts.add(-index - 1, keyboardLayout);
495             if (mCurrentKeyboardLayout == null) {
496                 mCurrentKeyboardLayout = keyboardLayout;
497             }
498             return true;
499         }
500 
removeKeyboardLayout(String keyboardLayout)501         public boolean removeKeyboardLayout(String keyboardLayout) {
502             int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
503             if (index < 0) {
504                 return false;
505             }
506             mKeyboardLayouts.remove(index);
507             updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
508             return true;
509         }
510 
setKeyboardBacklightBrightness(int lightId, int brightness)511         public boolean setKeyboardBacklightBrightness(int lightId, int brightness) {
512             if (mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE) == brightness) {
513                 return false;
514             }
515             mKeyboardBacklightBrightnessMap.put(lightId, brightness);
516             return true;
517         }
518 
getKeyboardBacklightBrightness(int lightId)519         public OptionalInt getKeyboardBacklightBrightness(int lightId) {
520             int brightness = mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE);
521             return brightness == INVALID_VALUE ? OptionalInt.empty() : OptionalInt.of(brightness);
522         }
523 
updateCurrentKeyboardLayoutIfRemoved( String removedKeyboardLayout, int removedIndex)524         private void updateCurrentKeyboardLayoutIfRemoved(
525                 String removedKeyboardLayout, int removedIndex) {
526             if (Objects.equals(mCurrentKeyboardLayout, removedKeyboardLayout)) {
527                 if (!mKeyboardLayouts.isEmpty()) {
528                     int index = removedIndex;
529                     if (index == mKeyboardLayouts.size()) {
530                         index = 0;
531                     }
532                     mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
533                 } else {
534                     mCurrentKeyboardLayout = null;
535                 }
536             }
537         }
538 
switchKeyboardLayout(int direction)539         public boolean switchKeyboardLayout(int direction) {
540             final int size = mKeyboardLayouts.size();
541             if (size < 2) {
542                 return false;
543             }
544             int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
545             assert index >= 0;
546             if (direction > 0) {
547                 index = (index + 1) % size;
548             } else {
549                 index = (index + size - 1) % size;
550             }
551             mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
552             return true;
553         }
554 
removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts)555         public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
556             boolean changed = false;
557             for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
558                 String keyboardLayout = mKeyboardLayouts.get(i);
559                 if (!availableKeyboardLayouts.contains(keyboardLayout)) {
560                     Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
561                     mKeyboardLayouts.remove(i);
562                     updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
563                     changed = true;
564                 }
565             }
566             List<String> removedEntries = new ArrayList<>();
567             for (String key : mKeyboardLayoutMap.keySet()) {
568                 if (!availableKeyboardLayouts.contains(mKeyboardLayoutMap.get(key))) {
569                     removedEntries.add(key);
570                 }
571             }
572             if (!removedEntries.isEmpty()) {
573                 for (String key : removedEntries) {
574                     mKeyboardLayoutMap.remove(key);
575                 }
576                 changed = true;
577             }
578             return changed;
579         }
580 
loadFromXml(TypedXmlPullParser parser)581         public void loadFromXml(TypedXmlPullParser parser)
582                 throws IOException, XmlPullParserException {
583             final int outerDepth = parser.getDepth();
584             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
585                 if (parser.getName().equals("keyboard-layout")) {
586                     String descriptor = parser.getAttributeValue(null, "descriptor");
587                     if (descriptor == null) {
588                         throw new XmlPullParserException(
589                                 "Missing descriptor attribute on keyboard-layout.");
590                     }
591                     String current = parser.getAttributeValue(null, "current");
592                     if (mKeyboardLayouts.contains(descriptor)) {
593                         throw new XmlPullParserException(
594                                 "Found duplicate keyboard layout.");
595                     }
596 
597                     mKeyboardLayouts.add(descriptor);
598                     if (current != null && current.equals("true")) {
599                         if (mCurrentKeyboardLayout != null) {
600                             throw new XmlPullParserException(
601                                     "Found multiple current keyboard layouts.");
602                         }
603                         mCurrentKeyboardLayout = descriptor;
604                     }
605                 } else if (parser.getName().equals("keyed-keyboard-layout")) {
606                     String key = parser.getAttributeValue(null, "key");
607                     if (key == null) {
608                         throw new XmlPullParserException(
609                                 "Missing key attribute on keyed-keyboard-layout.");
610                     }
611                     String layout = parser.getAttributeValue(null, "layout");
612                     if (layout == null) {
613                         throw new XmlPullParserException(
614                                 "Missing layout attribute on keyed-keyboard-layout.");
615                     }
616                     mKeyboardLayoutMap.put(key, layout);
617                 } else if (parser.getName().equals("selected-keyboard-layout")) {
618                     String layout = parser.getAttributeValue(null, "layout");
619                     if (layout == null) {
620                         throw new XmlPullParserException(
621                                 "Missing layout attribute on selected-keyboard-layout.");
622                     }
623                     if (mSelectedKeyboardLayouts == null) {
624                         mSelectedKeyboardLayouts = new HashSet<>();
625                     }
626                     mSelectedKeyboardLayouts.add(layout);
627                 } else if (parser.getName().equals("light-info")) {
628                     int lightId = parser.getAttributeInt(null, "light-id");
629                     int lightBrightness = parser.getAttributeInt(null, "light-brightness");
630                     mKeyboardBacklightBrightnessMap.put(lightId, lightBrightness);
631                 } else if (parser.getName().equals("calibration")) {
632                     String format = parser.getAttributeValue(null, "format");
633                     String rotation = parser.getAttributeValue(null, "rotation");
634                     int r = -1;
635 
636                     if (format == null) {
637                         throw new XmlPullParserException(
638                                 "Missing format attribute on calibration.");
639                     }
640                     if (!format.equals("affine")) {
641                         throw new XmlPullParserException(
642                                 "Unsupported format for calibration.");
643                     }
644                     if (rotation != null) {
645                         try {
646                             r = stringToSurfaceRotation(rotation);
647                         } catch (IllegalArgumentException e) {
648                             throw new XmlPullParserException(
649                                     "Unsupported rotation for calibration.");
650                         }
651                     }
652 
653                     float[] matrix = TouchCalibration.IDENTITY.getAffineTransform();
654                     int depth = parser.getDepth();
655                     while (XmlUtils.nextElementWithin(parser, depth)) {
656                         String tag = parser.getName().toLowerCase();
657                         String value = parser.nextText();
658 
659                         for (int i = 0; i < matrix.length && i < CALIBRATION_NAME.length; i++) {
660                             if (tag.equals(CALIBRATION_NAME[i])) {
661                                 matrix[i] = Float.parseFloat(value);
662                                 break;
663                             }
664                         }
665                     }
666 
667                     if (r == -1) {
668                         // Assume calibration applies to all rotations
669                         for (r = 0; r < mTouchCalibration.length; r++) {
670                             mTouchCalibration[r] = new TouchCalibration(matrix[0],
671                                 matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
672                         }
673                     } else {
674                         mTouchCalibration[r] = new TouchCalibration(matrix[0],
675                             matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
676                     }
677                 }
678             }
679 
680             // Maintain invariant that layouts are sorted.
681             Collections.sort(mKeyboardLayouts);
682 
683             // Maintain invariant that there is always a current keyboard layout unless
684             // there are none installed.
685             if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
686                 mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
687             }
688         }
689 
saveToXml(TypedXmlSerializer serializer)690         public void saveToXml(TypedXmlSerializer serializer) throws IOException {
691             for (String layout : mKeyboardLayouts) {
692                 serializer.startTag(null, "keyboard-layout");
693                 serializer.attribute(null, "descriptor", layout);
694                 if (layout.equals(mCurrentKeyboardLayout)) {
695                     serializer.attributeBoolean(null, "current", true);
696                 }
697                 serializer.endTag(null, "keyboard-layout");
698             }
699 
700             for (String key : mKeyboardLayoutMap.keySet()) {
701                 serializer.startTag(null, "keyed-keyboard-layout");
702                 serializer.attribute(null, "key", key);
703                 serializer.attribute(null, "layout", mKeyboardLayoutMap.get(key));
704                 serializer.endTag(null, "keyed-keyboard-layout");
705             }
706 
707             if (mSelectedKeyboardLayouts != null) {
708                 for (String layout : mSelectedKeyboardLayouts) {
709                     serializer.startTag(null, "selected-keyboard-layout");
710                     serializer.attribute(null, "layout", layout);
711                     serializer.endTag(null, "selected-keyboard-layout");
712                 }
713             }
714 
715             for (int i = 0; i < mKeyboardBacklightBrightnessMap.size(); i++) {
716                 serializer.startTag(null, "light-info");
717                 serializer.attributeInt(null, "light-id", mKeyboardBacklightBrightnessMap.keyAt(i));
718                 serializer.attributeInt(null, "light-brightness",
719                         mKeyboardBacklightBrightnessMap.valueAt(i));
720                 serializer.endTag(null, "light-info");
721             }
722 
723             for (int i = 0; i < mTouchCalibration.length; i++) {
724                 if (mTouchCalibration[i] != null) {
725                     String rotation = surfaceRotationToString(i);
726                     float[] transform = mTouchCalibration[i].getAffineTransform();
727 
728                     serializer.startTag(null, "calibration");
729                     serializer.attribute(null, "format", "affine");
730                     serializer.attribute(null, "rotation", rotation);
731                     for (int j = 0; j < transform.length && j < CALIBRATION_NAME.length; j++) {
732                         serializer.startTag(null, CALIBRATION_NAME[j]);
733                         serializer.text(Float.toString(transform[j]));
734                         serializer.endTag(null, CALIBRATION_NAME[j]);
735                     }
736                     serializer.endTag(null, "calibration");
737                 }
738             }
739         }
740 
surfaceRotationToString(int surfaceRotation)741         private static String surfaceRotationToString(int surfaceRotation) {
742             switch (surfaceRotation) {
743                 case Surface.ROTATION_0:   return "0";
744                 case Surface.ROTATION_90:  return "90";
745                 case Surface.ROTATION_180: return "180";
746                 case Surface.ROTATION_270: return "270";
747             }
748             throw new IllegalArgumentException("Unsupported surface rotation value" + surfaceRotation);
749         }
750 
stringToSurfaceRotation(String s)751         private static int stringToSurfaceRotation(String s) {
752             if ("0".equals(s)) {
753                 return Surface.ROTATION_0;
754             }
755             if ("90".equals(s)) {
756                 return Surface.ROTATION_90;
757             }
758             if ("180".equals(s)) {
759                 return Surface.ROTATION_180;
760             }
761             if ("270".equals(s)) {
762                 return Surface.ROTATION_270;
763             }
764             throw new IllegalArgumentException("Unsupported surface rotation string '" + s + "'");
765         }
766     }
767 
768     @VisibleForTesting
769     static class Injector {
770         private final AtomicFile mAtomicFile;
771 
Injector()772         Injector() {
773             mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"),
774                     "input-state");
775         }
776 
openRead()777         InputStream openRead() throws FileNotFoundException {
778             return mAtomicFile.openRead();
779         }
780 
startWrite()781         FileOutputStream startWrite() throws IOException {
782             return mAtomicFile.startWrite();
783         }
784 
finishWrite(FileOutputStream fos, boolean success)785         void finishWrite(FileOutputStream fos, boolean success) {
786             if (success) {
787                 mAtomicFile.finishWrite(fos);
788             } else {
789                 mAtomicFile.failWrite(fos);
790             }
791         }
792     }
793 }
794