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