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