1 /* 2 * Copyright (C) 2022 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.uwb; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.AlarmManager; 23 import android.content.Context; 24 import android.os.Handler; 25 import android.os.UserHandle; 26 import android.util.AtomicFile; 27 import android.util.Log; 28 import android.util.SparseArray; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.internal.util.Preconditions; 32 import com.android.proto.uwb.UwbConfigProto; 33 34 import com.google.protobuf.InvalidProtocolBufferException; 35 36 import java.io.File; 37 import java.io.FileDescriptor; 38 import java.io.FileOutputStream; 39 import java.io.IOException; 40 import java.io.PrintWriter; 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.Collection; 46 import java.util.HashSet; 47 import java.util.List; 48 import java.util.Objects; 49 import java.util.Set; 50 import java.util.stream.Collectors; 51 import java.util.stream.Stream; 52 53 /** 54 * This class provides a mechanism to save data to persistent store files {@link StoreFile}. 55 * Modules can register a {@link StoreData} instance indicating the {@link StoreFile} 56 * into which they want to save their data to. 57 * 58 * NOTE: 59 * <li>Modules can register their {@link StoreData} using 60 * {@link UwbConfigStore#registerStoreData(StoreData)} directly, but should 61 * use {@link UwbConfigStore#saveToStore(boolean)} for any writes.</li> 62 * <li>{@link UwbConfigStore} controls {@link UwbConfigStore} and initiates read at bootup and 63 * store file changes on user switch.</li> 64 * <li>Not thread safe!</li> 65 */ 66 public class UwbConfigStore { 67 /** 68 * Config store file for general shared store file. 69 */ 70 public static final int STORE_FILE_SHARED_GENERAL = 0; 71 72 /** 73 * Config store file for general user store file. 74 */ 75 public static final int STORE_FILE_USER_GENERAL = 1; 76 77 @IntDef(prefix = { "STORE_FILE_" }, value = { 78 STORE_FILE_SHARED_GENERAL, 79 STORE_FILE_USER_GENERAL 80 }) 81 @Retention(RetentionPolicy.SOURCE) 82 public @interface StoreFileId { } 83 84 /** 85 * Current config store data version. This will be incremented for any additions. 86 */ 87 private static final int CURRENT_CONFIG_STORE_DATA_VERSION = 1; 88 89 /** This list of older versions will be used to restore data from older config store. */ 90 /** 91 * First version of the config store data format. 92 */ 93 public static final int INITIAL_CONFIG_STORE_DATA_VERSION = 1; 94 95 /** 96 * Alarm tag to use for starting alarms for buffering file writes. 97 */ 98 @VisibleForTesting 99 public static final String BUFFERED_WRITE_ALARM_TAG = "WriteBufferAlarm"; 100 /** 101 * Log tag. 102 */ 103 private static final String TAG = "UwbConfigStore"; 104 /** 105 * Time interval for buffering file writes for non-forced writes 106 */ 107 private static final int BUFFERED_WRITE_ALARM_INTERVAL_MS = 10 * 1000; 108 /** 109 * Config store file name for general shared store file. 110 */ 111 private static final String STORE_FILE_NAME_SHARED_GENERAL = "UwbConfigStore.bin"; 112 /** 113 * Config store file name for general user store file. 114 */ 115 private static final String STORE_FILE_NAME_USER_GENERAL = "UwbConfigStore.bin"; 116 /** 117 * Mapping of Store file Id to Store file names. 118 */ 119 private static final SparseArray<String> STORE_ID_TO_FILE_NAME = 120 new SparseArray<String>() {{ 121 put(STORE_FILE_SHARED_GENERAL, STORE_FILE_NAME_SHARED_GENERAL); 122 put(STORE_FILE_USER_GENERAL, STORE_FILE_NAME_USER_GENERAL); 123 }}; 124 /** 125 * Handler instance to post alarm timeouts to 126 */ 127 private final Handler mEventHandler; 128 129 /** 130 * Alarm manager instance to start buffer timeout alarms. 131 */ 132 private final AlarmManager mAlarmManager; 133 /** 134 * Reference to UwbInjector 135 */ 136 private final UwbInjector mUwbInjector; 137 138 /** 139 * Shared config store file instance. There are 2 shared store files: 140 * {@link #STORE_FILE_NAME_SHARED_GENERAL}. 141 */ 142 private final List<StoreFile> mSharedStores; 143 /** 144 * User specific store file instances. There are 2 user store files: 145 * {@link #STORE_FILE_NAME_USER_GENERAL}. 146 */ 147 private List<StoreFile> mUserStores; 148 149 /** 150 * Flag to indicate if there is a buffered write pending. 151 */ 152 private boolean mBufferedWritePending = false; 153 /** 154 * Alarm listener for flushing out any buffered writes. 155 */ 156 private boolean mPendingStoreRead = false; 157 /** 158 * Flag to indicate if the user unlock was deferred until the store load occurs. 159 */ 160 private boolean mDeferredUserUnlockRead = false; 161 /** 162 * Current logged in user ID. 163 */ 164 private int mCurrentUserId = UserHandle.SYSTEM.getIdentifier(); 165 /** 166 * Flag to indicate that the new user's store has not yet been read since user switch. 167 * Initialize this flag to |true| to trigger a read on the first user unlock after 168 * bootup. 169 */ 170 private boolean mPendingUnlockStoreRead = true; 171 /** 172 * For verbose logs 173 */ 174 private boolean mVerboseLoggingEnabled = true; 175 176 /** 177 * Enable verbose logging. 178 */ enableVerboseLogging(boolean verbose)179 public void enableVerboseLogging(boolean verbose) { 180 mVerboseLoggingEnabled = verbose; 181 } 182 183 private final AlarmManager.OnAlarmListener mBufferedWriteListener = 184 () -> { 185 try { 186 writeBufferedData(); 187 } catch (IOException e) { 188 Log.wtf(TAG, "Buffered write failed", e); 189 } 190 }; 191 getStoreDataList()192 public List<StoreData> getStoreDataList() { 193 return mStoreDataList; 194 } 195 196 /** 197 * List of data containers. 198 */ 199 private final List<StoreData> mStoreDataList; 200 201 /** 202 * Create a new instance of UwbConfigStore. 203 * Note: The store file instances have been made inputs to this class to ease unit-testing. 204 * 205 * @param context context to use for retrieving the alarm manager. 206 * @param handler handler instance to post alarm timeouts to. 207 * @param uwbInjector reference to UwbInjector. 208 * @param sharedStores List of {@link StoreFile} instances pointing to the shared store files. 209 * This should be retrieved using {@link #createSharedFiles()}} 210 * method. 211 */ UwbConfigStore(Context context, Handler handler, UwbInjector uwbInjector, List<StoreFile> sharedStores)212 public UwbConfigStore(Context context, Handler handler, UwbInjector uwbInjector, 213 List<StoreFile> sharedStores) { 214 215 mAlarmManager = context.getSystemService(AlarmManager.class); 216 mEventHandler = handler; 217 mUwbInjector = uwbInjector; 218 mStoreDataList = new ArrayList<>(); 219 220 // Initialize the store files. 221 mSharedStores = sharedStores; 222 // The user store is initialized to null, this will be set when the user unlocks and 223 // CE storage is accessible via |switchUserStoresAndRead|. 224 mUserStores = null; 225 } 226 227 /** 228 * Read the config store and load the in-memory lists from the store data retrieved and sends 229 * out the networks changed broadcast. 230 * 231 * This reads all the network configurations from: 232 * 1. Shared UwbConfigStore.bin 233 * 2. User UwbConfigStore.bin 234 * 235 * @return true on success or not needed (fresh install), false otherwise. 236 */ loadFromStore()237 public boolean loadFromStore() { 238 // If the user unlock comes in before we load from store, which means the user store have 239 // not been setup yet for the current user. Setup the user store before the read so that 240 // configurations for the current user will also being loaded. 241 if (mDeferredUserUnlockRead) { 242 Log.i(TAG, "Handling user unlock before loading from store."); 243 List<UwbConfigStore.StoreFile> userStoreFiles = 244 UwbConfigStore.createUserFiles(UserHandle.SYSTEM.getIdentifier()); 245 if (userStoreFiles == null) { 246 Log.wtf(TAG, "Failed to create user store files"); 247 return false; 248 } 249 setUserStores(userStoreFiles); 250 mDeferredUserUnlockRead = false; 251 } 252 mPendingStoreRead = true; 253 try { 254 read(); 255 } catch (IOException | IllegalStateException e) { 256 Log.wtf(TAG, "Reading from new store failed. All saved networks are lost!", e); 257 // TODO Need to handle this based on dev build vs prod 258 } 259 mPendingStoreRead = false; 260 return true; 261 } 262 263 /** 264 * Handles the unlock of foreground user. This maybe needed to read the store file if the user's 265 * CE storage is not visible when {@link #handleUserSwitch(int)} is invoked. 266 * 267 * Need to be called when {@link com.android.server.SystemService#onUserUnlocking} is invoked. 268 * 269 * @param userId The identifier of the user that unlocked. 270 */ handleUserUnlock(int userId)271 public void handleUserUnlock(int userId) { 272 if (mVerboseLoggingEnabled) { 273 Log.v(TAG, "Handling user unlock for " + userId); 274 } 275 if (userId != mCurrentUserId) { 276 Log.e(TAG, "Ignore user unlock for non current user " + userId); 277 return; 278 } 279 if (mPendingStoreRead) { 280 Log.w(TAG, "Ignore user unlock until store is read!"); 281 mDeferredUserUnlockRead = true; 282 return; 283 } 284 if (mPendingUnlockStoreRead) { 285 handleUserUnlockOrSwitch(mCurrentUserId); 286 } 287 } 288 289 /** 290 * Helper method to perform the following operations during user switch/unlock: 291 * - Remove private networks of the old user. 292 * - Load from the new user store file. 293 * - Save the store files again to migrate any user specific networks from the shared store 294 * to user store. 295 * This method assumes the user store is visible (i.e CE storage is unlocked). So, the caller 296 * should ensure that the stores are accessible before invocation. 297 * 298 * @param userId The identifier of the new foreground user, after the unlock or switch. 299 */ handleUserUnlockOrSwitch(int userId)300 private void handleUserUnlockOrSwitch(int userId) { 301 if (mVerboseLoggingEnabled) { 302 Log.v(TAG, "Loading from store after user switch/unlock for " + userId); 303 } 304 // Switch out the user store file. 305 if (loadFromUserStoreAfterUnlockOrSwitch(userId)) { 306 saveToStore(true); 307 mPendingUnlockStoreRead = false; 308 } 309 } 310 311 /** 312 * Read the user config store and load the in-memory lists from the store data retrieved and 313 * sends out the networks changed broadcast. 314 * This should be used for all user switches/unlocks to only load networks from the user 315 * specific store and avoid reloading the shared networks. 316 * 317 * This reads all the network configurations from: 318 * 1. User UwbConfigStore.bin 319 * 320 * @param userId The identifier of the foreground user. 321 * @return true on success, false otherwise. 322 */ loadFromUserStoreAfterUnlockOrSwitch(int userId)323 private boolean loadFromUserStoreAfterUnlockOrSwitch(int userId) { 324 try { 325 List<StoreFile> userStoreFiles = createUserFiles(userId); 326 if (userStoreFiles == null) { 327 Log.e(TAG, "Failed to create user store files"); 328 return false; 329 } 330 switchUserStoresAndRead(userStoreFiles); 331 } catch (IOException | IllegalStateException e) { 332 Log.wtf(TAG, "Reading from new store failed. All saved private networks are lost!", e); 333 return false; 334 } 335 return true; 336 } 337 338 /** 339 * Handles the switch to a different foreground user: 340 * - Flush the current state to the old user's store file. 341 * - Switch the user specific store file. 342 * - Reload the networks from the store files (shared & user). 343 * - Write the store files to move any user specific private networks from shared store to user 344 * store. 345 * 346 * Need to be called when {@link com.android.server.SystemService#onUserSwitching} is invoked. 347 * 348 * @param userId The identifier of the new foreground user, after the switch. 349 */ handleUserSwitch(int userId)350 public void handleUserSwitch(int userId) { 351 if (mVerboseLoggingEnabled) { 352 Log.v(TAG, "Handling user switch for " + userId); 353 } 354 if (userId == mCurrentUserId) { 355 Log.w(TAG, "User already in foreground " + userId); 356 } 357 if (mPendingStoreRead) { 358 Log.w(TAG, "User switch before store is read!"); 359 mCurrentUserId = userId; 360 // Reset any state from previous user unlock. 361 mDeferredUserUnlockRead = false; 362 // Cannot read data from new user's CE store file before they log-in. 363 mPendingUnlockStoreRead = true; 364 } 365 366 if (mUwbInjector.getUserManager() 367 .isUserUnlockingOrUnlocked(UserHandle.of(mCurrentUserId))) { 368 saveToStore(true); 369 } 370 // Remove any private config of the old user before switching the userId. 371 clearInternalDataForUser(); 372 mCurrentUserId = userId; 373 374 if (mUwbInjector.getUserManager() 375 .isUserUnlockingOrUnlocked(UserHandle.of(mCurrentUserId))) { 376 handleUserUnlockOrSwitch(mCurrentUserId); 377 } else { 378 // Cannot read data from new user's CE store file before they log-in. 379 mPendingUnlockStoreRead = true; 380 Log.i(TAG, "Waiting for user unlock to load from store"); 381 } 382 } 383 clearInternalDataForUser()384 void clearInternalDataForUser() { 385 if (mUserStores != null) { 386 for (StoreFile userStoreFile : mUserStores) { 387 List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(userStoreFile); 388 for (StoreData storeData : storeDataList) { 389 storeData.resetData(); 390 } 391 } 392 } 393 } 394 395 /** 396 * Save the current snapshot of the in-memory lists to the config store. 397 * 398 * @param forceWrite Whether the write needs to be forced or not. 399 * @return Whether the write was successful or not, this is applicable only for force writes. 400 */ saveToStore(boolean forceWrite)401 public boolean saveToStore(boolean forceWrite) { 402 if (mPendingStoreRead) { 403 Log.e(TAG, "Cannot save to store before store is read!"); 404 return false; 405 } 406 try { 407 write(forceWrite); 408 } catch (IOException | IllegalStateException e) { 409 Log.wtf(TAG, "Writing to store failed. Saved networks maybe lost!", e); 410 return false; 411 } 412 return true; 413 } 414 415 /** 416 * Set the user store files. 417 * (Useful for mocking in unit tests). 418 * @param userStores List of {@link StoreFile} created using 419 * {@link #createUserFiles(int)} }. 420 */ setUserStores(@onNull List<StoreFile> userStores)421 public void setUserStores(@NonNull List<StoreFile> userStores) { 422 Preconditions.checkNotNull(userStores); 423 mUserStores = userStores; 424 } 425 426 /** 427 * Register a {@link StoreData} to read/write data from/to a store. A {@link StoreData} is 428 * responsible for a block of data in the store file, and provides serialization/deserialization 429 * functions for those data. 430 * 431 * @param storeData The store data to be registered to the config store 432 * @return true if registered successfully, false if the store file name is not valid. 433 */ registerStoreData(StoreData storeData)434 public boolean registerStoreData(StoreData storeData) { 435 if (storeData == null) { 436 Log.e(TAG, "Unable to register null store data"); 437 return false; 438 } 439 int storeFileId = storeData.getStoreFileId(); 440 if (STORE_ID_TO_FILE_NAME.get(storeFileId) == null) { 441 Log.e(TAG, "Invalid shared store file specified" + storeFileId); 442 return false; 443 } 444 mStoreDataList.add(storeData); 445 return true; 446 } 447 448 /** 449 * Helper method to create a store file instance for either the shared store or user store. 450 * Note: The method creates the store directory if not already present. This may be needed for 451 * user store files. 452 * 453 * @param storeDir Base directory under which the store file is to be stored. The store file 454 * will be at <storeDir>/UwbConfigStore.bin. 455 * @param fileId Identifier for the file. See {@link StoreFileId}. 456 * @return new instance of the store file or null if the directory cannot be created. 457 */ 458 @Nullable createFile(@onNull File storeDir, @StoreFileId int fileId)459 private static StoreFile createFile(@NonNull File storeDir, 460 @StoreFileId int fileId) { 461 if (!storeDir.exists()) { 462 if (!storeDir.mkdir()) { 463 Log.w(TAG, "Could not create store directory " + storeDir); 464 return null; 465 } 466 } 467 File file = new File(storeDir, STORE_ID_TO_FILE_NAME.get(fileId)); 468 return new StoreFile(file, fileId); 469 } 470 471 @Nullable createFiles(File storeDir, List<Integer> storeFileIds)472 private static List<StoreFile> createFiles(File storeDir, List<Integer> storeFileIds) { 473 List<StoreFile> storeFiles = new ArrayList<>(); 474 for (int fileId : storeFileIds) { 475 StoreFile storeFile = 476 createFile(storeDir, fileId); 477 if (storeFile == null) { 478 return null; 479 } 480 storeFiles.add(storeFile); 481 } 482 return storeFiles; 483 } 484 485 /** 486 * Create a new instance of the shared store file. 487 * 488 * @return new instance of the store file or null if the directory cannot be created. 489 */ 490 @NonNull createSharedFiles()491 public static List<StoreFile> createSharedFiles() { 492 return createFiles( 493 UwbInjector.getDeviceProtectedDataDir(), 494 Arrays.asList(STORE_FILE_SHARED_GENERAL)); 495 } 496 497 /** 498 * Create new instances of the user specific store files. 499 * The user store file is inside the user's encrypted data directory. 500 * 501 * @param userId userId corresponding to the currently logged-in user. 502 * @return List of new instances of the store files created or null if the directory cannot be 503 * created. 504 */ 505 @Nullable createUserFiles(int userId)506 public static List<StoreFile> createUserFiles(int userId) { 507 return createFiles( 508 UwbInjector.getCredentialProtectedDataDirForUser(userId), 509 Arrays.asList(STORE_FILE_USER_GENERAL)); 510 } 511 512 /** 513 * Retrieve the list of {@link StoreData} instances registered for the provided 514 * {@link StoreFile}. 515 */ retrieveStoreDataListForStoreFile(@onNull StoreFile storeFile)516 private List<StoreData> retrieveStoreDataListForStoreFile(@NonNull StoreFile storeFile) { 517 return mStoreDataList 518 .stream() 519 .filter(s -> s.getStoreFileId() == storeFile.getFileId()) 520 .collect(Collectors.toList()); 521 } 522 523 /** 524 * Check if any of the provided list of {@link StoreData} instances registered 525 * for the provided {@link StoreFile }have indicated that they have new data to serialize. 526 */ hasNewDataToSerialize(@onNull StoreFile storeFile)527 private boolean hasNewDataToSerialize(@NonNull StoreFile storeFile) { 528 List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile); 529 return storeDataList.stream().anyMatch(StoreData::hasNewDataToSerialize); 530 } 531 532 /** 533 * API to write the data provided by registered store data to config stores. 534 * The method writes the user specific configurations to user specific config store and the 535 * shared configurations to shared config store. 536 * 537 * @param forceSync boolean to force write the config stores now. if false, the writes are 538 * buffered and written after the configured interval. 539 */ write(boolean forceSync)540 public void write(boolean forceSync) throws IOException { 541 boolean hasAnyNewData = false; 542 // Serialize the provided data and send it to the respective stores. The actual write will 543 // be performed later depending on the |forceSync| flag . 544 for (StoreFile sharedStoreFile : mSharedStores) { 545 if (hasNewDataToSerialize(sharedStoreFile)) { 546 byte[] sharedDataBytes = serializeData(sharedStoreFile); 547 sharedStoreFile.storeRawDataToWrite(sharedDataBytes); 548 hasAnyNewData = true; 549 } 550 } 551 552 if (mUserStores != null) { 553 for (StoreFile userStoreFile : mUserStores) { 554 if (hasNewDataToSerialize(userStoreFile)) { 555 byte[] userDataBytes = serializeData(userStoreFile); 556 userStoreFile.storeRawDataToWrite(userDataBytes); 557 hasAnyNewData = true; 558 } 559 } 560 } 561 562 if (hasAnyNewData) { 563 // Every write provides a new snapshot to be persisted, so |forceSync| flag overrides 564 // any pending buffer writes. 565 if (forceSync) { 566 writeBufferedData(); 567 } else { 568 startBufferedWriteAlarm(); 569 } 570 } else if (forceSync && mBufferedWritePending) { 571 // no new data to write, but there is a pending buffered write. So, |forceSync| should 572 // flush that out. 573 writeBufferedData(); 574 } 575 } 576 577 /** 578 * Serialize all the data from all the {@link StoreData} clients registered for the provided 579 * {@link StoreFile}. 580 * 581 * @param storeFile StoreFile that we want to write to. 582 * @return byte[] of serialized bytes 583 */ serializeData(@onNull StoreFile storeFile)584 private byte[] serializeData(@NonNull StoreFile storeFile) { 585 List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile); 586 UwbConfigProto.UwbConfig.Builder builder = UwbConfigProto.UwbConfig.newBuilder(); 587 builder.setVersion(CURRENT_CONFIG_STORE_DATA_VERSION); 588 for (StoreData storeData : storeDataList) { 589 storeData.serializeData(builder); 590 } 591 return builder.build().toByteArray(); 592 } 593 594 /** 595 * Helper method to start a buffered write alarm if one doesn't already exist. 596 */ startBufferedWriteAlarm()597 private void startBufferedWriteAlarm() { 598 if (!mBufferedWritePending) { 599 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 600 mUwbInjector.getElapsedSinceBootMillis() + BUFFERED_WRITE_ALARM_INTERVAL_MS, 601 BUFFERED_WRITE_ALARM_TAG, mBufferedWriteListener, mEventHandler); 602 mBufferedWritePending = true; 603 } 604 } 605 606 /** 607 * Helper method to stop a buffered write alarm if one exists. 608 */ stopBufferedWriteAlarm()609 private void stopBufferedWriteAlarm() { 610 if (mBufferedWritePending) { 611 mAlarmManager.cancel(mBufferedWriteListener); 612 mBufferedWritePending = false; 613 } 614 } 615 616 /** 617 * Helper method to actually perform the writes to the file. This flushes out any write data 618 * being buffered in the respective stores and cancels any pending buffer write alarms. 619 */ writeBufferedData()620 private void writeBufferedData() throws IOException { 621 stopBufferedWriteAlarm(); 622 623 long writeStartTime = mUwbInjector.getElapsedSinceBootMillis(); 624 for (StoreFile sharedStoreFile : mSharedStores) { 625 sharedStoreFile.writeBufferedRawData(); 626 } 627 if (mUserStores != null) { 628 for (StoreFile userStoreFile : mUserStores) { 629 userStoreFile.writeBufferedRawData(); 630 } 631 } 632 long writeTime = mUwbInjector.getElapsedSinceBootMillis() - writeStartTime; 633 Log.d(TAG, "Writing to stores completed in " + writeTime + " ms."); 634 } 635 636 /** 637 * Helper method to read from the shared store files. 638 */ readFromSharedStoreFiles()639 private void readFromSharedStoreFiles() throws IOException { 640 for (StoreFile sharedStoreFile : mSharedStores) { 641 byte[] sharedDataBytes = sharedStoreFile.readRawData(); 642 deserializeData(sharedDataBytes, sharedStoreFile); 643 } 644 } 645 646 /** 647 * Helper method to read from the user store files. 648 */ readFromUserStoreFiles()649 private void readFromUserStoreFiles() { 650 for (StoreFile userStoreFile : mUserStores) { 651 byte[] userDataBytes = userStoreFile.readRawData(); 652 deserializeData(userDataBytes, userStoreFile); 653 } 654 } 655 656 /** 657 * API to read the store data from the config stores. 658 * The method reads the user specific configurations from user specific config store and the 659 * shared configurations from the shared config store. 660 */ read()661 public void read() throws IOException { 662 // Reset both share and user store data. 663 for (StoreFile sharedStoreFile : mSharedStores) { 664 resetStoreData(sharedStoreFile); 665 } 666 if (mUserStores != null) { 667 for (StoreFile userStoreFile : mUserStores) { 668 resetStoreData(userStoreFile); 669 } 670 } 671 long readStartTime = mUwbInjector.getElapsedSinceBootMillis(); 672 readFromSharedStoreFiles(); 673 if (mUserStores != null) { 674 readFromUserStoreFiles(); 675 } 676 long readTime = mUwbInjector.getElapsedSinceBootMillis() - readStartTime; 677 Log.d(TAG, "Reading from all stores completed in " + readTime + " ms."); 678 } 679 680 /** 681 * Handles a user switch. This method changes the user specific store files and reads from the 682 * new user's store files. 683 * 684 * @param userStores List of {@link StoreFile} created using {@link #createUserFiles(int)}. 685 */ switchUserStoresAndRead(@onNull List<StoreFile> userStores)686 public void switchUserStoresAndRead(@NonNull List<StoreFile> userStores) 687 throws IOException { 688 //TODO Not yet supported. 689 Preconditions.checkNotNull(userStores); 690 // Reset user store data. 691 if (mUserStores != null) { 692 for (StoreFile userStoreFile : mUserStores) { 693 resetStoreData(userStoreFile); 694 } 695 } 696 697 // Stop any pending buffered writes, if any. 698 stopBufferedWriteAlarm(); 699 mUserStores = userStores; 700 701 // Now read from the user store files. 702 long readStartTime = mUwbInjector.getElapsedSinceBootMillis(); 703 readFromUserStoreFiles(); 704 long readTime = mUwbInjector.getElapsedSinceBootMillis() - readStartTime; 705 Log.d(TAG, "Reading from user stores completed in " + readTime + " ms."); 706 } 707 708 /** 709 * Reset data for all {@link StoreData} instances registered for this {@link StoreFile}. 710 */ resetStoreData(@onNull StoreFile storeFile)711 private void resetStoreData(@NonNull StoreFile storeFile) { 712 for (StoreData storeData: retrieveStoreDataListForStoreFile(storeFile)) { 713 storeData.resetData(); 714 } 715 } 716 717 // Inform all the provided store data clients that there is nothing in the store for them. indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet)718 private void indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet) { 719 for (StoreData storeData : storeDataSet) { 720 storeData.deserializeData(null); 721 } 722 } 723 724 /** 725 * Deserialize data from a {@link StoreFile} for all {@link StoreData} instances registered. 726 * 727 * @param dataBytes The data to parse 728 * @param storeFile StoreFile that we read from. Will be used to retrieve the list of clients 729 * who have data to deserialize from this file. 730 */ deserializeData(@onNull byte[] dataBytes, @NonNull StoreFile storeFile)731 private void deserializeData(@NonNull byte[] dataBytes, @NonNull StoreFile storeFile) { 732 List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile); 733 if (dataBytes == null) { 734 indicateNoDataForStoreDatas(storeDataList); 735 return; 736 } 737 738 Set<StoreData> storeDatasInvoked = new HashSet<>(); 739 UwbConfigProto.UwbConfig uwbConfig; 740 try { 741 uwbConfig = UwbConfigProto.UwbConfig.parseFrom(dataBytes); 742 } catch (InvalidProtocolBufferException e) { 743 Log.e(TAG, "Wrong Uwb config proto version"); 744 return; 745 } 746 for (StoreData storeData: mStoreDataList) { 747 storeData.deserializeData(uwbConfig); 748 storeDatasInvoked.add(storeData); 749 } 750 // Inform all the other registered store data clients that there is nothing in the store 751 // for them. 752 Set<StoreData> storeDatasNotInvoked = new HashSet<>(storeDataList); 753 storeDatasNotInvoked.removeAll(storeDatasInvoked); 754 indicateNoDataForStoreDatas(storeDatasNotInvoked); 755 } 756 757 /** 758 * Dump the local log buffer and other internal state of UwbConfigManager. 759 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)760 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 761 pw.println("---- Dump of UwbConfigStore ----"); 762 pw.println("UwbConfigStore - Store File Begin ----"); 763 Stream.of(mSharedStores, mUserStores) 764 .filter(Objects::nonNull) 765 .flatMap(List::stream) 766 .forEach((storeFile) -> { 767 pw.print("Name: " + storeFile.mFileName); 768 pw.println(", File Id: " + storeFile.mFileId); 769 }); 770 pw.println("UwbConfigStore - Store Data Begin ----"); 771 for (StoreData storeData : mStoreDataList) { 772 pw.print("StoreData =>"); 773 pw.print(" "); 774 pw.print("Name: " + storeData.getName()); 775 pw.print(", "); 776 pw.print("File Id: " + storeData.getStoreFileId()); 777 pw.print(", "); 778 pw.println("File Name: " + STORE_ID_TO_FILE_NAME.get(storeData.getStoreFileId())); 779 } 780 pw.println("---- Dump of UwbConfigStore ----"); 781 } 782 783 /** 784 * Class to encapsulate all file writes. This is a wrapper over {@link AtomicFile} to write/read 785 * raw data from the persistent file with integrity. This class provides helper methods to 786 * read/write the entire file into a byte array. 787 * This helps to separate out the processing, parsing, and integrity checking from the actual 788 * file writing. 789 */ 790 public static class StoreFile { 791 /** 792 * The store file to be written to. 793 */ 794 private final AtomicFile mAtomicFile; 795 /** 796 * This is an intermediate buffer to store the data to be written. 797 */ 798 private byte[] mWriteData; 799 /** 800 * Store the file name for setting the file permissions/logging purposes. 801 */ 802 private final String mFileName; 803 /** 804 * {@link StoreFileId} Type of store file. 805 */ 806 @StoreFileId 807 private final int mFileId; 808 StoreFile(File file, @StoreFileId int fileId)809 public StoreFile(File file, @StoreFileId int fileId) { 810 mAtomicFile = new AtomicFile(file); 811 mFileName = file.getAbsolutePath(); 812 mFileId = fileId; 813 } 814 getName()815 public String getName() { 816 return mAtomicFile.getBaseFile().getName(); 817 } 818 819 @StoreFileId getFileId()820 public int getFileId() { 821 return mFileId; 822 } 823 824 /** 825 * Read the entire raw data from the store file and return in a byte array. 826 * 827 * @return raw data read from the file or null if the file is not found or the data has 828 * been altered. 829 */ readRawData()830 public byte[] readRawData() { 831 byte[] bytes; 832 try { 833 bytes = mAtomicFile.readFully(); 834 } catch (IOException e) { 835 return null; 836 } 837 return bytes; 838 } 839 840 /** 841 * Store the provided byte array to be written when {@link #writeBufferedRawData()} method 842 * is invoked. 843 * This intermediate step is needed to help in buffering file writes. 844 * 845 * @param data raw data to be written to the file. 846 */ storeRawDataToWrite(byte[] data)847 public void storeRawDataToWrite(byte[] data) { 848 mWriteData = data; 849 } 850 851 /** 852 * Write the stored raw data to the store file. 853 * After the write to file, the mWriteData member is reset. 854 * @throws IOException if an error occurs. The output stream is always closed by the method 855 * even when an exception is encountered. 856 */ writeBufferedRawData()857 public void writeBufferedRawData() throws IOException { 858 if (mWriteData == null) return; // No data to write for this file. 859 // Write the data to the atomic file. 860 FileOutputStream out = null; 861 try { 862 out = mAtomicFile.startWrite(); 863 out.write(mWriteData); 864 mAtomicFile.finishWrite(out); 865 } catch (IOException e) { 866 if (out != null) { 867 mAtomicFile.failWrite(out); 868 } 869 throw e; 870 } 871 // Reset the pending write data after write. 872 mWriteData = null; 873 } 874 } 875 876 /** 877 * Interface to be implemented by a module that contained data in the config store file. 878 * 879 * The module will be responsible for serializing/deserializing their own data. 880 * Whenever {@link UwbConfigStore#read()} is invoked, all registered StoreData instances will 881 * be notified that a read was performed via {@link StoreData#deserializeData 882 * UwbConfig)} regardless of whether there is any data for them or not in the 883 * store file. 884 * 885 */ 886 public interface StoreData { 887 /** 888 * @param builder UwbConfigProto builder 889 * @throws NullPointerException failure serializing data 890 */ serializeData(UwbConfigProto.UwbConfig.Builder builder)891 void serializeData(UwbConfigProto.UwbConfig.Builder builder) throws NullPointerException; 892 893 /** 894 * @param uwbConfig config read from file to be deserialized 895 */ deserializeData(UwbConfigProto.UwbConfig uwbConfig)896 void deserializeData(UwbConfigProto.UwbConfig uwbConfig); 897 898 /** 899 * Reset configuration data. 900 */ resetData()901 void resetData(); 902 903 /** 904 * Check if there is any new data to persist from the last write. 905 * 906 * @return true if the module has new data to persist, false otherwise. 907 */ hasNewDataToSerialize()908 boolean hasNewDataToSerialize(); 909 910 /** 911 * Return the name of this store data. The data will be enclosed under this tag in 912 * the XML block. 913 * 914 * @return The name of the store data 915 */ getName()916 String getName(); 917 918 /** 919 * File Id where this data needs to be written to. 920 * This should be one of {@link #STORE_FILE_SHARED_GENERAL}, 921 * {@link #STORE_FILE_USER_GENERAL} 922 * 923 * Note: For most uses, the shared or user general store is sufficient. Creating and 924 * managing store files are expensive. Only use specific store files if you have a large 925 * amount of data which may not need to be persisted frequently (or at least not as 926 * frequently as the general store). 927 * @return Id of the file where this data needs to be persisted. 928 */ getStoreFileId()929 @StoreFileId int getStoreFileId(); 930 } 931 } 932