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