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