• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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