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 com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.config.Option;
21 import com.android.tradefed.config.OptionClass;
22 import com.android.tradefed.device.CollectingOutputReceiver;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.device.ITestDevice;
25 import com.android.tradefed.log.LogUtil.CLog;
26 import com.android.tradefed.targetprep.BuildError;
27 import com.android.tradefed.targetprep.ITargetCleaner;
28 import com.android.tradefed.targetprep.TargetSetupError;
29 
30 import java.util.concurrent.TimeUnit;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33 
34 /**
35  * Tradedfed target preparer for the backup tests.
36  * Enables backup before all the tests and selects local transport.
37  * Reverts to the original state after all the tests are executed.
38  */
39 @OptionClass(alias = "backup-preparer")
40 public class BackupPreparer implements ITargetCleaner {
41     @Option(name="enable-backup-if-needed", description=
42             "Enable backup before all the tests and return to the original state after.")
43     private boolean mEnableBackup = true;
44 
45     @Option(name="select-local-transport", description=
46             "Select local transport before all the tests and return to the original transport "
47                     + "after.")
48     private boolean mSelectLocalTransport = true;
49 
50     /** Value of PackageManager.FEATURE_BACKUP */
51     private static final String FEATURE_BACKUP = "android.software.backup";
52 
53     private static final String LOCAL_TRANSPORT =
54             "com.android.localtransport/.LocalTransport";
55 
56     private static final int BACKUP_PROVISIONING_TIMEOUT_SECONDS = 30;
57     private static final int BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS = 1;
58 
59     private boolean mIsBackupSupported;
60     private boolean mWasBackupEnabled;
61     private String mOldTransport;
62     private ITestDevice mDevice;
63 
64     @Override
setUp(ITestDevice device, IBuildInfo buildInfo)65     public void setUp(ITestDevice device, IBuildInfo buildInfo)
66             throws TargetSetupError, BuildError, DeviceNotAvailableException {
67         mDevice = device;
68 
69         mIsBackupSupported = mDevice.hasFeature("feature:" + FEATURE_BACKUP);
70 
71         // In case the device was just rebooted, wait for the broadcast queue to get idle to avoid
72         // any interference from services doing backup clean up on reboot.
73         waitForBroadcastIdle();
74 
75         if (mIsBackupSupported) {
76             // Enable backup and select local backup transport
77             if (!hasBackupTransport(LOCAL_TRANSPORT)) {
78                 throw new TargetSetupError("Device should have LocalTransport available",
79                         device.getDeviceDescriptor());
80             }
81             if (mEnableBackup) {
82                 CLog.i("Enabling backup on %s", mDevice.getSerialNumber());
83                 mWasBackupEnabled = enableBackup(true);
84                 CLog.d("Backup was enabled? : %s", mWasBackupEnabled);
85                 if (mSelectLocalTransport) {
86                     CLog.i("Selecting local transport on %s", mDevice.getSerialNumber());
87                     mOldTransport = setBackupTransport(LOCAL_TRANSPORT);
88                     CLog.d("Old transport : %s", mOldTransport);
89                 }
90                 waitForBackupInitialization();
91             }
92         }
93     }
94 
95     @Override
tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)96     public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
97             throws DeviceNotAvailableException {
98         mDevice = device;
99 
100         if (mIsBackupSupported) {
101             if (mEnableBackup) {
102                 CLog.i("Returning backup to it's previous state on %s", mDevice.getSerialNumber());
103                 enableBackup(mWasBackupEnabled);
104                 if (mSelectLocalTransport) {
105                     CLog.i("Returning selected transport to it's previous value on %s",
106                             mDevice.getSerialNumber());
107                     setBackupTransport(mOldTransport);
108                 }
109             }
110         }
111     }
112 
113     // Copied over from BackupQuotaTest
hasBackupTransport(String transport)114     private boolean hasBackupTransport(String transport) throws DeviceNotAvailableException {
115         String output = mDevice.executeShellCommand("bmgr list transports");
116         for (String t : output.split(" ")) {
117             if (transport.equals(t.trim())) {
118                 return true;
119             }
120         }
121         return false;
122     }
123 
124     // Copied over from BackupQuotaTest
enableBackup(boolean enable)125     private boolean enableBackup(boolean enable) throws DeviceNotAvailableException {
126         boolean previouslyEnabled;
127         String output = mDevice.executeShellCommand("bmgr enabled");
128         Pattern pattern = Pattern.compile("^Backup Manager currently (enabled|disabled)$");
129         Matcher matcher = pattern.matcher(output.trim());
130         if (matcher.find()) {
131             previouslyEnabled = "enabled".equals(matcher.group(1));
132         } else {
133             throw new RuntimeException("non-parsable output setting bmgr enabled: " + output);
134         }
135 
136         mDevice.executeShellCommand("bmgr enable " + enable);
137         return previouslyEnabled;
138     }
139 
140     // Copied over from BackupQuotaTest
setBackupTransport(String transport)141     private String setBackupTransport(String transport) throws DeviceNotAvailableException {
142         String output = mDevice.executeShellCommand("bmgr transport " + transport);
143         Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$");
144         Matcher matcher = pattern.matcher(output);
145         if (matcher.find()) {
146             return matcher.group(1);
147         } else {
148             throw new RuntimeException("non-parsable output setting bmgr transport: " + output);
149         }
150     }
151 
waitForBackupInitialization()152     private void waitForBackupInitialization()
153         throws TargetSetupError, DeviceNotAvailableException {
154         long tryUntilNanos = System.nanoTime()
155             + TimeUnit.SECONDS.toNanos(BACKUP_PROVISIONING_TIMEOUT_SECONDS);
156         while (System.nanoTime() < tryUntilNanos) {
157             String output = mDevice.executeShellCommand("dumpsys backup");
158             if (output.matches("(?s)"  // DOTALL
159                 + "^Backup Manager is .* not pending init.*")) {
160                 return;
161             }
162             try {
163                 Thread.sleep(TimeUnit.SECONDS.toMillis(BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS));
164             } catch (InterruptedException e) {
165                 Thread.currentThread().interrupt();
166                 break;
167             }
168         }
169         throw new TargetSetupError("Timed out waiting for backup initialization",
170             mDevice.getDeviceDescriptor());
171     }
172 
173     // Copied over from BaseDevicePolicyTest
waitForBroadcastIdle()174     private void waitForBroadcastIdle() throws DeviceNotAvailableException, TargetSetupError {
175         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
176         try {
177             // we allow 20 min for the command to complete and 10 min for the command to start to
178             // output something
179             mDevice.executeShellCommand(
180                     "am wait-for-broadcast-idle", receiver, 20, 10, TimeUnit.MINUTES, 0);
181         } finally {
182             String output = receiver.getOutput();
183             CLog.d("Output from 'am wait-for-broadcast-idle': %s", output);
184             if (!output.contains("All broadcast queues are idle!")) {
185                 // the call most likely failed we should fail the test
186                 throw new TargetSetupError("'am wait-for-broadcase-idle' did not complete.",
187                         mDevice.getDeviceDescriptor());
188                 // TODO: consider adding a reboot or recovery before failing if necessary
189             }
190         }
191     }
192 
193 }
194