blob: 161232161b91564003715d9cedaa41dffe2ba6e3 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.testutils;
import android.annotation.NonNull;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.util.ArrayMap;
import android.util.Pair;
import com.android.internal.util.Preconditions;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
public class FakeDeviceConfigInterface implements DeviceConfigInterface {
private static final String COMPOSITE_DELIMITER = "/";
private Map<String, String> mProperties = new HashMap<>();
private ArrayMap<DeviceConfig.OnPropertiesChangedListener, Pair<String, Executor>> mListeners =
new ArrayMap<>();
private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
Preconditions.checkNotNull(namespace);
Preconditions.checkNotNull(name);
return namespace + COMPOSITE_DELIMITER + name;
}
public void clearProperties() {
mProperties.clear();
}
public void putProperty(String namespace, String key, String value) {
mProperties.put(createCompositeName(namespace, key), value);
}
public void putPropertyAndNotify(String namespace, String key, String value) {
putProperty(namespace, key, value);
DeviceConfig.Properties properties = makeProperties(namespace, key, value);
CountDownLatch latch = new CountDownLatch(mListeners.size());
for (int i = 0; i < mListeners.size(); i++) {
if (namespace.equals(mListeners.valueAt(i).first)) {
final int j = i;
mListeners.valueAt(i).second.execute(
() -> {
mListeners.keyAt(j).onPropertiesChanged(properties);
latch.countDown();
});
} else {
latch.countDown();
}
}
boolean success;
try {
success = latch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
success = false;
}
if (!success) {
throw new RuntimeException("Failed to notify all listeners in time.");
}
}
private DeviceConfig.Properties makeProperties(String namespace, String key, String value) {
try {
final Constructor<DeviceConfig.Properties> ctor =
DeviceConfig.Properties.class.getDeclaredConstructor(String.class, Map.class);
ctor.setAccessible(true);
final HashMap<String, String> keyValueMap = new HashMap<>();
keyValueMap.put(key, value);
return ctor.newInstance(namespace, keyValueMap);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public String getProperty(String namespace, String name) {
return mProperties.get(createCompositeName(namespace, name));
}
@Override
public DeviceConfig.Properties getProperties(String namespace, String... names) {
if (!mProperties.keySet().contains(namespace)) {
return new DeviceConfig.Properties(namespace, null);
}
DeviceConfig.Properties.Builder propertiesBuilder = new DeviceConfig.Properties.Builder(
namespace);
for (String compositeName : mProperties.keySet()) {
if (compositeName.split(COMPOSITE_DELIMITER).length != 2) {
continue;
}
String existingPropertyNamespace = compositeName.split(COMPOSITE_DELIMITER)[0];
String existingPropertyName = compositeName.split(COMPOSITE_DELIMITER)[1];
if ((names.length == 0 && existingPropertyNamespace.equals(namespace)) || Arrays.asList(
names).contains(compositeName)) {
propertiesBuilder.setString(existingPropertyName, mProperties.get(compositeName));
}
}
return propertiesBuilder.build();
}
@Override
public boolean setProperty(String namespace, String name, String value, boolean makeDefault) {
putPropertyAndNotify(namespace, name, value);
return true;
}
@Override
public boolean setProperties(DeviceConfig.Properties properties)
throws DeviceConfig.BadConfigException {
for (String property : properties.getKeyset()) {
String compositeName = createCompositeName(properties.getNamespace(), property);
putPropertyAndNotify(properties.getNamespace(), compositeName,
properties.getString(property, ""));
}
return true;
}
@Override
public boolean deleteProperty(String namespace, String name) {
mProperties.remove(createCompositeName(namespace, name));
return true;
}
@Override
public void resetToDefaults(int resetMode, String namespace) {
clearProperties();
}
@Override
public String getString(String namespace, String name, String defaultValue) {
String value = getProperty(namespace, name);
return value != null ? value : defaultValue;
}
@Override
public int getInt(String namespace, String name, int defaultValue) {
String value = getProperty(namespace, name);
if (value == null) {
return defaultValue;
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public long getLong(String namespace, String name, long defaultValue) {
String value = getProperty(namespace, name);
if (value == null) {
return defaultValue;
}
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public float getFloat(String namespace, String name, float defaultValue) {
String value = getProperty(namespace, name);
if (value == null) {
return defaultValue;
}
try {
return Float.parseFloat(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public boolean getBoolean(String namespace, String name, boolean defaultValue) {
String value = getProperty(namespace, name);
return value != null ? Boolean.parseBoolean(value) : defaultValue;
}
@Override
public void addOnPropertiesChangedListener(String namespace, Executor executor,
DeviceConfig.OnPropertiesChangedListener listener) {
Pair<String, Executor> oldNamespace = mListeners.get(listener);
if (oldNamespace == null) {
// Brand new listener, add it to the list.
mListeners.put(listener, new Pair<>(namespace, executor));
} else if (namespace.equals(oldNamespace.first)) {
// Listener is already registered for this namespace, update executor just in case.
mListeners.put(listener, new Pair<>(namespace, executor));
} else {
// DeviceConfig allows re-registering listeners for different namespaces, but that
// silently unregisters the prior namespace. This likely isn't something the caller
// intended.
throw new IllegalStateException("Listener " + listener + " already registered. This"
+ "is technically allowed by DeviceConfig, but likely indicates a logic "
+ "error.");
}
}
@Override
public void removeOnPropertiesChangedListener(
DeviceConfig.OnPropertiesChangedListener listener) {
mListeners.remove(listener);
}
}