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