1 /*
2  * Copyright (C) 2019 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.testutils;
18 
19 import android.annotation.NonNull;
20 import android.provider.DeviceConfig;
21 import android.provider.DeviceConfigInterface;
22 import android.util.ArrayMap;
23 import android.util.Pair;
24 
25 import com.android.internal.util.Preconditions;
26 
27 import java.lang.reflect.Constructor;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.concurrent.CountDownLatch;
32 import java.util.concurrent.Executor;
33 import java.util.concurrent.TimeUnit;
34 
35 public class FakeDeviceConfigInterface implements DeviceConfigInterface {
36 
37     private static final String COMPOSITE_DELIMITER = "/";
38     private Map<String, String> mProperties = new HashMap<>();
39     private ArrayMap<DeviceConfig.OnPropertiesChangedListener, Pair<String, Executor>> mListeners =
40             new ArrayMap<>();
41 
createCompositeName(@onNull String namespace, @NonNull String name)42     private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
43         Preconditions.checkNotNull(namespace);
44         Preconditions.checkNotNull(name);
45         return namespace + COMPOSITE_DELIMITER + name;
46     }
47 
clearProperties()48     public void clearProperties() {
49         mProperties.clear();
50     }
51 
putProperty(String namespace, String key, String value)52     public void putProperty(String namespace, String key, String value) {
53         mProperties.put(createCompositeName(namespace, key), value);
54     }
55 
putPropertyAndNotify(String namespace, String key, String value)56     public void putPropertyAndNotify(String namespace, String key, String value) {
57         putProperty(namespace, key, value);
58         DeviceConfig.Properties properties = makeProperties(namespace, key, value);
59         CountDownLatch latch = new CountDownLatch(mListeners.size());
60         for (int i = 0; i < mListeners.size(); i++) {
61             if (namespace.equals(mListeners.valueAt(i).first)) {
62                 final int j = i;
63                 mListeners.valueAt(i).second.execute(
64                         () -> {
65                             mListeners.keyAt(j).onPropertiesChanged(properties);
66                             latch.countDown();
67                         });
68             } else {
69                 latch.countDown();
70             }
71         }
72         boolean success;
73         try {
74             success = latch.await(10, TimeUnit.SECONDS);
75         } catch (InterruptedException e) {
76             success = false;
77         }
78         if (!success) {
79             throw new RuntimeException("Failed to notify all listeners in time.");
80         }
81     }
82 
makeProperties(String namespace, String key, String value)83     private DeviceConfig.Properties makeProperties(String namespace, String key, String value) {
84         try {
85             final Constructor<DeviceConfig.Properties> ctor =
86                     DeviceConfig.Properties.class.getDeclaredConstructor(String.class, Map.class);
87             ctor.setAccessible(true);
88             final HashMap<String, String> keyValueMap = new HashMap<>();
89             keyValueMap.put(key, value);
90             return ctor.newInstance(namespace, keyValueMap);
91         } catch (ReflectiveOperationException e) {
92             throw new RuntimeException(e);
93         }
94     }
95 
96     @Override
getProperty(String namespace, String name)97     public String getProperty(String namespace, String name) {
98         return mProperties.get(createCompositeName(namespace, name));
99     }
100 
101     @Override
getProperties(String namespace, String... names)102     public DeviceConfig.Properties getProperties(String namespace, String... names) {
103         if (!mProperties.keySet().contains(namespace)) {
104             return new DeviceConfig.Properties(namespace, null);
105         }
106         DeviceConfig.Properties.Builder propertiesBuilder = new DeviceConfig.Properties.Builder(
107                 namespace);
108 
109         for (String compositeName : mProperties.keySet()) {
110             if (compositeName.split(COMPOSITE_DELIMITER).length != 2) {
111                 continue;
112             }
113 
114             String existingPropertyNamespace = compositeName.split(COMPOSITE_DELIMITER)[0];
115             String existingPropertyName = compositeName.split(COMPOSITE_DELIMITER)[1];
116 
117             if ((names.length == 0 && existingPropertyNamespace.equals(namespace)) || Arrays.asList(
118                     names).contains(compositeName)) {
119                 propertiesBuilder.setString(existingPropertyName, mProperties.get(compositeName));
120             }
121         }
122 
123         return propertiesBuilder.build();
124     }
125 
126     @Override
setProperty(String namespace, String name, String value, boolean makeDefault)127     public boolean setProperty(String namespace, String name, String value, boolean makeDefault) {
128         putPropertyAndNotify(namespace, name, value);
129         return true;
130     }
131 
132     @Override
setProperties(DeviceConfig.Properties properties)133     public boolean setProperties(DeviceConfig.Properties properties)
134             throws DeviceConfig.BadConfigException {
135         for (String property : properties.getKeyset()) {
136             String compositeName = createCompositeName(properties.getNamespace(), property);
137             putPropertyAndNotify(properties.getNamespace(), compositeName,
138                     properties.getString(property, ""));
139         }
140         return true;
141     }
142 
143     @Override
deleteProperty(String namespace, String name)144     public boolean deleteProperty(String namespace, String name) {
145         mProperties.remove(createCompositeName(namespace, name));
146         return true;
147     }
148 
149     @Override
resetToDefaults(int resetMode, String namespace)150     public void resetToDefaults(int resetMode, String namespace) {
151         clearProperties();
152     }
153 
154     @Override
getString(String namespace, String name, String defaultValue)155     public String getString(String namespace, String name, String defaultValue) {
156         String value = getProperty(namespace, name);
157         return value != null ? value : defaultValue;
158     }
159 
160     @Override
getInt(String namespace, String name, int defaultValue)161     public int getInt(String namespace, String name, int defaultValue) {
162         String value = getProperty(namespace, name);
163         if (value == null) {
164             return defaultValue;
165         }
166         try {
167             return Integer.parseInt(value);
168         } catch (NumberFormatException e) {
169             return defaultValue;
170         }
171     }
172 
173     @Override
getLong(String namespace, String name, long defaultValue)174     public long getLong(String namespace, String name, long defaultValue) {
175         String value = getProperty(namespace, name);
176         if (value == null) {
177             return defaultValue;
178         }
179         try {
180             return Long.parseLong(value);
181         } catch (NumberFormatException e) {
182             return defaultValue;
183         }
184     }
185 
186     @Override
getFloat(String namespace, String name, float defaultValue)187     public float getFloat(String namespace, String name, float defaultValue) {
188         String value = getProperty(namespace, name);
189         if (value == null) {
190             return defaultValue;
191         }
192         try {
193             return Float.parseFloat(value);
194         } catch (NumberFormatException e) {
195             return defaultValue;
196         }
197     }
198 
199     @Override
getBoolean(String namespace, String name, boolean defaultValue)200     public boolean getBoolean(String namespace, String name, boolean defaultValue) {
201         String value = getProperty(namespace, name);
202         return value != null ? Boolean.parseBoolean(value) : defaultValue;
203     }
204 
205     @Override
addOnPropertiesChangedListener(String namespace, Executor executor, DeviceConfig.OnPropertiesChangedListener listener)206     public void addOnPropertiesChangedListener(String namespace, Executor executor,
207             DeviceConfig.OnPropertiesChangedListener listener) {
208         Pair<String, Executor> oldNamespace = mListeners.get(listener);
209         if (oldNamespace == null) {
210             // Brand new listener, add it to the list.
211             mListeners.put(listener, new Pair<>(namespace, executor));
212         } else if (namespace.equals(oldNamespace.first)) {
213             // Listener is already registered for this namespace, update executor just in case.
214             mListeners.put(listener, new Pair<>(namespace, executor));
215         } else {
216             // DeviceConfig allows re-registering listeners for different namespaces, but that
217             // silently unregisters the prior namespace. This likely isn't something the caller
218             // intended.
219             throw new IllegalStateException("Listener " + listener + " already registered. This"
220                     + "is technically allowed by DeviceConfig, but likely indicates a logic "
221                     + "error.");
222         }
223     }
224 
225     @Override
removeOnPropertiesChangedListener( DeviceConfig.OnPropertiesChangedListener listener)226     public void removeOnPropertiesChangedListener(
227             DeviceConfig.OnPropertiesChangedListener listener) {
228         mListeners.remove(listener);
229     }
230 }
231