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 * <input-mananger-state> 64 * <input-devices> 65 * <input-device descriptor="xxxxx" keyboard-layout="yyyyy" /> 66 * >input-devices> 67 * >/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