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 * <input-mananger-state> 61 * <input-devices> 62 * <input-device descriptor="xxxxx" keyboard-layout="yyyyy" /> 63 * >input-devices> 64 * >/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