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