• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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