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