1 /*
2  * Copyright (C) 2018 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 com.android.tradefed.targetprep;
18 
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.config.Option;
21 import com.android.tradefed.device.DeviceNotAvailableException;
22 import com.android.tradefed.device.ITestDevice;
23 import com.android.tradefed.log.LogUtil.CLog;
24 
25 /**
26  * Perform basic device preparation tasks such as check (and turn on/off) device framework.
27  *
28  * Options with prefix "enable" or "disable" enables or disables a feature,
29  * and setting them to false means no operation.
30  *
31  * For a same feature, if "enable" and "disable" options are both set to true will result in no-op.
32  *
33  * Any of the "restore" option will restore the feature to the state before test. This means actions
34  * may be taken even if the feature is not enabled. For example, if device framework is on,
35  * "enable-framework" is "false", "restore-all" is set to "true", and during a test the framework
36  * is turned on/off, this class will start the framework in the end.
37  */
38 public class VtsDevicePreparer implements ITargetPreparer, ITargetCleaner {
39     @Option(name = "start-framework",
40             description = "Whether to start android framework on device. "
41                     + "Setting this option to false does not stop framework. "
42                     + "If both start-framework and stop-framework is set to true, "
43                     + "no action will be taken.")
44     boolean mStartFramework = false;
45 
46     @Option(name = "stop-framework",
47             description = "Whether to stop android framework on device. "
48                     + "Setting this option to false does not start framework. "
49                     + "If both start-framework and disable-framework is set to true, "
50                     + "no action will be taken.")
51     boolean mStopFramework = false;
52 
53     @Option(name = "restore-framework",
54             description = "Whether to restore the initial framework "
55                     + "start/stop status after tests. "
56                     + "This option will be overriden by restore-all if that is set to true.")
57     boolean mRestoreFramework = false;
58 
59     @Option(name = "enable-adb-root",
60             description = "Whether to enable adb root on device. "
61                     + "If set to true, `adb root` will be called. "
62                     + "Setting this option to false does not disable adb root. "
63                     + "This option requires enable-root setting be true in test plan setting. "
64                     + "If both enable-adb-root and disable-adb-root is set to true, no action "
65                     + "will be taken.")
66     boolean mEnableAdbRoot = false;
67 
68     @Option(name = "disable-adb-root",
69             description = "Whether to disable adb root on device. "
70                     + "If set to true, `adb root` will be called. "
71                     + "Setting this option to false does not enable adb root. "
72                     + "If both enable-adb-root and disable-adb-root is set to true, "
73                     + "no action will be taken.")
74     boolean mDisableAdbRoot = false;
75 
76     @Option(name = "restore-adb-root",
77             description = "Whether to restore the initial adb root "
78                     + "status after tests. "
79                     + "This option will be overriden by restore-all if that is set to true.")
80     boolean mRestoreAdbRoot = false;
81 
82     @Option(name = "restore-all",
83             description = "Whether to restore device status after tests. "
84                     + "This option overrides individual restore options such as restore-framework.")
85     boolean mRestoreAll = false;
86 
87     @Option(name = "enable-radio-log",
88             description = "Whether to enable radio modem logcat. Device will reboot if enabled. "
89                     + "This option requires adb root but will not automatically root adb. "
90                     + "Setting this option false does not disable already enabled radio log.")
91     boolean mEnableRadioLog = false;
92 
93     @Option(name = "restore-radio-log",
94             description = "Whether to restore radio modem logcat status after test. "
95                     + "This option requires adb root but will not automatically root adb. "
96                     + "If both enable-radio-log and this option is set to true, "
97                     + "device will reboot again at the end of test.")
98     boolean mRestoreRadioLog = false;
99 
100     public static long DEVICE_BOOT_TIMEOUT = 3 * 60 * 1000;
101     static final String SYSPROP_DEV_BOOTCOMPLETE = "dev.bootcomplete";
102     static final String SYSPROP_SYS_BOOT_COMPLETED = "sys.boot_completed";
103     public static String SYSPROP_RADIO_LOG = "persist.vendor.radio.adb_log_on";
104     public static String SYSPROP_RADIO_LOG_OLD = "persist.radio.adb_log_on";
105     public String mSyspropRadioLog = SYSPROP_RADIO_LOG;
106 
107     // The name of a system property which tells whether to stop properly configured
108     // native servers where properly configured means a server's init.rc is
109     // configured to stop when that property's value is 1.
110     static final String SYSPROP_VTS_NATIVE_SERVER = "vts.native_server.on";
111 
112     boolean mInitialFrameworkStarted = true;
113     boolean mInitialAdbRoot = false;
114     DeviceOptionState mInitialRadioLog = DeviceOptionState.UNKNOWN;
115     ITestDevice mDevice = null;
116 
117     // Whether to reboot device during setUp
118     boolean mRebootSetup = false;
119     // Whether to reboot device during tearDown
120     boolean mRebootTearDown = false;
121 
122     public enum DeviceOptionState {
123         UNKNOWN,
124         ENABLED,
125         DISABLED,
126         NOT_AVAILABLE;
127     }
128 
129     /** {@inheritDoc} */
130     @Override
setUp(ITestDevice device, IBuildInfo buildInfo)131     public void setUp(ITestDevice device, IBuildInfo buildInfo)
132             throws TargetSetupError, BuildError, DeviceNotAvailableException {
133         mDevice = device;
134 
135         // The order of the following steps matters.
136 
137         adbRootPreRebootSetUp();
138         radioLogPreRebootSetup();
139         frameworkPreRebootSetUp();
140 
141         if (mRebootSetup) {
142             device.reboot();
143             device.waitForBootComplete(DEVICE_BOOT_TIMEOUT);
144         }
145 
146         frameworkPostRebootSetUp();
147     }
148 
149     /** {@inheritDoc} */
150     @Override
tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)151     public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
152             throws DeviceNotAvailableException {
153         if (e instanceof DeviceNotAvailableException) {
154             CLog.i("Skip tear down due to DeviceNotAvailableException");
155             return;
156         }
157         // The order of the following steps matters.
158 
159         radioLogPreTearDown();
160 
161         if (mRebootTearDown) {
162             device.reboot();
163             device.waitForBootComplete(DEVICE_BOOT_TIMEOUT);
164         }
165 
166         adbRootPostTearDown();
167         frameworkPostTearDown();
168     }
169 
170     /**
171      * Prepares adb root status.
172      *
173      * @throws DeviceNotAvailableException
174      */
adbRootPreRebootSetUp()175     private void adbRootPreRebootSetUp() throws DeviceNotAvailableException {
176         if (mRestoreAll || mRestoreAdbRoot) {
177             // mInitialAdbRoot is only needed at tearDown because AndroidDeviceController
178             // already checks initial adb root status
179             mInitialAdbRoot = mDevice.isAdbRoot();
180         }
181 
182         if (mEnableAdbRoot && !mDisableAdbRoot) {
183             adbRoot();
184         } else if (mDisableAdbRoot && !mEnableAdbRoot) {
185             adbUnroot();
186         }
187     }
188 
189     /**
190      * Restores adb root status
191      *
192      * @throws DeviceNotAvailableException
193      *
194      */
adbRootPostTearDown()195     private void adbRootPostTearDown() throws DeviceNotAvailableException {
196         if (!mRestoreAll && !mRestoreAdbRoot) {
197             return;
198         }
199 
200         if (mInitialAdbRoot) {
201             adbRoot();
202         } else {
203             adbUnroot();
204         }
205     }
206 
207     /**
208      * Collect framework on/off status if restore option is enabled.
209      * @throws DeviceNotAvailableException
210      */
frameworkPreRebootSetUp()211     private void frameworkPreRebootSetUp() throws DeviceNotAvailableException {
212         if (mRestoreAll || mRestoreFramework) {
213             // mInitialFrameworkStarted is only needed at tearDown because AndroidDeviceController
214             // already checks initial framework status
215             mInitialFrameworkStarted = isFrameworkRunning();
216         }
217     }
218 
219     /**
220      * Prepares device framework start/stop status
221      *
222      * @throws DeviceNotAvailableException
223      */
frameworkPostRebootSetUp()224     private void frameworkPostRebootSetUp() throws DeviceNotAvailableException {
225         if (mStartFramework && !mStopFramework) {
226             startFramework();
227         } else if (mStopFramework && !mStartFramework) {
228             stopFramework();
229         }
230     }
231 
232     /**
233      * Restores the framework start/stop status
234      *
235      * @throws DeviceNotAvailableException
236      */
frameworkPostTearDown()237     private void frameworkPostTearDown() throws DeviceNotAvailableException {
238         if (!mRestoreAll && !mRestoreFramework) {
239             return;
240         }
241 
242         boolean current = isFrameworkRunning();
243 
244         if (mInitialFrameworkStarted && !current) {
245             startFramework();
246         } else if (!mInitialFrameworkStarted && current) {
247             stopFramework();
248         }
249     }
250 
251     /**
252      * @throws DeviceNotAvailableException
253      *
254      */
radioLogPreRebootSetup()255     private void radioLogPreRebootSetup() throws DeviceNotAvailableException {
256         if (mEnableRadioLog || mRestoreAll || mRestoreRadioLog) {
257             mInitialRadioLog = radioLogGetState();
258 
259             if (mInitialRadioLog == DeviceOptionState.NOT_AVAILABLE) {
260                 CLog.d("Radio modem log configured but the setting is not available "
261                         + " on device. Skipping.");
262                 return;
263             }
264         }
265 
266         if (mEnableRadioLog && mInitialRadioLog == DeviceOptionState.DISABLED) {
267             this.setProperty(SYSPROP_RADIO_LOG, "1");
268             CLog.d("Turing on radio modem log.");
269             mRebootSetup = true;
270         }
271     }
272 
273     /**
274      * @throws DeviceNotAvailableException
275      *
276      */
radioLogPreTearDown()277     private void radioLogPreTearDown() throws DeviceNotAvailableException {
278         if (mInitialRadioLog == DeviceOptionState.NOT_AVAILABLE) {
279             return;
280         }
281 
282         if (!mRestoreAll && !mRestoreRadioLog) {
283             return;
284         }
285 
286         DeviceOptionState current = radioLogGetState();
287 
288         if (mInitialRadioLog == DeviceOptionState.DISABLED
289                 && current == DeviceOptionState.ENABLED) {
290             CLog.d("Turing off radio modem log.");
291             this.setProperty(SYSPROP_RADIO_LOG, "0");
292         } else if (mInitialRadioLog == DeviceOptionState.ENABLED
293                 && current == DeviceOptionState.DISABLED) {
294             CLog.d("Turing on radio modem log.");
295             this.setProperty(SYSPROP_RADIO_LOG, "1");
296         } else {
297             return;
298         }
299 
300         mRebootTearDown = true;
301     }
302 
303     /**
304      * Returns the state of radio modem log on/off state.
305      * @return DeviceOptionState specifying the state.
306      * @throws DeviceNotAvailableException
307      */
radioLogGetState()308     private DeviceOptionState radioLogGetState() throws DeviceNotAvailableException {
309         String radioProp = mDevice.getProperty(mSyspropRadioLog);
310 
311         if (radioProp == null && mSyspropRadioLog != SYSPROP_RADIO_LOG_OLD) {
312             mSyspropRadioLog = SYSPROP_RADIO_LOG_OLD;
313             radioProp = mDevice.getProperty(mSyspropRadioLog);
314         }
315 
316         if (radioProp == null) {
317             return DeviceOptionState.NOT_AVAILABLE;
318         }
319 
320         switch (radioProp) {
321             case "1":
322                 return DeviceOptionState.ENABLED;
323             case "0":
324                 return DeviceOptionState.DISABLED;
325             default:
326                 return DeviceOptionState.NOT_AVAILABLE;
327         }
328     }
329 
330     // ----------------------- Below are device util methods -----------------------
331 
332     /**
333      * Executes command "adb root" if adb is not running as root.
334      *
335      * @throws DeviceNotAvailableException
336      */
adbRoot()337     void adbRoot() throws DeviceNotAvailableException {
338         if (!mDevice.isAdbRoot()) {
339             mDevice.executeAdbCommand("root");
340         }
341     }
342 
343     /**
344      * Executes command "adb unroot" if adb is running as root.
345      *
346      * @throws DeviceNotAvailableException
347      */
adbUnroot()348     void adbUnroot() throws DeviceNotAvailableException {
349         if (mDevice.isAdbRoot()) {
350             mDevice.executeAdbCommand("unroot");
351         }
352     }
353 
354     /**
355      * Start Android framework on a device.
356      *
357      * This method also starts VTS native servers which is required to start the framework
358      * This method will block until boot complete or timeout.
359      * A default timeout value will be used
360      *
361      * @throws DeviceNotAvailableException if timeout waiting for boot complete
362      */
startFramework()363     void startFramework() throws DeviceNotAvailableException {
364         startFramework(DEVICE_BOOT_TIMEOUT);
365     }
366 
367     /**
368      * Starts Android framework on a device.
369      *
370      * This method also starts VTS native servers which is required to start the framework
371      * This method will block until boot complete or timeout.
372      *
373      * @param timeout timeout in milliseconds.
374      * @throws DeviceNotAvailableException if timeout waiting for boot complete
375      */
startFramework(long timeout)376     void startFramework(long timeout) throws DeviceNotAvailableException {
377         startNativeServers();
378         mDevice.executeShellCommand("start");
379 
380         waitForFrameworkStartComplete();
381     }
382 
383     /**
384      * Wait for Android framework to complete starting.
385      *
386      * @throws DeviceNotAvailableException timed out
387      */
waitForFrameworkStartComplete()388     void waitForFrameworkStartComplete() throws DeviceNotAvailableException {
389         waitForFrameworkStartComplete(DEVICE_BOOT_TIMEOUT);
390     }
391 
392     /**
393      * Wait for Android framework to complete starting.
394      *
395      * @param timeout
396      * @throws DeviceNotAvailableException
397      */
waitForFrameworkStartComplete(long timeout)398     void waitForFrameworkStartComplete(long timeout) throws DeviceNotAvailableException {
399         long start = System.currentTimeMillis();
400 
401         // First, wait for boot completion
402         mDevice.waitForBootComplete(timeout);
403 
404         while (!isFrameworkRunning()) {
405             if (System.currentTimeMillis() - start >= timeout) {
406                 throw new DeviceNotAvailableException(
407                         "Timed out waiting for framework start complete.",
408                         mDevice.getSerialNumber());
409             }
410 
411             try {
412                 Thread.sleep(1000);
413             } catch (InterruptedException e) {
414                 e.printStackTrace();
415                 throw new DeviceNotAvailableException(
416                         "Intrupted while waiting for framework start complete.",
417                         mDevice.getSerialNumber());
418             }
419         }
420     }
421 
422     /**
423      * Stops Android framework on a device.
424      *
425      * @throws DeviceNotAvailableException
426      */
stopFramework()427     void stopFramework() throws DeviceNotAvailableException {
428         mDevice.executeShellCommand("stop");
429         this.setProperty(SYSPROP_SYS_BOOT_COMPLETED, "0");
430     }
431 
432     /**
433      * Restarts Android framework on a device.
434      *
435      * This method will block until start finish or timeout.
436      *
437      * @throws DeviceNotAvailableException
438      */
restartFramework()439     void restartFramework() throws DeviceNotAvailableException {
440         stopFramework();
441         startFramework();
442     }
443 
444     /**
445      * Starts all native servers.
446      *
447      * @throws DeviceNotAvailableException
448      */
startNativeServers()449     void startNativeServers() throws DeviceNotAvailableException {
450         this.setProperty(SYSPROP_VTS_NATIVE_SERVER, "0");
451     }
452 
453     /**
454      * Stops all native servers.
455      *
456      * @throws DeviceNotAvailableException
457      */
stopNativeServers()458     void stopNativeServers() throws DeviceNotAvailableException {
459         this.setProperty(SYSPROP_VTS_NATIVE_SERVER, "1");
460     }
461 
462     /**
463      * Sets a sysprop on the device.
464      *
465      * TODO: to be removed once the API is added to TF
466      *
467      * @param key the key of a sysprop.
468      * @param value the value of a sysprop.
469      * @throws DeviceNotAvailableException
470      */
setProperty(String key, String value)471     void setProperty(String key, String value) throws DeviceNotAvailableException {
472         // TODO: check success
473         mDevice.executeShellCommand(String.format("setprop %s %s", key, value));
474     }
475 
isBootCompleted()476     boolean isBootCompleted() throws DeviceNotAvailableException {
477         String sysBootCompleted = mDevice.getProperty(SYSPROP_SYS_BOOT_COMPLETED);
478         String devBootCompleted = mDevice.getProperty(SYSPROP_DEV_BOOTCOMPLETE);
479         return sysBootCompleted != null && sysBootCompleted.equals("1") && devBootCompleted != null
480                 && devBootCompleted.equals("1");
481     }
482 
483     /**
484      * Checks whether Android framework is started.
485      *
486      * @return True if started, False otherwise.
487      * @throws DeviceNotAvailableException
488      */
isFrameworkRunning()489     boolean isFrameworkRunning() throws DeviceNotAvailableException {
490         // First, check whether boot has completed.
491         if (!isBootCompleted()) {
492             return false;
493         }
494 
495         String cmd = "ps -g system | grep system_server";
496         return mDevice.executeShellCommand(cmd).contains("system_server");
497     }
498 }
499