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