1 /*
2  * Copyright (C) 2014 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 package com.android.tradefed.device;
17 
18 import com.android.ddmlib.Log.LogLevel;
19 import com.android.tradefed.log.LogUtil.CLog;
20 import com.android.tradefed.util.CommandResult;
21 import com.android.tradefed.util.CommandStatus;
22 import com.android.tradefed.util.IRunUtil;
23 
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.LinkedHashMap;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 
32 /**
33  * A helper class for fastboot operations.
34  */
35 public class FastbootHelper {
36 
37     /** max wait time in ms for fastboot devices command to complete */
38     private static final long FASTBOOT_CMD_TIMEOUT = 1 * 60 * 1000;
39 
40     private IRunUtil mRunUtil;
41     private String mFastbootPath = "fastboot";
42 
43     /**
44      * Constructor.
45      *
46      * @param runUtil a {@link IRunUtil}.
47      */
FastbootHelper(final IRunUtil runUtil, final String fastbootPath)48     public FastbootHelper(final IRunUtil runUtil, final String fastbootPath) {
49         if (runUtil == null) {
50             throw new IllegalArgumentException("runUtil cannot be null");
51         }
52         if (fastbootPath == null || fastbootPath.isEmpty()) {
53             throw new IllegalArgumentException("fastboot cannot be null or empty");
54         }
55         mRunUtil = runUtil;
56         mFastbootPath = fastbootPath;
57     }
58 
59     /**
60      * Determine if fastboot is available for use.
61      */
isFastbootAvailable()62     public boolean isFastbootAvailable() {
63         // Run "fastboot help" to check the existence and the version of fastboot
64         // (Old versions do not support "help" command).
65         CommandResult fastbootResult = mRunUtil.runTimedCmdSilently(15000, mFastbootPath, "help");
66         if (CommandStatus.SUCCESS.equals(fastbootResult.getStatus())) {
67             return true;
68         }
69         if (fastbootResult.getStderr() != null &&
70             fastbootResult.getStderr().indexOf("usage: fastboot") >= 0) {
71             CLog.logAndDisplay(LogLevel.WARN,
72                     "You are running an older version of fastboot, please update it.");
73             return true;
74         }
75         CLog.d("fastboot not available. stdout: %s, stderr: %s",
76                 fastbootResult.getStdout(), fastbootResult.getStderr());
77         return false;
78     }
79 
80     /**
81      * Returns a set of device serials in fastboot mode or an empty set if no fastboot devices.
82      *
83      * @return a set of device serials.
84      */
getDevices()85     public Set<String> getDevices() {
86         CommandResult fastbootResult =
87                 mRunUtil.runTimedCmdSilently(FASTBOOT_CMD_TIMEOUT, mFastbootPath, "devices");
88         if (fastbootResult.getStatus().equals(CommandStatus.SUCCESS)) {
89             CLog.v("fastboot devices returned\n %s",
90                     fastbootResult.getStdout());
91             return parseDevices(fastbootResult.getStdout(), false);
92         } else {
93             CLog.w("'fastboot devices' failed. Result: %s, stderr: %s", fastbootResult.getStatus(),
94                     fastbootResult.getStderr());
95         }
96         return new HashSet<String>();
97     }
98 
99     /**
100      * Returns a map of device serials and whether they are in fastbootd mode or not.
101      *
102      * @return a Map of serial in bootloader or fastbootd, the boolean is true if in fastbootd
103      */
getBootloaderAndFastbootdDevices()104     public Map<String, Boolean> getBootloaderAndFastbootdDevices() {
105         CommandResult fastbootResult =
106                 mRunUtil.runTimedCmdSilently(FASTBOOT_CMD_TIMEOUT, mFastbootPath, "devices");
107         if (fastbootResult.getStatus().equals(CommandStatus.SUCCESS)) {
108             CLog.v("fastboot devices returned\n %s", fastbootResult.getStdout());
109             Set<String> fastboot = parseDevices(fastbootResult.getStdout(), false);
110             Set<String> fastbootd = parseDevices(fastbootResult.getStdout(), true);
111             HashMap<String, Boolean> devices = new LinkedHashMap<>();
112             for (String f : fastboot) {
113                 devices.put(f, false);
114             }
115             for (String f : fastbootd) {
116                 devices.put(f, true);
117             }
118             return devices;
119         } else {
120             CLog.w(
121                     "'fastboot devices' failed. Result: %s, stderr: %s",
122                     fastbootResult.getStatus(), fastbootResult.getStderr());
123         }
124         return new HashMap<String, Boolean>();
125     }
126 
127     /**
128      * Parses the output of "fastboot devices" command. Exposed for unit testing.
129      *
130      * @param fastbootOutput the output of fastboot command.
131      * @param fastbootd whether or not we parse fastbootd or fastboot output
132      * @return a set of device serials.
133      */
parseDevices(String fastbootOutput, boolean fastbootd)134     Set<String> parseDevices(String fastbootOutput, boolean fastbootd) {
135         Set<String> serials = new HashSet<String>();
136         Pattern fastbootPattern = null;
137         if (fastbootd) {
138             fastbootPattern = Pattern.compile("([\\w\\d-]+)\\s+fastbootd\\s*");
139         } else {
140             fastbootPattern = Pattern.compile("([\\w\\d-]+)\\s+fastboot\\s*");
141         }
142         Matcher fastbootMatcher = fastbootPattern.matcher(fastbootOutput);
143         while (fastbootMatcher.find()) {
144             serials.add(fastbootMatcher.group(1));
145         }
146         return serials;
147     }
148 
149     /**
150      * Executes a fastboot command on a device and return the output.
151      *
152      * @param serial a device serial.
153      * @param command a fastboot command to run.
154      * @return the output of the fastboot command. null if the command failed.
155      */
executeCommand(String serial, String command)156     public String executeCommand(String serial, String command) {
157         final CommandResult fastbootResult = mRunUtil.runTimedCmd(FASTBOOT_CMD_TIMEOUT,
158                 mFastbootPath, "-s", serial, command);
159         if (fastbootResult.getStatus() != CommandStatus.SUCCESS) {
160             CLog.w("'fastboot -s %s %s' failed. Result: %s, stderr: %s", serial, command,
161                     fastbootResult.getStatus(), fastbootResult.getStderr());
162             return null;
163         }
164         return fastbootResult.getStdout();
165     }
166 
167     /** Returns whether or not a device is in Fastbootd instead of Bootloader. */
isFastbootd(String serial)168     public boolean isFastbootd(String serial) {
169         final CommandResult fastbootResult =
170                 mRunUtil.runTimedCmd(
171                         FASTBOOT_CMD_TIMEOUT,
172                         mFastbootPath,
173                         "-s",
174                         serial,
175                         "getvar",
176                         "is-userspace");
177         if (fastbootResult.getStderr().contains("is-userspace: yes")) {
178             return true;
179         }
180         return false;
181     }
182 }
183