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