1 /* 2 * Copyright (C) 2024 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 package android.provider; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.content.ContentResolver; 21 import android.database.ContentObserver; 22 import android.net.Uri; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.provider.DeviceConfig.BadConfigException; 26 import android.provider.DeviceConfig.MonitorCallback; 27 import android.provider.DeviceConfig.Properties; 28 29 import com.android.internal.annotations.GuardedBy; 30 31 import java.util.ArrayList; 32 import java.util.HashMap; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Objects; 36 import java.util.concurrent.Executor; 37 38 /** 39 * {@link DeviceConfigDataStore} used only on Ravenwood. 40 * 41 * TODO(b/368591527) Support monitor callback related features 42 * TODO(b/368591527) Support "default" related features 43 */ 44 final class RavenwoodConfigDataStore implements DeviceConfigDataStore { 45 private static final RavenwoodConfigDataStore sInstance = new RavenwoodConfigDataStore(); 46 47 private final Object mLock = new Object(); 48 49 @GuardedBy("mLock") 50 private int mSyncDisabledMode = DeviceConfig.SYNC_DISABLED_MODE_NONE; 51 52 @GuardedBy("mLock") 53 private final HashMap<String, HashMap<String, String>> mStore = new HashMap<>(); 54 ObserverInfo(String namespace, ContentObserver observer)55 private record ObserverInfo(String namespace, ContentObserver observer) { 56 } 57 58 @GuardedBy("mLock") 59 private final ArrayList<ObserverInfo> mObservers = new ArrayList<>(); 60 getInstance()61 static RavenwoodConfigDataStore getInstance() { 62 return sInstance; 63 } 64 shouldNotBeCalled()65 private static void shouldNotBeCalled() { 66 throw new RuntimeException("shouldNotBeCalled"); 67 } 68 clearAll()69 void clearAll() { 70 synchronized (mLock) { 71 mSyncDisabledMode = DeviceConfig.SYNC_DISABLED_MODE_NONE; 72 mStore.clear(); 73 } 74 } 75 76 @GuardedBy("mLock") getNamespaceLocked(@onNull String namespace)77 private HashMap<String, String> getNamespaceLocked(@NonNull String namespace) { 78 Objects.requireNonNull(namespace); 79 return mStore.computeIfAbsent(namespace, k -> new HashMap<>()); 80 } 81 82 /** {@inheritDoc} */ 83 @NonNull 84 @Override getAllProperties()85 public Map<String, String> getAllProperties() { 86 synchronized (mLock) { 87 var ret = new HashMap<String, String>(); 88 89 for (var namespaceAndMap : mStore.entrySet()) { 90 for (var nameAndValue : namespaceAndMap.getValue().entrySet()) { 91 ret.put(namespaceAndMap.getKey() + "/" + nameAndValue.getKey(), 92 nameAndValue.getValue()); 93 } 94 } 95 return ret; 96 } 97 } 98 99 /** {@inheritDoc} */ 100 @NonNull 101 @Override getProperties(@onNull String namespace, @NonNull String... names)102 public Properties getProperties(@NonNull String namespace, @NonNull String... names) { 103 Objects.requireNonNull(namespace); 104 105 synchronized (mLock) { 106 var namespaceMap = getNamespaceLocked(namespace); 107 108 if (names.length == 0) { 109 return new Properties(namespace, Map.copyOf(namespaceMap)); 110 } else { 111 var map = new HashMap<String, String>(); 112 for (var name : names) { 113 Objects.requireNonNull(name); 114 map.put(name, namespaceMap.get(name)); 115 } 116 return new Properties(namespace, map); 117 } 118 } 119 } 120 121 /** {@inheritDoc} */ 122 @Override setProperties(@onNull Properties properties)123 public boolean setProperties(@NonNull Properties properties) throws BadConfigException { 124 Objects.requireNonNull(properties); 125 126 synchronized (mLock) { 127 var namespaceMap = getNamespaceLocked(properties.getNamespace()); 128 for (var kv : properties.getPropertyValues().entrySet()) { 129 namespaceMap.put( 130 Objects.requireNonNull(kv.getKey()), 131 Objects.requireNonNull(kv.getValue()) 132 ); 133 } 134 notifyObserversLock(properties.getNamespace(), 135 properties.getKeyset().toArray(new String[0])); 136 } 137 return true; 138 } 139 140 /** {@inheritDoc} */ 141 @Override setProperty(@onNull String namespace, @NonNull String name, @Nullable String value, boolean makeDefault)142 public boolean setProperty(@NonNull String namespace, @NonNull String name, 143 @Nullable String value, boolean makeDefault) { 144 Objects.requireNonNull(namespace); 145 Objects.requireNonNull(name); 146 147 synchronized (mLock) { 148 var namespaceMap = getNamespaceLocked(namespace); 149 namespaceMap.put(name, value); 150 151 // makeDefault not supported. 152 notifyObserversLock(namespace, new String[]{name}); 153 } 154 return true; 155 } 156 157 /** {@inheritDoc} */ 158 @Override deleteProperty(@onNull String namespace, @NonNull String name)159 public boolean deleteProperty(@NonNull String namespace, @NonNull String name) { 160 Objects.requireNonNull(namespace); 161 Objects.requireNonNull(name); 162 163 synchronized (mLock) { 164 var namespaceMap = getNamespaceLocked(namespace); 165 if (namespaceMap.remove(name) != null) { 166 notifyObserversLock(namespace, new String[]{name}); 167 } 168 } 169 return true; 170 } 171 172 /** {@inheritDoc} */ 173 @Override resetToDefaults(int resetMode, @Nullable String namespace)174 public void resetToDefaults(int resetMode, @Nullable String namespace) { 175 // not supported in DeviceConfig.java 176 shouldNotBeCalled(); 177 } 178 179 /** {@inheritDoc} */ 180 @Override setSyncDisabledMode(int syncDisabledMode)181 public void setSyncDisabledMode(int syncDisabledMode) { 182 synchronized (mLock) { 183 mSyncDisabledMode = syncDisabledMode; 184 } 185 } 186 187 /** {@inheritDoc} */ 188 @Override getSyncDisabledMode()189 public int getSyncDisabledMode() { 190 synchronized (mLock) { 191 return mSyncDisabledMode; 192 } 193 } 194 195 /** {@inheritDoc} */ 196 @Override setMonitorCallback(@onNull ContentResolver resolver, @NonNull Executor executor, @NonNull MonitorCallback callback)197 public void setMonitorCallback(@NonNull ContentResolver resolver, @NonNull Executor executor, 198 @NonNull MonitorCallback callback) { 199 // not supported in DeviceConfig.java 200 shouldNotBeCalled(); 201 } 202 203 /** {@inheritDoc} */ 204 @Override clearMonitorCallback(@onNull ContentResolver resolver)205 public void clearMonitorCallback(@NonNull ContentResolver resolver) { 206 // not supported in DeviceConfig.java 207 shouldNotBeCalled(); 208 } 209 210 /** {@inheritDoc} */ 211 @Override registerContentObserver(@onNull String namespace, boolean notifyForescendants, ContentObserver contentObserver)212 public void registerContentObserver(@NonNull String namespace, boolean notifyForescendants, 213 ContentObserver contentObserver) { 214 synchronized (mLock) { 215 mObservers.add(new ObserverInfo( 216 Objects.requireNonNull(namespace), 217 Objects.requireNonNull(contentObserver) 218 )); 219 } 220 } 221 222 /** {@inheritDoc} */ 223 @Override unregisterContentObserver(@onNull ContentObserver contentObserver)224 public void unregisterContentObserver(@NonNull ContentObserver contentObserver) { 225 synchronized (mLock) { 226 for (int i = mObservers.size() - 1; i >= 0; i--) { 227 if (mObservers.get(i).observer == contentObserver) { 228 mObservers.remove(i); 229 } 230 } 231 } 232 } 233 234 private static final Uri CONTENT_URI = Uri.parse("content://settings/config"); 235 236 @GuardedBy("mLock") notifyObserversLock(@onNull String namespace, String[] keys)237 private void notifyObserversLock(@NonNull String namespace, String[] keys) { 238 var urib = CONTENT_URI.buildUpon().appendPath(namespace); 239 for (var key : keys) { 240 urib.appendEncodedPath(key); 241 } 242 var uri = urib.build(); 243 244 final var copy = List.copyOf(mObservers); 245 new Handler(Looper.getMainLooper()).post(() -> { 246 for (int i = copy.size() - 1; i >= 0; i--) { 247 if (copy.get(i).namespace.equals(namespace)) { 248 copy.get(i).observer.dispatchChange(false, uri); 249 } 250 } 251 }); 252 } 253 } 254