1 /*
2  * Copyright (C) 2020 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.compatibility.common.util;
18 
19 import static org.junit.Assert.assertTrue;
20 
21 import android.provider.DeviceConfig;
22 import android.util.ArrayMap;
23 
24 import androidx.annotation.GuardedBy;
25 import androidx.annotation.NonNull;
26 import androidx.annotation.Nullable;
27 
28 import com.android.compatibility.common.util.TestUtils.RunnableWithThrow;
29 
30 import java.util.Objects;
31 
32 /**
33  * Helper to automatically save multiple existing DeviceConfig values, change them during tests, and
34  * restore the original values after the test.
35  */
36 public class DeviceConfigStateHelper implements AutoCloseable {
37     private final String mNamespace;
38     @GuardedBy("mOriginalValues")
39     private final ArrayMap<String, String> mOriginalValues = new ArrayMap<>();
40 
41     /**
42      * @param namespace DeviceConfig namespace.
43      */
DeviceConfigStateHelper(@onNull String namespace)44     public DeviceConfigStateHelper(@NonNull String namespace) {
45         mNamespace = Objects.requireNonNull(namespace);
46     }
47 
maybeCacheOriginalValueLocked(String key)48     private void maybeCacheOriginalValueLocked(String key) {
49         if (!mOriginalValues.containsKey(key)) {
50             // Only save the current value if we haven't changed it.
51             final String ogValue = SystemUtil.runWithShellPermissionIdentity(
52                     () -> DeviceConfig.getProperty(mNamespace, key));
53             mOriginalValues.put(key, ogValue);
54         }
55     }
56 
set(@onNull String key, @Nullable String value)57     public void set(@NonNull String key, @Nullable String value) {
58         synchronized (mOriginalValues) {
59             maybeCacheOriginalValueLocked(key);
60         }
61         SystemUtil.runWithShellPermissionIdentity(
62                 () -> assertTrue(
63                         DeviceConfig.setProperty(mNamespace, key, value, /* makeDefault */false)));
64     }
65 
66     /**
67      * Resets the value of the given key if was ever modified and returns {@code true} on success.
68      */
reset(@onNull String key)69     public boolean reset(@NonNull String key) {
70         final String ogValue;
71         synchronized (mOriginalValues) {
72             ogValue = mOriginalValues.get(key);
73             if (ogValue == null) {
74                 return false;
75             }
76         }
77         set(key, ogValue);
78         return true;
79     }
80 
81     /**
82      * Run a Runnable, with DeviceConfig.setSyncDisabledMode(SYNC_DISABLED_MODE_NONE),
83      * with all the shell permissions.
84      */
callWithSyncEnabledWithShellPermissions(RunnableWithThrow r)85     public static void callWithSyncEnabledWithShellPermissions(RunnableWithThrow r) {
86         SystemUtil.runWithShellPermissionIdentity(() -> {
87             final String originalSyncMode = ShellUtils.runShellCommand(
88                     "device_config get_sync_disabled_for_tests");
89             try {
90                 // TODO: Use DeviceConfig.setSyncDisabledMode, once the SYNC_* constants
91                 // are exposed.
92                 ShellUtils.runShellCommand("cmd device_config set_sync_disabled_for_tests none");
93 
94                 r.run();
95             } finally {
96                 ShellUtils.runShellCommand(
97                         "device_config set_sync_disabled_for_tests %s", originalSyncMode);
98             }
99         });
100     }
101 
set(@onNull DeviceConfig.Properties properties)102     public void set(@NonNull DeviceConfig.Properties properties) {
103         synchronized (mOriginalValues) {
104             for (String key : properties.getKeyset()) {
105                 maybeCacheOriginalValueLocked(key);
106             }
107         }
108         callWithSyncEnabledWithShellPermissions(
109                 () -> assertTrue(DeviceConfig.setProperties(properties)));
110     }
111 
restoreOriginalValues()112     public void restoreOriginalValues() {
113         final DeviceConfig.Properties.Builder builder =
114                 new DeviceConfig.Properties.Builder(mNamespace);
115         synchronized (mOriginalValues) {
116             for (int i = 0; i < mOriginalValues.size(); ++i) {
117                 builder.setString(mOriginalValues.keyAt(i), mOriginalValues.valueAt(i));
118             }
119             mOriginalValues.clear();
120         }
121         callWithSyncEnabledWithShellPermissions(
122                 () -> assertTrue(DeviceConfig.setProperties(builder.build())));
123     }
124 
125     @Override
close()126     public void close() throws Exception {
127         restoreOriginalValues();
128     }
129 }
130