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