• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.location.settings;
18 
19 import static com.android.server.location.LocationManagerService.TAG;
20 import static com.android.server.location.settings.SettingsStore.VersionedSettings.VERSION_DOES_NOT_EXIST;
21 
22 import android.util.AtomicFile;
23 import android.util.Log;
24 
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.internal.os.BackgroundThread;
28 import com.android.internal.util.Preconditions;
29 
30 import java.io.ByteArrayInputStream;
31 import java.io.DataInput;
32 import java.io.DataInputStream;
33 import java.io.DataOutput;
34 import java.io.DataOutputStream;
35 import java.io.File;
36 import java.io.FileOutputStream;
37 import java.io.IOException;
38 import java.util.Objects;
39 import java.util.concurrent.CountDownLatch;
40 import java.util.function.Function;
41 
42 /** Base class for read/write/versioning functionality for storing persistent settings to a file. */
43 abstract class SettingsStore<T extends SettingsStore.VersionedSettings> {
44 
45     interface VersionedSettings {
46         /** Represents that the settings do not exist. */
47         int VERSION_DOES_NOT_EXIST = Integer.MAX_VALUE;
48 
49         /** Must always return a version number less than {@link #VERSION_DOES_NOT_EXIST}. */
getVersion()50         int getVersion();
51     }
52 
53     private final AtomicFile mFile;
54 
55     @GuardedBy("this")
56     private boolean mInitialized;
57     @GuardedBy("this")
58     private T mCache;
59 
SettingsStore(File file)60     protected SettingsStore(File file) {
61         mFile = new AtomicFile(file);
62     }
63 
64     /**
65      * Must be implemented to read in a settings instance, and upgrade to the appropriate version
66      * where necessary. If the provided version is {@link VersionedSettings#VERSION_DOES_NOT_EXIST}
67      * then the DataInput will be empty, and the method should return a settings instance with all
68      * settings set to the default value.
69      */
read(int version, DataInput in)70     protected abstract T read(int version, DataInput in) throws IOException;
71 
72     /**
73      * Must be implemented to write the given settings to the given DataOutput.
74      */
write(DataOutput out, T settings)75     protected abstract void write(DataOutput out, T settings) throws IOException;
76 
77     /**
78      * Invoked when settings change, and while holding the internal lock. If used to invoke
79      * listeners, ensure they are not invoked while holding the lock (ie, asynchronously).
80      */
onChange(T oldSettings, T newSettings)81     protected abstract void onChange(T oldSettings, T newSettings);
82 
initializeCache()83     public final synchronized void initializeCache() {
84         if (!mInitialized) {
85             if (mFile.exists()) {
86                 try (DataInputStream is = new DataInputStream(mFile.openRead())) {
87                     mCache = read(is.readInt(), is);
88                     Preconditions.checkState(mCache.getVersion() < VERSION_DOES_NOT_EXIST);
89                 } catch (IOException e) {
90                     Log.e(TAG, "error reading location settings (" + mFile
91                             + "), falling back to defaults", e);
92                 }
93             }
94 
95             if (mCache == null) {
96                 try {
97                     mCache = read(VERSION_DOES_NOT_EXIST,
98                             new DataInputStream(new ByteArrayInputStream(new byte[0])));
99                     Preconditions.checkState(mCache.getVersion() < VERSION_DOES_NOT_EXIST);
100                 } catch (IOException e) {
101                     throw new AssertionError(e);
102                 }
103             }
104 
105             mInitialized = true;
106         }
107     }
108 
109     public final synchronized T get() {
110         initializeCache();
111         return mCache;
112     }
113 
114     public synchronized void update(Function<T, T> updater) {
115         initializeCache();
116 
117         T oldSettings = mCache;
118         T newSettings = Objects.requireNonNull(updater.apply(oldSettings));
119         if (oldSettings.equals(newSettings)) {
120             return;
121         }
122 
123         mCache = newSettings;
124         Preconditions.checkState(mCache.getVersion() < VERSION_DOES_NOT_EXIST);
125 
126         writeLazily(newSettings);
127 
128         onChange(oldSettings, newSettings);
129     }
130 
131     @VisibleForTesting
132     synchronized void flushFile() throws InterruptedException {
133         CountDownLatch latch = new CountDownLatch(1);
134         BackgroundThread.getExecutor().execute(latch::countDown);
135         latch.await();
136     }
137 
138     @VisibleForTesting
139     synchronized void deleteFile() throws InterruptedException {
140         CountDownLatch latch = new CountDownLatch(1);
141         BackgroundThread.getExecutor().execute(() -> {
142             mFile.delete();
143             latch.countDown();
144         });
145         latch.await();
146     }
147 
148     private void writeLazily(T settings) {
149         BackgroundThread.getExecutor().execute(() -> {
150             FileOutputStream os = null;
151             try {
152                 os = mFile.startWrite();
153                 DataOutputStream out = new DataOutputStream(os);
154                 out.writeInt(settings.getVersion());
155                 write(out, settings);
156                 mFile.finishWrite(os);
157             } catch (IOException e) {
158                 mFile.failWrite(os);
159                 Log.e(TAG, "failure serializing location settings", e);
160             } catch (Throwable e) {
161                 mFile.failWrite(os);
162                 throw e;
163             }
164         });
165     }
166 }
167