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