1 /* 2 * Copyright (C) 2017 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 org.junit.Assert.fail; 20 21 import com.android.compatibility.common.util.BackupHostSideUtils; 22 import com.android.compatibility.common.util.BackupUtils; 23 import com.android.tradefed.build.IBuildInfo; 24 import com.android.tradefed.config.Option; 25 import com.android.tradefed.config.OptionClass; 26 import com.android.tradefed.device.CollectingOutputReceiver; 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.BaseTargetPreparer; 31 import com.android.tradefed.targetprep.BuildError; 32 import com.android.tradefed.targetprep.TargetSetupError; 33 import com.android.tradefed.util.RunUtil; 34 35 import java.io.IOException; 36 import java.util.concurrent.Callable; 37 import java.util.concurrent.CompletionException; 38 import java.util.concurrent.TimeUnit; 39 import java.util.function.Function; 40 import java.util.regex.Matcher; 41 import java.util.regex.Pattern; 42 43 /** 44 * Tradedfed target preparer for the backup tests. Enables backup before all the tests and selects 45 * local transport. Reverts to the original state after all the tests are executed. 46 */ 47 @OptionClass(alias = "backup-preparer") 48 public class BackupPreparer extends BaseTargetPreparer { 49 private static final long TRANSPORT_AVAILABLE_TIMEOUT_SECONDS = TimeUnit.MINUTES.toSeconds(5); 50 @Option(name="enable-backup-if-needed", description= 51 "Enable backup before all the tests and return to the original state after.") 52 private boolean mEnableBackup = true; 53 54 @Option(name="select-local-transport", description= 55 "Select local transport before all the tests and return to the original transport " 56 + "after.") 57 private boolean mSelectLocalTransport = true; 58 59 /** Value of PackageManager.FEATURE_BACKUP */ 60 private static final String FEATURE_BACKUP = "android.software.backup"; 61 62 private static final String LOCAL_TRANSPORT = 63 "com.android.localtransport/.LocalTransport"; 64 private final int USER_SYSTEM = 0; 65 66 private boolean mIsBackupSupported; 67 private boolean mWasBackupEnabled; 68 private String mOldTransport; 69 private ITestDevice mDevice; 70 private BackupUtils mBackupUtils; 71 72 @Override setUp(ITestDevice device, IBuildInfo buildInfo)73 public void setUp(ITestDevice device, IBuildInfo buildInfo) 74 throws TargetSetupError, BuildError, DeviceNotAvailableException { 75 mDevice = device; 76 mBackupUtils = BackupHostSideUtils.createBackupUtils(mDevice); 77 mIsBackupSupported = mDevice.hasFeature("feature:" + FEATURE_BACKUP); 78 79 // In case the device was just rebooted, wait for the broadcast queue to get idle to avoid 80 // any interference from services doing backup clean up on reboot. 81 waitForBroadcastIdle(); 82 83 if (mIsBackupSupported) { 84 BackupHostSideUtils.checkSetupComplete(mDevice); 85 if (!isBackupActiveForSysytemUser()) { 86 throw new TargetSetupError("Cannot run test as backup is not active for system " 87 + "user", device.getDeviceDescriptor()); 88 } 89 90 // Enable backup and select local backup transport 91 waitForTransport(LOCAL_TRANSPORT); 92 93 if (mEnableBackup) { 94 CLog.i("Enabling backup on %s", mDevice.getSerialNumber()); 95 mWasBackupEnabled = enableBackup(true); 96 CLog.d("Backup was enabled? : %s", mWasBackupEnabled); 97 if (mSelectLocalTransport) { 98 CLog.i("Selecting local transport on %s", mDevice.getSerialNumber()); 99 mOldTransport = setBackupTransport(LOCAL_TRANSPORT); 100 CLog.d("Old transport : %s", mOldTransport); 101 } 102 try { 103 mBackupUtils.waitForBackupInitialization(); 104 } catch (IOException e) { 105 throw new TargetSetupError("Backup not initialized", e); 106 } 107 } 108 } 109 } 110 111 @Override tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)112 public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) 113 throws DeviceNotAvailableException { 114 mDevice = device; 115 116 if (mIsBackupSupported) { 117 if (mEnableBackup) { 118 CLog.i("Returning backup to it's previous state on %s", 119 mDevice.getSerialNumber()); 120 enableBackup(mWasBackupEnabled); 121 if (mSelectLocalTransport) { 122 CLog.i("Returning selected transport to it's previous value on %s", 123 mDevice.getSerialNumber()); 124 setBackupTransport(mOldTransport); 125 } 126 } 127 } 128 } 129 waitForTransport(String transport)130 private void waitForTransport(String transport) throws TargetSetupError { 131 try { 132 waitUntilWithLastTry( 133 "Local transport didn't become available", 134 TRANSPORT_AVAILABLE_TIMEOUT_SECONDS, 135 lastTry -> uncheck(() -> hasBackupTransport(transport, lastTry))); 136 } catch (InterruptedException e) { 137 throw new TargetSetupError( 138 "Device should have LocalTransport available", mDevice.getDeviceDescriptor()); 139 } 140 } 141 hasBackupTransport(String transport, boolean logIfFail)142 private boolean hasBackupTransport(String transport, boolean logIfFail) 143 throws DeviceNotAvailableException, TargetSetupError { 144 String output = mDevice.executeShellCommand("bmgr list transports"); 145 for (String t : output.split(" ")) { 146 if (transport.equals(t.trim())) { 147 return true; 148 } 149 } 150 if (logIfFail) { 151 throw new TargetSetupError( 152 transport + " not available. bmgr list transports: " + output, 153 mDevice.getDeviceDescriptor()); 154 } 155 return false; 156 } 157 158 /** 159 * Calls {@code predicate} with {@code false} until time-out {@code timeoutSeconds} is reached, 160 * if {@code predicate} returns true, method returns. If time-out is reached before that, we 161 * call {@code predicate} with {@code true} one last time, if that last call returns false we 162 * fail with {@code message}. 163 * 164 * TODO: Move to CommonTestUtils 165 */ waitUntilWithLastTry( String message, long timeoutSeconds, Function<Boolean, Boolean> predicate)166 private static void waitUntilWithLastTry( 167 String message, long timeoutSeconds, Function<Boolean, Boolean> predicate) 168 throws InterruptedException { 169 int sleep = 125; 170 final long timeout = System.currentTimeMillis() + timeoutSeconds * 1000; 171 while (System.currentTimeMillis() < timeout) { 172 if (predicate.apply(false)) { 173 return; 174 } 175 RunUtil.getDefault().sleep(sleep); 176 } 177 if (!predicate.apply(true)) { 178 fail(message); 179 } 180 } 181 182 // Copied over from BackupQuotaTest enableBackup(boolean enable)183 private boolean enableBackup(boolean enable) throws DeviceNotAvailableException { 184 boolean previouslyEnabled; 185 String output = mDevice.executeShellCommand("bmgr enabled"); 186 Pattern pattern = Pattern.compile("^Backup Manager currently (enabled|disabled)$"); 187 Matcher matcher = pattern.matcher(output.trim()); 188 if (matcher.find()) { 189 previouslyEnabled = "enabled".equals(matcher.group(1)); 190 } else { 191 throw new RuntimeException("non-parsable output setting bmgr enabled: " + output); 192 } 193 194 mDevice.executeShellCommand("bmgr enable " + enable); 195 return previouslyEnabled; 196 } 197 198 // Copied over from BackupQuotaTest setBackupTransport(String transport)199 private String setBackupTransport(String transport) throws DeviceNotAvailableException { 200 String output = mDevice.executeShellCommand("bmgr transport " + transport); 201 Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$"); 202 Matcher matcher = pattern.matcher(output); 203 if (matcher.find()) { 204 return matcher.group(1); 205 } else { 206 throw new RuntimeException("non-parsable output setting bmgr transport: " + output); 207 } 208 } 209 210 // Copied over from BaseDevicePolicyTest waitForBroadcastIdle()211 private void waitForBroadcastIdle() throws DeviceNotAvailableException, TargetSetupError { 212 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 213 try { 214 // we allow 20 min for the command to complete and 10 min for the command to start to 215 // output something 216 mDevice.executeShellCommand( 217 "am wait-for-broadcast-idle", receiver, 20, 10, TimeUnit.MINUTES, 0); 218 } finally { 219 String output = receiver.getOutput(); 220 CLog.d("Output from 'am wait-for-broadcast-idle': %s", output); 221 if (!output.contains("All broadcast queues are idle!")) { 222 // the call most likely failed we should fail the test 223 throw new TargetSetupError("'am wait-for-broadcase-idle' did not complete.", 224 mDevice.getDeviceDescriptor()); 225 // TODO: consider adding a reboot or recovery before failing if necessary 226 } 227 } 228 } 229 isBackupActiveForSysytemUser()230 private boolean isBackupActiveForSysytemUser() { 231 try { 232 return mBackupUtils.isBackupActivatedForUser(USER_SYSTEM); 233 } catch (IOException e) { 234 throw new RuntimeException("Failed to check backup activation status"); 235 } 236 } 237 uncheck(Callable<T> callable)238 private static <T> T uncheck(Callable<T> callable) { 239 try { 240 return callable.call(); 241 } catch (RuntimeException e) { 242 throw e; 243 } catch (Exception e) { 244 throw new CompletionException(e); 245 } 246 } 247 } 248