1 /*
2  * Copyright (C) 2019 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.backup;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assume.assumeTrue;
22 
23 import android.platform.test.annotations.AppModeFull;
24 
25 import com.android.compatibility.common.util.BackupUtils;
26 import com.android.compatibility.common.util.CommonTestUtils;
27 import com.android.tradefed.device.DeviceNotAvailableException;
28 import com.android.tradefed.device.ITestDevice;
29 import com.android.tradefed.log.LogUtil.CLog;
30 import com.android.tradefed.targetprep.TargetSetupError;
31 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
32 
33 import org.junit.After;
34 import org.junit.Before;
35 import org.junit.runner.RunWith;
36 
37 import java.io.IOException;
38 import java.util.Optional;
39 import java.util.concurrent.TimeUnit;
40 
41 /** Base class for CTS multi-user backup/restore host-side tests. */
42 @RunWith(DeviceJUnit4ClassRunner.class)
43 @AppModeFull
44 public abstract class BaseMultiUserBackupHostSideTest extends BaseBackupHostSideTest {
45     private static final String USER_SETUP_COMPLETE_SETTING = "user_setup_complete";
46     private static final long TRANSPORT_INITIALIZATION_TIMEOUT_SECS = TimeUnit.MINUTES.toSeconds(2);
47 
48     // Key-value test package.
49     static final String KEY_VALUE_APK = "CtsProfileKeyValueApp.apk";
50     static final String KEY_VALUE_TEST_PACKAGE = "android.cts.backup.profilekeyvalueapp";
51     static final String KEY_VALUE_DEVICE_TEST_NAME =
52             KEY_VALUE_TEST_PACKAGE + ".ProfileKeyValueBackupRestoreTest";
53 
54     // Full backup test package.
55     static final String FULL_BACKUP_APK = "CtsProfileFullBackupApp.apk";
56     static final String FULL_BACKUP_TEST_PACKAGE = "android.cts.backup.profilefullbackupapp";
57     static final String FULL_BACKUP_DEVICE_TEST_NAME =
58             FULL_BACKUP_TEST_PACKAGE + ".ProfileFullBackupRestoreTest";
59 
60     protected final BackupUtils mBackupUtils = getBackupUtils();
61     private ITestDevice mDevice;
62 
63     // Store initial device state as Optional as tearDown() will execute even if we have assumption
64     // failures in setUp().
65     private Optional<Integer> mInitialUser = Optional.empty();
66 
67     /** Check device features and keep track of pre-test device state. */
68     @Before
setUp()69     public void setUp() throws Exception {
70         mDevice = getDevice();
71         super.setUp();
72 
73         // Check that backup and multi-user features are both supported.
74         assumeTrue("Multi-user feature not supported", mDevice.isMultiUserSupported());
75 
76         // Keep track of initial user state to restore in tearDown.
77         int currentUserId = mDevice.getCurrentUser();
78         mInitialUser = Optional.of(currentUserId);
79 
80         // Switch to primary user.
81         int primaryUserId = mDevice.getPrimaryUserId();
82         if (currentUserId != primaryUserId) {
83             mDevice.switchUser(primaryUserId);
84         }
85     }
86 
87     /** Restore pre-test device state. */
88     @After
tearDown()89     public void tearDown() throws Exception {
90         if (mInitialUser.isPresent()) {
91             mDevice.switchUser(mInitialUser.get());
92             mInitialUser = Optional.empty();
93         }
94     }
95 
96     /**
97      * Attempts to create a profile tied to the parent user {@code parentId}. Returns the user id of
98      * the profile if successful, otherwise throws a {@link RuntimeException}.
99      */
createProfileUser(int parentId, String profileName)100     int createProfileUser(int parentId, String profileName) throws IOException {
101         String output =
102                 mBackupUtils.executeShellCommandAndReturnOutput(
103                         String.format("pm create-user --profileOf %d %s", parentId, profileName));
104         try {
105             // Expected output is "Success: created user id <id>"
106             String userId = output.substring(output.lastIndexOf(" ")).trim();
107             return Integer.parseInt(userId);
108         } catch (NumberFormatException e) {
109             CLog.d("Failed to parse user id when creating a profile user");
110             throw new RuntimeException(output, e);
111         }
112     }
113 
114     /** Start the user. */
startUser(int userId)115     void startUser(int userId) throws DeviceNotAvailableException {
116         boolean startSuccessful = mDevice.startUser(userId, /* wait for RUNNING_UNLOCKED */ true);
117         assertThat(startSuccessful).isTrue();
118 
119         mDevice.setSetting(userId, "secure", USER_SETUP_COMPLETE_SETTING, "1");
120     }
121 
122     /** Start the user and set necessary conditions for backup to be enabled in the user. */
startUserAndInitializeForBackup(int userId)123     void startUserAndInitializeForBackup(int userId)
124             throws IOException, DeviceNotAvailableException, InterruptedException {
125         // Turn on multi-user feature for this user.
126         mBackupUtils.activateBackupForUser(true, userId);
127 
128         startUser(userId);
129 
130         mBackupUtils.waitUntilBackupServiceIsRunning(userId);
131 
132         mBackupUtils.enableBackupForUser(true, userId);
133         assertThat(mBackupUtils.isBackupEnabledForUser(userId)).isTrue();
134     }
135 
136     /**
137      * Selects the local transport as the current transport for user {@code userId}. Returns the
138      * {@link String} name of the local transport.
139      */
switchUserToLocalTransportAndAssertSuccess(int userId)140     String switchUserToLocalTransportAndAssertSuccess(int userId)
141             throws Exception {
142         // Make sure the user has the local transport.
143         String localTransport = mBackupUtils.getLocalTransportName();
144 
145         // TODO (b/121198010): Update dumpsys or add shell command to query status of transport
146         // initialization. Transports won't be available until they are initialized/registered.
147         CommonTestUtils.waitUntil("wait for user to have local transport",
148                 TRANSPORT_INITIALIZATION_TIMEOUT_SECS,
149                 () -> userHasBackupTransport(localTransport, userId));
150 
151         // Switch to the local transport and assert success.
152         mBackupUtils.setBackupTransportForUser(localTransport, userId);
153         assertThat(mBackupUtils.isLocalTransportSelectedForUser(userId)).isTrue();
154 
155         return localTransport;
156     }
157 
158     // TODO(b/139652329): Move to backup utils.
userHasBackupTransport( String transport, int userId)159     private boolean userHasBackupTransport(
160             String transport, int userId) throws DeviceNotAvailableException {
161         String output = mDevice.executeShellCommand("bmgr --user " + userId + " list transports");
162         for (String t : output.split(" ")) {
163             if (transport.equals(t.replace("*", "").trim())) {
164                 return true;
165             }
166         }
167         return false;
168     }
169 
170     /** Runs "bmgr --user <id> wipe <transport> <package>" to clear the backup data. */
clearBackupDataInTransportForUser(String packageName, String transport, int userId)171     void clearBackupDataInTransportForUser(String packageName, String transport, int userId)
172             throws DeviceNotAvailableException {
173         mDevice.executeShellCommand(
174                 String.format("bmgr --user %d wipe %s %s", userId, transport, packageName));
175     }
176 
177     /** Clears data of {@code packageName} for user {@code userId}. */
clearPackageDataAsUser(String packageName, int userId)178     void clearPackageDataAsUser(String packageName, int userId) throws DeviceNotAvailableException {
179         mDevice.executeShellCommand(String.format("pm clear --user %d %s", userId, packageName));
180     }
181 
182     /** Installs {@code apk} for user {@code userId} and allows replacements and downgrades. */
installPackageAsUser(String apk, int userId)183     void installPackageAsUser(String apk, int userId)
184             throws DeviceNotAvailableException, TargetSetupError {
185         installPackageAsUser(apk, false, userId, "-r", "-d");
186     }
187 
188     /** Uninstalls {@code packageName} for user {@code userId}. */
uninstallPackageAsUser(String packageName, int userId)189     void uninstallPackageAsUser(String packageName, int userId) throws DeviceNotAvailableException {
190         mDevice.executeShellCommand(
191                 String.format("pm uninstall --user %d %s", userId, packageName));
192     }
193 
194     /**
195      * Installs existing {@code packageName} for user {@code userId}. This fires off asynchronous
196      * restore and returns before the restore operation has finished.
197      */
installExistingPackageAsUser(String packageName, int userId)198     void installExistingPackageAsUser(String packageName, int userId)
199             throws DeviceNotAvailableException {
200         mDevice.executeShellCommand(
201                 String.format("pm install-existing --user %d %s", userId, packageName));
202     }
203 
204     /**
205      * Installs existing {@code packageName} for user {@code userId}. This fires off asynchronous
206      * restore and returns after it receives an intent which is sent when the restore operation has
207      * completed.
208      */
installExistingPackageAsUserWaitTillComplete(String packageName, int userId)209     void installExistingPackageAsUserWaitTillComplete(String packageName, int userId)
210             throws DeviceNotAvailableException {
211         mDevice.executeShellCommand(
212                 String.format("pm install-existing --user %d --wait %s", userId, packageName));
213     }
214 
215     /** Run device side test as user {@code userId}. */
checkDeviceTestAsUser(String packageName, String className, String testName, int userId)216     void checkDeviceTestAsUser(String packageName, String className, String testName, int userId)
217             throws DeviceNotAvailableException {
218         boolean result = runDeviceTests(mDevice, packageName, className, testName, userId, null);
219         assertThat(result).isTrue();
220     }
221 }
222