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 * Run a Runnable, with DeviceConfig.setSyncDisabledMode(SYNC_DISABLED_MODE_NONE), 68 * with all the shell permissions. 69 */ callWithSyncEnabledWithShellPermissions(RunnableWithThrow r)70 private void callWithSyncEnabledWithShellPermissions(RunnableWithThrow r) { 71 SystemUtil.runWithShellPermissionIdentity(() -> { 72 final int originalSyncMode = DeviceConfig.getSyncDisabledMode(); 73 try { 74 // TODO: Use DeviceConfig.setSyncDisabledMode, once the SYNC_* constants 75 // are exposed. 76 ShellUtils.runShellCommand("cmd device_config set_sync_disabled_for_tests none"); 77 78 r.run(); 79 } finally { 80 DeviceConfig.setSyncDisabledMode(originalSyncMode); 81 } 82 }); 83 } 84 set(@onNull DeviceConfig.Properties properties)85 public void set(@NonNull DeviceConfig.Properties properties) { 86 synchronized (mOriginalValues) { 87 for (String key : properties.getKeyset()) { 88 maybeCacheOriginalValueLocked(key); 89 } 90 } 91 callWithSyncEnabledWithShellPermissions( 92 () -> assertTrue(DeviceConfig.setProperties(properties))); 93 } 94 restoreOriginalValues()95 public void restoreOriginalValues() { 96 final DeviceConfig.Properties.Builder builder = 97 new DeviceConfig.Properties.Builder(mNamespace); 98 synchronized (mOriginalValues) { 99 for (int i = 0; i < mOriginalValues.size(); ++i) { 100 builder.setString(mOriginalValues.keyAt(i), mOriginalValues.valueAt(i)); 101 } 102 mOriginalValues.clear(); 103 } 104 callWithSyncEnabledWithShellPermissions( 105 () -> assertTrue(DeviceConfig.setProperties(builder.build()))); 106 } 107 108 @Override close()109 public void close() throws Exception { 110 restoreOriginalValues(); 111 } 112 } 113