1 /* 2 * Copyright (C) 2024 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 android.cts.credentials.backuprestore; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assume.assumeFalse; 23 import static org.junit.Assert.assertTrue; 24 25 import android.platform.test.annotations.AppModeFull; 26 27 import com.android.compatibility.common.util.BackupHostSideUtils; 28 import com.android.compatibility.common.util.BackupUtils; 29 import com.android.tradefed.device.DeviceNotAvailableException; 30 import com.android.tradefed.device.ITestDevice; 31 import com.android.tradefed.log.LogUtil.CLog; 32 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 33 import com.android.tradefed.testtype.ITestInformationReceiver; 34 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 35 36 import org.junit.After; 37 import org.junit.AssumptionViolatedException; 38 import org.junit.Before; 39 import org.junit.Rule; 40 import org.junit.Test; 41 import org.junit.rules.TestRule; 42 import org.junit.runner.Description; 43 import org.junit.runner.RunWith; 44 import org.junit.runners.model.Statement; 45 46 import java.io.ByteArrayInputStream; 47 import java.io.IOException; 48 import java.io.InputStream; 49 import java.nio.charset.StandardCharsets; 50 import java.util.regex.Matcher; 51 import java.util.regex.Pattern; 52 53 /** Verifies that Credential Manager settings are restored correctly. */ 54 @RunWith(DeviceJUnit4ClassRunner.class) 55 @AppModeFull 56 public class CredentialManagerRestoreSettingsHostSideTest extends BaseHostJUnit4Test { 57 /** Value of PackageManager.FEATURE_BACKUP */ 58 private static final String FEATURE_BACKUP = "android.software.backup"; 59 60 /** Value of PackageManager.FEATURE_CREDENTIALS */ 61 private static final String FEATURE_CREDENTIALS = "android.software.credentials"; 62 63 protected static final String LOCAL_TRANSPORT = "com.android.localtransport/.LocalTransport"; 64 65 @Rule 66 public final RequiredFeatureRule mBackupRequiredRule = 67 new RequiredFeatureRule(this, FEATURE_BACKUP); 68 69 @Rule 70 public final RequiredFeatureRule mCredManRequiredRule = 71 new RequiredFeatureRule(this, FEATURE_CREDENTIALS); 72 73 private static final String SETTINGS_PACKAGE = "com.android.providers.settings"; 74 private static final String TEST_APP_PACKAGE = "android.cts.credentials.backuprestoreapp"; 75 private static final String TEST_APP_APK = "CtsCredentialManagerBackupRestoreApp.apk"; 76 77 private static final String AUTOFILL_SETTING_NAME = "autofill_service"; 78 private static final String CREDMAN_SETTING_NAME = "credential_service"; 79 private static final String CREDMAN_PRIMARY_SETTING_NAME = "credential_service_primary"; 80 private static final String SETTINGS_DO_NOT_RESTORE_PRESERVED_SETTING_NAME = 81 "settings_do_not_restore_preserved"; 82 83 private static final String AUTOFILL_TEST_SERVICE = "com.example.test/.AutofillService"; 84 private static final String CREDMAN_TEST_SERVICE = 85 "com.example.test/.ServiceA:com.example.test/.ServiceB"; 86 private static final String CREDMAN_TEST_PRIMARY_SERVICE = "com.example.test/.ServiceA"; 87 private static final String NEW_SETTINGS_VALUE = "com.example.test2/.Service"; 88 89 private static final String SECURE_NAMESPACE = "secure"; 90 private static final String GLOBAL_NAMESPACE = "global"; 91 92 private String mOriginalFeatureFlagValue = ""; 93 94 private BackupUtils mBackupUtils = 95 new BackupUtils() { 96 @Override 97 protected InputStream executeShellCommand(String command) throws IOException { 98 return executeDeviceShellCommand(getDevice(), command); 99 } 100 }; 101 102 @Before setUp()103 public void setUp() throws Exception { 104 assumeFalse("Skipping test not supported on HSUM devices.", 105 getDevice().isHeadlessSystemUserMode()); 106 107 mOriginalFeatureFlagValue = 108 getSettingValue(GLOBAL_NAMESPACE, SETTINGS_DO_NOT_RESTORE_PRESERVED_SETTING_NAME); 109 setSettingValue( 110 GLOBAL_NAMESPACE, 111 SETTINGS_DO_NOT_RESTORE_PRESERVED_SETTING_NAME, 112 Boolean.TRUE.toString()); 113 114 BackupHostSideUtils.checkSetupComplete(getDevice()); 115 116 mBackupUtils.enableBackup(true); 117 mBackupUtils.activateBackupForUser(true, 0); 118 mBackupUtils.setBackupTransportForUser(mBackupUtils.getLocalTransportName(), 0); 119 120 // Check that the backup wasn't disabled and the transport wasn't switched unexpectedly. 121 assertTrue( 122 "Backup was unexpectedly disabled during the module test run", 123 mBackupUtils.isBackupEnabled()); 124 assertEquals( 125 "LocalTransport should be selected at this point", 126 LOCAL_TRANSPORT, 127 getCurrentTransport()); 128 mBackupUtils.wakeAndUnlockDevice(); 129 } 130 131 @After tearDown()132 public void tearDown() throws Exception { 133 setSettingValue( 134 GLOBAL_NAMESPACE, 135 SETTINGS_DO_NOT_RESTORE_PRESERVED_SETTING_NAME, 136 mOriginalFeatureFlagValue); 137 } 138 139 @Test testSettingsAreRestoredCorrectly()140 public void testSettingsAreRestoredCorrectly() throws Exception { 141 // 1. Set the CredMan settings before backup. 142 setSecureSettingValue(AUTOFILL_SETTING_NAME, AUTOFILL_TEST_SERVICE); 143 setSecureSettingValue(CREDMAN_SETTING_NAME, CREDMAN_TEST_SERVICE); 144 setSecureSettingValue(CREDMAN_PRIMARY_SETTING_NAME, CREDMAN_TEST_PRIMARY_SERVICE); 145 146 // 2. Run the backup. 147 mBackupUtils.backupNowAndAssertSuccess(SETTINGS_PACKAGE); 148 149 // 3. Clear the credman settings. 150 getDevice().executeShellCommand("settings delete secure autofill_service"); 151 getDevice().executeShellCommand("settings delete secure credential_service"); 152 getDevice().executeShellCommand("settings delete secure credential_service_primary"); 153 154 // 4. Install & remove a test app. This will trigger some logic in Credential Manager 155 // that will update the setting values. 156 installPackage(TEST_APP_APK); 157 assertThat(isPackageInstalled(TEST_APP_PACKAGE)).isTrue(); 158 uninstallPackage(TEST_APP_PACKAGE); 159 160 // 5. Restore the backup. 161 mBackupUtils.restoreAndAssertSuccess("1", SETTINGS_PACKAGE); 162 163 // 6. Make sure the settings were not overridden. 164 assertSameComponentName( 165 getSecureSettingValue(AUTOFILL_SETTING_NAME), AUTOFILL_TEST_SERVICE); 166 assertSameComponentName(getSecureSettingValue(CREDMAN_SETTING_NAME), CREDMAN_TEST_SERVICE); 167 assertSameComponentName( 168 getSecureSettingValue(CREDMAN_PRIMARY_SETTING_NAME), CREDMAN_TEST_PRIMARY_SERVICE); 169 } 170 171 @Test testSettingsAreNotRestoredIfUserHasChangedThem()172 public void testSettingsAreNotRestoredIfUserHasChangedThem() throws Exception { 173 // 1. Set the CredMan settings before backup. 174 setSecureSettingValue(AUTOFILL_SETTING_NAME, AUTOFILL_TEST_SERVICE); 175 setSecureSettingValue(CREDMAN_SETTING_NAME, CREDMAN_TEST_SERVICE); 176 setSecureSettingValue(CREDMAN_PRIMARY_SETTING_NAME, CREDMAN_TEST_PRIMARY_SERVICE); 177 178 // 2. Run the backup. 179 mBackupUtils.backupNowAndAssertSuccess(SETTINGS_PACKAGE); 180 181 // 3. Simulate the user changing the settings. 182 setSecureSettingValue(AUTOFILL_SETTING_NAME, NEW_SETTINGS_VALUE); 183 setSecureSettingValue(CREDMAN_SETTING_NAME, NEW_SETTINGS_VALUE); 184 setSecureSettingValue(CREDMAN_PRIMARY_SETTING_NAME, NEW_SETTINGS_VALUE); 185 186 // 4. Restore the backup. 187 mBackupUtils.restoreAndAssertSuccess("1", SETTINGS_PACKAGE); 188 189 // 5. Make sure the settings were not overridden. 190 assertSameComponentName(getSecureSettingValue(AUTOFILL_SETTING_NAME), NEW_SETTINGS_VALUE); 191 assertSameComponentName(getSecureSettingValue(CREDMAN_SETTING_NAME), NEW_SETTINGS_VALUE); 192 assertSameComponentName( 193 getSecureSettingValue(CREDMAN_PRIMARY_SETTING_NAME), NEW_SETTINGS_VALUE); 194 } 195 assertSameComponentName(String one, String two)196 private void assertSameComponentName(String one, String two) { 197 assertThat(normalizedString(one)) 198 .isEqualTo(normalizedString(two)); 199 } 200 normalizedString(String str)201 private static String normalizedString(String str) { 202 int sep = str.indexOf(':'); 203 if (sep < 0 || (sep + 1) >= str.length()) { 204 return normalizedSingleString(str); 205 } 206 String p1 = str.substring(0, sep); 207 String p2 = str.substring(sep + 1); 208 return normalizedSingleString(p1) + ":" + normalizedSingleString(p2); 209 } 210 normalizedSingleString(String str)211 private static String normalizedSingleString(String str) { 212 int sep = str.indexOf('/'); 213 if (sep < 0 || (sep + 1) >= str.length()) { 214 return ""; 215 } 216 String pkg = str.substring(0, sep); 217 String cls = str.substring(sep + 1); 218 if (cls.length() > 0 && cls.charAt(0) == '.') { 219 cls = pkg + cls; 220 } 221 return cls; 222 } 223 getSecureSettingValue(String name)224 private String getSecureSettingValue(String name) throws Exception { 225 return getSettingValue(SECURE_NAMESPACE, name); 226 } 227 getSettingValue(String namespace, String name)228 private String getSettingValue(String namespace, String name) throws Exception { 229 return getDevice() 230 .executeShellCommand("settings get " + namespace + " " + name) 231 .replace("\n", ""); 232 } 233 setSecureSettingValue(String name, String value)234 private void setSecureSettingValue(String name, String value) throws Exception { 235 setSettingValue(SECURE_NAMESPACE, name, value); 236 } 237 setSettingValue(String namespace, String name, String value)238 private void setSettingValue(String namespace, String name, String value) throws Exception { 239 getDevice().executeShellCommand("settings put " + namespace + " " + name + " " + value); 240 } 241 getCurrentTransport()242 protected String getCurrentTransport() throws DeviceNotAvailableException { 243 String output = getDevice().executeShellCommand("bmgr list transports"); 244 Pattern pattern = Pattern.compile("\\* (.*)"); 245 Matcher matcher = pattern.matcher(output); 246 if (matcher.find()) { 247 return matcher.group(1); 248 } else { 249 throw new RuntimeException("non-parsable output setting bmgr transport: " + output); 250 } 251 } 252 executeDeviceShellCommand(ITestDevice device, String command)253 static InputStream executeDeviceShellCommand(ITestDevice device, String command) 254 throws IOException { 255 try { 256 String result = device.executeShellCommand(command); 257 return new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)); 258 } catch (DeviceNotAvailableException e) { 259 throw new IOException(e); 260 } 261 } 262 263 private static final class RequiredFeatureRule implements TestRule { 264 265 private final ITestInformationReceiver mReceiver; 266 private final String mFeature; 267 RequiredFeatureRule(ITestInformationReceiver receiver, String feature)268 RequiredFeatureRule(ITestInformationReceiver receiver, String feature) { 269 mReceiver = receiver; 270 mFeature = feature; 271 } 272 273 @Override apply(Statement base, Description description)274 public Statement apply(Statement base, Description description) { 275 return new Statement() { 276 277 @Override 278 public void evaluate() throws Throwable { 279 boolean hasFeature = false; 280 try { 281 hasFeature = 282 mReceiver.getTestInformation().getDevice().hasFeature(mFeature); 283 } catch (DeviceNotAvailableException e) { 284 CLog.e("Could not check if device has feature %s: %e", mFeature, e); 285 return; 286 } 287 288 if (!hasFeature) { 289 CLog.d( 290 "skipping %s#%s" + " because device does not have feature '%s'", 291 description.getClassName(), description.getMethodName(), mFeature); 292 throw new AssumptionViolatedException( 293 "Device does not have feature '" + mFeature + "'"); 294 } 295 base.evaluate(); 296 } 297 }; 298 } 299 300 @Override toString()301 public String toString() { 302 return "RequiredFeatureRule[" + mFeature + "]"; 303 } 304 } 305 } 306