blob: cc5b8d63a416f0b0bd5fa950264340ccf2176946 [file] [log] [blame]
Julien Desprez6961b272016-02-01 09:58:23 +00001/*
2 * Copyright (C) 2016 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 */
Julien Desprez6961b272016-02-01 09:58:23 +000016package com.android.tradefed.device;
17
18import com.android.ddmlib.AdbCommandRejectedException;
19import com.android.ddmlib.FileListingService;
20import com.android.ddmlib.FileListingService.FileEntry;
21import com.android.ddmlib.IDevice;
22import com.android.ddmlib.IShellOutputReceiver;
23import com.android.ddmlib.InstallException;
jdesprez1fbb2472018-03-07 11:15:38 -080024import com.android.ddmlib.Log.LogLevel;
Julien Desprez6961b272016-02-01 09:58:23 +000025import com.android.ddmlib.NullOutputReceiver;
Julien Desprez6961b272016-02-01 09:58:23 +000026import com.android.ddmlib.ShellCommandUnresponsiveException;
27import com.android.ddmlib.SyncException;
28import com.android.ddmlib.SyncException.SyncError;
29import com.android.ddmlib.SyncService;
30import com.android.ddmlib.TimeoutException;
31import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
Guang Zhua7dde442019-10-01 19:35:19 +000032import com.android.ddmlib.testrunner.ITestRunListener;
33import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
Julien Desprez6961b272016-02-01 09:58:23 +000034import com.android.tradefed.build.IBuildInfo;
Julien Desprez07fe1532016-08-12 11:48:32 +010035import com.android.tradefed.command.remote.DeviceDescriptor;
Guang Zhu26cca482017-11-01 18:18:08 -070036import com.android.tradefed.config.GlobalConfiguration;
Julien Desprez1dc6b392019-03-18 11:49:13 -070037import com.android.tradefed.device.contentprovider.ContentProviderHandler;
Guang Zhu26cca482017-11-01 18:18:08 -070038import com.android.tradefed.host.IHostOptions;
Julien Desprez0c836c92016-08-10 14:40:41 +010039import com.android.tradefed.log.ITestLogger;
Julien Desprez78de5532019-05-17 10:46:27 -070040import com.android.tradefed.log.LogUtil;
Julien Desprez6961b272016-02-01 09:58:23 +000041import com.android.tradefed.log.LogUtil.CLog;
42import com.android.tradefed.result.ByteArrayInputStreamSource;
Julien Desprez16184162016-06-10 08:56:17 +010043import com.android.tradefed.result.FileInputStreamSource;
jdesprez4de2f552018-02-02 14:38:13 -080044import com.android.tradefed.result.ITestLifeCycleReceiver;
Julien Desprez6961b272016-02-01 09:58:23 +000045import com.android.tradefed.result.InputStreamSource;
Julien Desprez0c836c92016-08-10 14:40:41 +010046import com.android.tradefed.result.LogDataType;
Julien Desprez6961b272016-02-01 09:58:23 +000047import com.android.tradefed.result.SnapshotInputStreamSource;
Guang Zhua7dde442019-10-01 19:35:19 +000048import com.android.tradefed.result.StubTestRunListener;
49import com.android.tradefed.result.ddmlib.TestRunToTestInvocationForwarder;
Julien Desprez26bee8d2016-03-29 12:09:48 +010050import com.android.tradefed.targetprep.TargetSetupError;
Julien Desprez6961b272016-02-01 09:58:23 +000051import com.android.tradefed.util.ArrayUtil;
Julien Desprez0c836c92016-08-10 14:40:41 +010052import com.android.tradefed.util.Bugreport;
Julien Desprez6961b272016-02-01 09:58:23 +000053import com.android.tradefed.util.CommandResult;
54import com.android.tradefed.util.CommandStatus;
55import com.android.tradefed.util.FileUtil;
56import com.android.tradefed.util.IRunUtil;
Julien Desprez14e96692017-01-12 12:31:29 +000057import com.android.tradefed.util.KeyguardControllerState;
Gopinath65e837a2016-10-06 17:55:12 -070058import com.android.tradefed.util.ProcessInfo;
jdesprez3d7b9142018-03-02 12:11:23 -080059import com.android.tradefed.util.QuotationAwareTokenizer;
Julien Desprez6961b272016-02-01 09:58:23 +000060import com.android.tradefed.util.RunUtil;
61import com.android.tradefed.util.SizeLimitedOutputStream;
Julien Desprez0c836c92016-08-10 14:40:41 +010062import com.android.tradefed.util.StreamUtil;
Julien Desprezed378b92019-04-19 12:31:57 -070063import com.android.tradefed.util.StringEscapeUtils;
Julien Desprez96c00252018-06-06 03:25:40 -070064import com.android.tradefed.util.ZipUtil;
Guang Zhu91fdf442017-01-03 17:59:55 -080065import com.android.tradefed.util.ZipUtil2;
Julien Desprez6961b272016-02-01 09:58:23 +000066
Jeffrey Lu279122f2018-01-29 17:25:08 -080067import com.google.common.annotations.VisibleForTesting;
Guang Zhuc0a99082019-02-27 15:21:27 -080068import com.google.common.base.Strings;
Jeffrey Lu279122f2018-01-29 17:25:08 -080069
Guang Zhu91fdf442017-01-03 17:59:55 -080070import org.apache.commons.compress.archivers.zip.ZipFile;
71
Julien Desprez6961b272016-02-01 09:58:23 +000072import java.io.File;
73import java.io.FilenameFilter;
74import java.io.IOException;
jovanakf39a78c2019-04-02 17:08:43 -070075import java.io.OutputStream;
Julien Desprez6961b272016-02-01 09:58:23 +000076import java.text.ParseException;
77import java.text.SimpleDateFormat;
Jeffrey Lu279122f2018-01-29 17:25:08 -080078import java.time.Clock;
Julien Desprez6961b272016-02-01 09:58:23 +000079import java.util.ArrayList;
80import java.util.Arrays;
81import java.util.Collection;
82import java.util.Date;
Julien Desprez69324072018-08-14 14:08:47 -070083import java.util.HashSet;
Betty Zhou8fd29192019-05-30 10:52:32 -070084import java.util.LinkedHashMap;
Julien Desprez6961b272016-02-01 09:58:23 +000085import java.util.List;
86import java.util.Map;
87import java.util.Random;
88import java.util.Set;
Julien Desprez7c15ac42016-08-10 16:45:44 +010089import java.util.TimeZone;
Julien Desprez6961b272016-02-01 09:58:23 +000090import java.util.concurrent.ExecutionException;
Julien Desprezb0e88012018-11-19 11:49:48 -080091import java.util.concurrent.Future;
Julien Desprez6961b272016-02-01 09:58:23 +000092import java.util.concurrent.TimeUnit;
93import java.util.concurrent.locks.ReentrantLock;
94import java.util.regex.Matcher;
95import java.util.regex.Pattern;
96
Nikita Ioffef0aa00a2020-01-16 00:11:37 +000097import javax.annotation.Nullable;
Julien Desprez6961b272016-02-01 09:58:23 +000098import javax.annotation.concurrent.GuardedBy;
Julien Desprez6961b272016-02-01 09:58:23 +000099
100/**
101 * Default implementation of a {@link ITestDevice}
102 * Non-full stack android devices.
103 */
Julien Desprez2f34e382016-06-21 12:30:39 +0100104public class NativeDevice implements IManagedTestDevice {
Julien Desprez6961b272016-02-01 09:58:23 +0000105
Julien Desprez878096f2019-05-31 09:12:40 -0700106 protected static final String SD_CARD = "/sdcard/";
Julien Desprez16184162016-06-10 08:56:17 +0100107 /**
108 * Allow pauses of up to 2 minutes while receiving bugreport.
109 * <p/>
110 * Note that dumpsys may pause up to a minute while waiting for unresponsive components.
111 * It still should bail after that minute, if it will ever terminate on its own.
112 */
113 private static final int BUGREPORT_TIMEOUT = 2 * 60 * 1000;
Julien Desprezf54735c2016-08-16 09:17:07 +0100114 /**
115 * Allow a little more time for bugreportz because there are extra steps.
116 */
117 private static final int BUGREPORTZ_TIMEOUT = 5 * 60 * 1000;
Julien Desprez16184162016-06-10 08:56:17 +0100118 private static final String BUGREPORT_CMD = "bugreport";
119 private static final String BUGREPORTZ_CMD = "bugreportz";
Nick Kralevich5487ad22016-11-19 12:16:15 -0800120 private static final String BUGREPORTZ_TMP_PATH = "/bugreports/";
Julien Desprez16184162016-06-10 08:56:17 +0100121
Julien Desprez73c55bf2016-09-01 09:27:37 +0100122 /**
123 * Allow up to 2 minutes to receives the full logcat dump.
124 */
125 private static final int LOGCAT_DUMP_TIMEOUT = 2 * 60 * 1000;
126
Julien Desprez6961b272016-02-01 09:58:23 +0000127 /** the default number of command retry attempts to perform */
Julien Desprezc8474552016-02-17 10:59:27 +0000128 protected static final int MAX_RETRY_ATTEMPTS = 2;
129
Julien Desprez3172c9b2019-04-24 15:58:04 -0700130 /** Value returned for any invalid/not found user id: UserHandle defined the -10000 value */
131 public static final int INVALID_USER_ID = -10000;
Julien Desprez4c8aabc2016-03-14 15:53:48 +0000132
Julien Desprez6961b272016-02-01 09:58:23 +0000133 /** regex to match input dispatch readiness line **/
134 static final Pattern INPUT_DISPATCH_STATE_REGEX =
135 Pattern.compile("DispatchEnabled:\\s?([01])");
136 /** regex to match build signing key type */
137 private static final Pattern KEYS_PATTERN = Pattern.compile("^.*-keys$");
138 private static final Pattern DF_PATTERN = Pattern.compile(
139 //Fs 1K-blks Used Available Use% Mounted on
Julien Desprez3d8c1472016-09-19 11:18:09 +0100140 "^/\\S+\\s+\\d+\\s+\\d+\\s+(\\d+)\\s+\\d+%\\s+/\\S*$", Pattern.MULTILINE);
Julien Desprez16184162016-06-10 08:56:17 +0100141 private static final Pattern BUGREPORTZ_RESPONSE_PATTERN = Pattern.compile("(OK:)(.*)");
Julien Desprez6961b272016-02-01 09:58:23 +0000142
Julien Desprez1fadf1a2016-10-20 15:50:46 +0100143 protected static final long MAX_HOST_DEVICE_TIME_OFFSET = 5 * 1000;
Julien Desprez6961b272016-02-01 09:58:23 +0000144
145 /** The password for encrypting and decrypting the device. */
146 private static final String ENCRYPTION_PASSWORD = "android";
147 /** Encrypting with inplace can take up to 2 hours. */
148 private static final int ENCRYPTION_INPLACE_TIMEOUT_MIN = 2 * 60;
149 /** Encrypting with wipe can take up to 20 minutes. */
150 private static final long ENCRYPTION_WIPE_TIMEOUT_MIN = 20;
Julien Desprez6961b272016-02-01 09:58:23 +0000151
Betty Zhou35804ce2019-08-01 10:52:55 -0700152 /** The maximum system_server start delay in seconds after device boot up */
153 private static final int MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC = 10;
154
Julien Desprez6961b272016-02-01 09:58:23 +0000155 /** The time in ms to wait before starting logcat for a device */
156 private int mLogStartDelay = 5*1000;
157
158 /** The time in ms to wait for a device to become unavailable. Should usually be short */
159 private static final int DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000;
160 /** The time in ms to wait for a recovery that we skip because of the NONE mode */
161 static final int NONE_RECOVERY_MODE_DELAY = 1000;
Julien Desprez6961b272016-02-01 09:58:23 +0000162
jdesprezbc580f92017-06-02 11:41:40 -0700163 private static final String SIM_STATE_PROP = "gsm.sim.state";
164 private static final String SIM_OPERATOR_PROP = "gsm.operator.alpha";
165
Kevin Lau Fang805161d2016-12-07 21:06:38 -0800166 static final String MAC_ADDRESS_PATTERN = "([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}";
Kevin Lau Fang27ec20c2017-08-22 10:54:27 -0700167 static final String MAC_ADDRESS_COMMAND = "su root cat /sys/class/net/wlan0/address";
Kevin Lau Fang805161d2016-12-07 21:06:38 -0800168
Julien Desprez6961b272016-02-01 09:58:23 +0000169 /** The network monitoring interval in ms. */
170 private static final int NETWORK_MONITOR_INTERVAL = 10 * 1000;
171
172 /** Wifi reconnect check interval in ms. */
173 private static final int WIFI_RECONNECT_CHECK_INTERVAL = 1 * 1000;
174
175 /** Wifi reconnect timeout in ms. */
176 private static final int WIFI_RECONNECT_TIMEOUT = 60 * 1000;
177
Julien Desprez41e09ab2018-07-24 11:07:17 -0700178 /** Pattern to find an executable file. */
179 private static final Pattern EXE_FILE = Pattern.compile("^[-l]r.x.+");
180
Julien Desprezb48b9e22019-05-16 18:52:22 -0700181 /** Path of the device containing the tombstones */
182 private static final String TOMBSTONE_PATH = "/data/tombstones/";
183
Julien Desprez6961b272016-02-01 09:58:23 +0000184 /** The time in ms to wait for a command to complete. */
jdesprez3d7b9142018-03-02 12:11:23 -0800185 private long mCmdTimeout = 2 * 60 * 1000L;
Julien Desprez6961b272016-02-01 09:58:23 +0000186 /** The time in ms to wait for a 'long' command to complete. */
jdesprez3d7b9142018-03-02 12:11:23 -0800187 private long mLongCmdTimeout = 25 * 60 * 1000L;
Julien Desprez6961b272016-02-01 09:58:23 +0000188
Julien Desprez6961b272016-02-01 09:58:23 +0000189 private IDevice mIDevice;
190 private IDeviceRecovery mRecovery = new WaitDeviceRecovery();
Julien Desprezc8474552016-02-17 10:59:27 +0000191 protected final IDeviceStateMonitor mStateMonitor;
Julien Desprez6961b272016-02-01 09:58:23 +0000192 private TestDeviceState mState = TestDeviceState.ONLINE;
193 private final ReentrantLock mFastbootLock = new ReentrantLock();
194 private LogcatReceiver mLogcatReceiver;
195 private boolean mFastbootEnabled = true;
Julien Desprez0a7d67d2016-07-21 16:05:57 +0100196 private String mFastbootPath = "fastboot";
Julien Desprez6961b272016-02-01 09:58:23 +0000197
Julien Desprezc8474552016-02-17 10:59:27 +0000198 protected TestDeviceOptions mOptions = new TestDeviceOptions();
Julien Desprez6961b272016-02-01 09:58:23 +0000199 private Process mEmulatorProcess;
200 private SizeLimitedOutputStream mEmulatorOutput;
Jeffrey Lu279122f2018-01-29 17:25:08 -0800201 private Clock mClock = Clock.systemUTC();
Julien Desprez6961b272016-02-01 09:58:23 +0000202
203 private RecoveryMode mRecoveryMode = RecoveryMode.AVAILABLE;
204
205 private Boolean mIsEncryptionSupported = null;
206 private ReentrantLock mAllocationStateLock = new ReentrantLock();
207 @GuardedBy("mAllocationStateLock")
208 private DeviceAllocationState mAllocationState = DeviceAllocationState.Unknown;
209 private IDeviceMonitor mAllocationMonitor = null;
210
211 private String mLastConnectedWifiSsid = null;
212 private String mLastConnectedWifiPsk = null;
213 private boolean mNetworkMonitorEnabled = false;
214
Julien Desprez1dc6b392019-03-18 11:49:13 -0700215 private ContentProviderHandler mContentProvider = null;
216 private boolean mShouldSkipContentProviderSetup = false;
Julien Despreza5397f12019-03-28 09:46:16 -0700217 /** Keep track of the last time Tradefed itself triggered a reboot. */
218 private long mLastTradefedRebootTime = 0L;
Julien Desprez1dc6b392019-03-18 11:49:13 -0700219
Julien Desprez78de5532019-05-17 10:46:27 -0700220 private File mExecuteShellCommandLogs = null;
221
Julien Desprez58e9d972020-03-17 11:57:14 -0700222 private DeviceDescriptor mCachedDeviceDescriptor = null;
223 private final Object mCacheLock = new Object();
224
Julien Desprez6961b272016-02-01 09:58:23 +0000225 /**
226 * Interface for a generic device communication attempt.
227 */
Julien Desprezc8474552016-02-17 10:59:27 +0000228 abstract interface DeviceAction {
Julien Desprez6961b272016-02-01 09:58:23 +0000229
230 /**
231 * Execute the device operation.
232 *
233 * @return <code>true</code> if operation is performed successfully, <code>false</code>
234 * otherwise
Julien Desprezc8474552016-02-17 10:59:27 +0000235 * @throws IOException, TimeoutException, AdbCommandRejectedException,
236 * ShellCommandUnresponsiveException, InstallException,
237 * SyncException if operation terminated abnormally
Julien Desprez6961b272016-02-01 09:58:23 +0000238 */
239 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
240 ShellCommandUnresponsiveException, InstallException, SyncException;
241 }
242
243 /**
244 * A {@link DeviceAction} for running a OS 'adb ....' command.
245 */
Julien Desprezc8474552016-02-17 10:59:27 +0000246 protected class AdbAction implements DeviceAction {
Julien Desprez6961b272016-02-01 09:58:23 +0000247 /** the output from the command */
248 String mOutput = null;
249 private String[] mCmd;
Guang Zhu6e5df882019-05-24 14:20:06 -0700250 private long mTimeout;
Julien Desprezfab28732019-08-28 09:01:11 -0700251 private boolean mIsShellCommand;
Julien Desprez6961b272016-02-01 09:58:23 +0000252
Julien Desprezfab28732019-08-28 09:01:11 -0700253 AdbAction(long timeout, String[] cmd, boolean isShell) {
Guang Zhu6e5df882019-05-24 14:20:06 -0700254 mTimeout = timeout;
Julien Desprez6961b272016-02-01 09:58:23 +0000255 mCmd = cmd;
Julien Desprezfab28732019-08-28 09:01:11 -0700256 mIsShellCommand = isShell;
Julien Desprez6961b272016-02-01 09:58:23 +0000257 }
258
Guang Zhu5786a912019-06-20 17:42:56 -0700259 private void logExceptionAndOutput(CommandResult result) {
260 CLog.w("Command exited with status: %s", result.getStatus().toString());
261 CLog.w("Command stdout:\n%s\n", result.getStdout());
262 CLog.w("Command stderr:\n%s\n", result.getStderr());
263 }
264
Julien Desprez6961b272016-02-01 09:58:23 +0000265 @Override
266 public boolean run() throws TimeoutException, IOException {
Guang Zhu6e5df882019-05-24 14:20:06 -0700267 CommandResult result = getRunUtil().runTimedCmd(mTimeout, mCmd);
Julien Desprez6961b272016-02-01 09:58:23 +0000268 // TODO: how to determine device not present with command failing for other reasons
269 if (result.getStatus() == CommandStatus.EXCEPTION) {
Guang Zhu5786a912019-06-20 17:42:56 -0700270 logExceptionAndOutput(result);
271 throw new IOException("CommandStatus was EXCEPTION, details in host log");
Julien Desprez6961b272016-02-01 09:58:23 +0000272 } else if (result.getStatus() == CommandStatus.TIMED_OUT) {
Guang Zhu5786a912019-06-20 17:42:56 -0700273 logExceptionAndOutput(result);
274 throw new TimeoutException("CommandStatus was TIMED_OUT, details in host log");
Julien Desprez6961b272016-02-01 09:58:23 +0000275 } else if (result.getStatus() == CommandStatus.FAILED) {
Julien Desprezfab28732019-08-28 09:01:11 -0700276
Guang Zhu5786a912019-06-20 17:42:56 -0700277 logExceptionAndOutput(result);
Julien Desprezfab28732019-08-28 09:01:11 -0700278 if (mIsShellCommand) {
279 // Interpret as communication failure for shell commands
280 throw new IOException("CommandStatus was FAILED, details in host log");
281 } else {
282 mOutput = result.getStdout();
283 return false;
284 }
Julien Desprez6961b272016-02-01 09:58:23 +0000285 }
286 mOutput = result.getStdout();
287 return true;
288 }
289 }
290
jdesprez3d7b9142018-03-02 12:11:23 -0800291 protected class AdbShellAction implements DeviceAction {
292 /** the output from the command */
293 CommandResult mResult = null;
294
295 private String[] mCmd;
296 private long mTimeout;
jovanakf39a78c2019-04-02 17:08:43 -0700297 private File mPipeAsInput; // Used in pushFile, uses local file as input to "content write"
298 private OutputStream mPipeToOutput; // Used in pullFile, to pipe content from "content read"
jdesprez3d7b9142018-03-02 12:11:23 -0800299
jovanakf39a78c2019-04-02 17:08:43 -0700300 AdbShellAction(String[] cmd, File pipeAsInput, OutputStream pipeToOutput, long timeout) {
jdesprez3d7b9142018-03-02 12:11:23 -0800301 mCmd = cmd;
Julien Desprez41bd40b2019-02-27 12:32:48 -0800302 mPipeAsInput = pipeAsInput;
jovanakf39a78c2019-04-02 17:08:43 -0700303 mPipeToOutput = pipeToOutput;
jdesprez3d7b9142018-03-02 12:11:23 -0800304 mTimeout = timeout;
305 }
306
307 @Override
308 public boolean run() throws TimeoutException, IOException {
Julien Desprez41bd40b2019-02-27 12:32:48 -0800309 if (mPipeAsInput != null) {
310 mResult = getRunUtil().runTimedCmdWithInputRedirect(mTimeout, mPipeAsInput, mCmd);
311 } else {
jovanakf39a78c2019-04-02 17:08:43 -0700312 mResult =
313 getRunUtil().runTimedCmd(mTimeout, mPipeToOutput, /* stderr= */ null, mCmd);
Julien Desprez41bd40b2019-02-27 12:32:48 -0800314 }
jdesprez3d7b9142018-03-02 12:11:23 -0800315 if (mResult.getStatus() == CommandStatus.EXCEPTION) {
316 throw new IOException(mResult.getStderr());
317 } else if (mResult.getStatus() == CommandStatus.TIMED_OUT) {
318 throw new TimeoutException(mResult.getStderr());
319 }
320 // If it's not some issue with running the adb command, then we return the CommandResult
321 // which will contain all the infos.
322 return true;
323 }
324 }
325
Julien Desprez78344aa2018-09-04 16:06:05 -0700326 /** {@link DeviceAction} for rebooting a device. */
327 protected class RebootDeviceAction implements DeviceAction {
328
Nikita Ioffef0aa00a2020-01-16 00:11:37 +0000329 private final RebootMode mRebootMode;
330 @Nullable private final String mReason;
Julien Desprez78344aa2018-09-04 16:06:05 -0700331
Nikita Ioffef0aa00a2020-01-16 00:11:37 +0000332 RebootDeviceAction(RebootMode rebootMode, @Nullable String reason) {
333 mRebootMode = rebootMode;
334 mReason = reason;
Julien Desprez78344aa2018-09-04 16:06:05 -0700335 }
336
337 @Override
338 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException {
Nikita Ioffef0aa00a2020-01-16 00:11:37 +0000339 getIDevice().reboot(mRebootMode.formatRebootCommand(mReason));
Julien Desprez78344aa2018-09-04 16:06:05 -0700340 return true;
341 }
342 }
343
Julien Desprez6961b272016-02-01 09:58:23 +0000344 /**
345 * Creates a {@link TestDevice}.
346 *
347 * @param device the associated {@link IDevice}
348 * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use
349 * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes.
350 * Can be null
351 */
Julien Desprez2f34e382016-06-21 12:30:39 +0100352 public NativeDevice(IDevice device, IDeviceStateMonitor stateMonitor,
Julien Desprezc8474552016-02-17 10:59:27 +0000353 IDeviceMonitor allocationMonitor) {
Julien Desprez6961b272016-02-01 09:58:23 +0000354 throwIfNull(device);
355 throwIfNull(stateMonitor);
356 mIDevice = device;
357 mStateMonitor = stateMonitor;
358 mAllocationMonitor = allocationMonitor;
359 }
360
Jeffrey Lu279122f2018-01-29 17:25:08 -0800361 /** Get the {@link RunUtil} instance to use. */
362 @VisibleForTesting
Julien Desprezc8474552016-02-17 10:59:27 +0000363 protected IRunUtil getRunUtil() {
Julien Desprez6961b272016-02-01 09:58:23 +0000364 return RunUtil.getDefault();
365 }
366
Jeffrey Lu279122f2018-01-29 17:25:08 -0800367 /** Set the Clock instance to use. */
368 @VisibleForTesting
369 protected void setClock(Clock clock) {
370 mClock = clock;
371 }
372
Julien Desprez6961b272016-02-01 09:58:23 +0000373 /**
374 * {@inheritDoc}
375 */
376 @Override
377 public void setOptions(TestDeviceOptions options) {
378 throwIfNull(options);
379 mOptions = options;
380 mStateMonitor.setDefaultOnlineTimeout(options.getOnlineTimeout());
381 mStateMonitor.setDefaultAvailableTimeout(options.getAvailableTimeout());
382 }
383
384 /**
385 * Sets the max size of a tmp logcat file.
386 *
387 * @param size max byte size of tmp file
388 */
389 void setTmpLogcatSize(long size) {
390 mOptions.setMaxLogcatDataSize(size);
391 }
392
393 /**
394 * Sets the time in ms to wait before starting logcat capture for a online device.
395 *
396 * @param delay the delay in ms
397 */
Julien Desprezbca52e02017-01-23 09:50:07 +0000398 protected void setLogStartDelay(int delay) {
Julien Desprez6961b272016-02-01 09:58:23 +0000399 mLogStartDelay = delay;
400 }
401
402 /**
403 * {@inheritDoc}
404 */
405 @Override
406 public IDevice getIDevice() {
407 synchronized (mIDevice) {
408 return mIDevice;
409 }
410 }
411
412 /**
413 * {@inheritDoc}
414 */
415 @Override
416 public void setIDevice(IDevice newDevice) {
417 throwIfNull(newDevice);
418 IDevice currentDevice = mIDevice;
419 if (!getIDevice().equals(newDevice)) {
420 synchronized (currentDevice) {
421 mIDevice = newDevice;
422 }
423 mStateMonitor.setIDevice(mIDevice);
424 }
425 }
426
427 /**
428 * {@inheritDoc}
429 */
430 @Override
431 public String getSerialNumber() {
432 return getIDevice().getSerialNumber();
433 }
434
Julien Desprez6961b272016-02-01 09:58:23 +0000435 /**
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800436 * Fetch a device property, from the ddmlib cache by default, and falling back to either `adb
437 * shell getprop` or `fastboot getvar` depending on whether the device is in Fastboot or not.
Julien Desprez6961b272016-02-01 09:58:23 +0000438 *
439 * @param propName The name of the device property as returned by `adb shell getprop`
440 * @param fastbootVar The name of the equivalent fastboot variable to query. if {@code null},
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800441 * fastboot query will not be attempted
442 * @param description A simple description of the variable. First letter should be capitalized.
Julien Desprez6961b272016-02-01 09:58:23 +0000443 * @return A string, possibly {@code null} or empty, containing the value of the given property
444 */
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800445 protected String internalGetProperty(String propName, String fastbootVar, String description)
Julien Desprez6961b272016-02-01 09:58:23 +0000446 throws DeviceNotAvailableException, UnsupportedOperationException {
Julien Desprezf39ff932020-02-25 13:50:35 -0800447 String propValue = getProperty(propName);
Julien Desprez6961b272016-02-01 09:58:23 +0000448 if (propValue != null) {
449 return propValue;
Julien Despreza16120f2020-04-27 08:54:10 -0700450 } else if (isStateBootloaderOrFastbootd() && fastbootVar != null) {
Julien Desprez6961b272016-02-01 09:58:23 +0000451 CLog.i("%s for device %s is null, re-querying in fastboot", description,
452 getSerialNumber());
453 return getFastbootVariable(fastbootVar);
454 } else {
Julien Desprez0abf2f12018-12-19 14:49:21 -0800455 CLog.d(
456 "property collection '%s' for device %s is null.",
457 description, getSerialNumber());
458 return null;
Julien Desprez6961b272016-02-01 09:58:23 +0000459 }
460 }
461
462 /**
463 * {@inheritDoc}
464 */
465 @Override
466 public String getProperty(final String name) throws DeviceNotAvailableException {
jdesprez5fb89732017-08-22 14:50:23 -0700467 if (getIDevice() instanceof StubDevice) {
468 return null;
469 }
Jeffrey Lua6abe552020-01-14 02:56:38 +0000470 if (!TestDeviceState.ONLINE.equals(getDeviceState())) {
471 // Only query property for online device
472 CLog.d("Device %s is not online cannot get property %s.", getSerialNumber(), name);
jdesprezbc580f92017-06-02 11:41:40 -0700473 return null;
474 }
Julien Desprezf39ff932020-02-25 13:50:35 -0800475 String cmd = String.format("getprop %s", name);
476 CommandResult result = executeShellV2Command(cmd);
477 if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
478 CLog.e(
479 "Failed to run '%s' returning null. stdout: %s\nstderr: %s\nexit code: %s",
480 cmd, result.getStdout(), result.getStderr(), result.getExitCode());
481 return null;
482 }
Julien Desprezcfc0e352020-03-09 11:46:23 -0700483 if (result.getStdout() == null || result.getStdout().trim().isEmpty()) {
Julien Desprezf39ff932020-02-25 13:50:35 -0800484 return null;
485 }
486 return result.getStdout().trim();
Julien Desprez6961b272016-02-01 09:58:23 +0000487 }
488
Julien Desprez499c5df2018-07-16 14:05:29 -0700489 /** {@inheritDoc} */
490 @Override
Nikita Ioffe745d97d2019-12-19 00:10:55 +0000491 public long getIntProperty(String name, long defaultValue) throws DeviceNotAvailableException {
492 String value = getProperty(name);
493 if (value == null) {
494 return defaultValue;
495 }
496 try {
497 return Long.parseLong(value);
498 } catch (NumberFormatException e) {
499 return defaultValue;
500 }
501 }
502
503 private static final List<String> TRUE_VALUES = Arrays.asList("1", "y", "yes", "on", "true");
504 private static final List<String> FALSE_VALUES = Arrays.asList("0", "n", "no", "off", "false");
505
506 /** {@inheritDoc} */
507 @Override
508 public boolean getBooleanProperty(String name, boolean defaultValue)
509 throws DeviceNotAvailableException {
510 String value = getProperty(name);
511 if (value == null) {
512 return defaultValue;
513 }
514 if (TRUE_VALUES.contains(value)) {
515 return true;
516 }
517 if (FALSE_VALUES.contains(value)) {
518 return false;
519 }
520 return defaultValue;
521 }
522
523 /** {@inheritDoc} */
524 @Override
Julien Desprez499c5df2018-07-16 14:05:29 -0700525 public boolean setProperty(String propKey, String propValue)
526 throws DeviceNotAvailableException {
527 if (propKey == null || propValue == null) {
528 throw new IllegalArgumentException("set property key or value cannot be null.");
529 }
Nikita Ioffeeaba4422020-01-23 17:03:53 +0000530 String setPropCmd = String.format("\"setprop %s '%s'\"", propKey, propValue);
531 CommandResult result = executeShellV2Command(setPropCmd);
Julien Desprez499c5df2018-07-16 14:05:29 -0700532 if (CommandStatus.SUCCESS.equals(result.getStatus())) {
533 return true;
534 }
Nikita Ioffeeaba4422020-01-23 17:03:53 +0000535 CLog.e(
536 "Something went wrong went setting property %s (command: %s): %s",
537 propKey, setPropCmd, result.getStderr());
Julien Desprez499c5df2018-07-16 14:05:29 -0700538 return false;
539 }
540
Julien Desprez6961b272016-02-01 09:58:23 +0000541 /**
542 * {@inheritDoc}
543 */
Julien Desprez6961b272016-02-01 09:58:23 +0000544 @Override
545 public String getBootloaderVersion() throws UnsupportedOperationException,
546 DeviceNotAvailableException {
547 return internalGetProperty("ro.bootloader", "version-bootloader", "Bootloader");
548 }
549
550 @Override
551 public String getBasebandVersion() throws DeviceNotAvailableException {
552 return internalGetProperty("gsm.version.baseband", "version-baseband", "Baseband");
553 }
554
555 /**
556 * {@inheritDoc}
557 */
558 @Override
559 public String getProductType() throws DeviceNotAvailableException {
560 return internalGetProductType(MAX_RETRY_ATTEMPTS);
561 }
562
563 /**
Julien Desprezc8474552016-02-17 10:59:27 +0000564 * {@link #getProductType()}
Julien Desprez6961b272016-02-01 09:58:23 +0000565 *
Julien Desprezc8474552016-02-17 10:59:27 +0000566 * @param retryAttempts The number of times to try calling {@link #recoverDevice()} if the
Julien Desprez6961b272016-02-01 09:58:23 +0000567 * device's product type cannot be found.
568 */
569 private String internalGetProductType(int retryAttempts) throws DeviceNotAvailableException {
Guang Zhu3ad4f832017-08-28 15:13:25 -0700570 String productType = internalGetProperty(DeviceProperties.BOARD, "product", "Product type");
Guang Zhuc0a99082019-02-27 15:21:27 -0800571 // fallback to ro.hardware for legacy devices
572 if (Strings.isNullOrEmpty(productType)) {
573 productType = internalGetProperty(DeviceProperties.HARDWARE, "product", "Product type");
574 }
Julien Desprez6961b272016-02-01 09:58:23 +0000575
576 // Things will likely break if we don't have a valid product type. Try recovery (in case
577 // the device is only partially booted for some reason), and if that doesn't help, bail.
Guang Zhuc0a99082019-02-27 15:21:27 -0800578 if (Strings.isNullOrEmpty(productType)) {
Julien Desprez6961b272016-02-01 09:58:23 +0000579 if (retryAttempts > 0) {
580 recoverDevice();
581 productType = internalGetProductType(retryAttempts - 1);
582 }
583
Guang Zhuc0a99082019-02-27 15:21:27 -0800584 if (Strings.isNullOrEmpty(productType)) {
Julien Desprez6961b272016-02-01 09:58:23 +0000585 throw new DeviceNotAvailableException(String.format(
Julien Desprez0c6c77c2016-05-31 16:35:57 +0100586 "Could not determine product type for device %s.", getSerialNumber()),
587 getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +0000588 }
589 }
590
Guang Zhud95f1722017-08-31 17:16:47 -0700591 return productType.toLowerCase();
Julien Desprez6961b272016-02-01 09:58:23 +0000592 }
593
594 /**
595 * {@inheritDoc}
596 */
597 @Override
598 public String getFastbootProductType()
599 throws DeviceNotAvailableException, UnsupportedOperationException {
Guang Zhud95f1722017-08-31 17:16:47 -0700600 String prop = getFastbootVariable("product");
601 if (prop != null) {
602 prop = prop.toLowerCase();
603 }
604 return prop;
Julien Desprez6961b272016-02-01 09:58:23 +0000605 }
606
607 /**
608 * {@inheritDoc}
609 */
610 @Override
611 public String getProductVariant() throws DeviceNotAvailableException {
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800612 String prop = internalGetProperty(DeviceProperties.VARIANT, "variant", "Product variant");
613 if (prop == null) {
614 prop =
615 internalGetProperty(
Bowgo Tsai2ae19b52018-10-18 11:58:53 +0800616 DeviceProperties.VARIANT_LEGACY_O_MR1, "variant", "Product variant");
617 }
618 if (prop == null) {
619 prop =
620 internalGetProperty(
621 DeviceProperties.VARIANT_LEGACY_LESS_EQUAL_O,
622 "variant",
623 "Product variant");
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800624 }
Guang Zhud95f1722017-08-31 17:16:47 -0700625 if (prop != null) {
626 prop = prop.toLowerCase();
627 }
628 return prop;
Julien Desprez6961b272016-02-01 09:58:23 +0000629 }
630
631 /**
632 * {@inheritDoc}
633 */
634 @Override
635 public String getFastbootProductVariant()
636 throws DeviceNotAvailableException, UnsupportedOperationException {
Guang Zhud95f1722017-08-31 17:16:47 -0700637 String prop = getFastbootVariable("variant");
638 if (prop != null) {
639 prop = prop.toLowerCase();
640 }
641 return prop;
Julien Desprez6961b272016-02-01 09:58:23 +0000642 }
643
Dan Shifdfbc532020-01-14 10:59:52 -0800644 /** {@inheritDoc} */
645 @Override
646 public String getFastbootVariable(String variableName)
Julien Desprez6961b272016-02-01 09:58:23 +0000647 throws DeviceNotAvailableException, UnsupportedOperationException {
648 CommandResult result = executeFastbootCommand("getvar", variableName);
649 if (result.getStatus() == CommandStatus.SUCCESS) {
650 Pattern fastbootProductPattern = Pattern.compile(variableName + ":\\s(.*)\\s");
651 // fastboot is weird, and may dump the output on stderr instead of stdout
652 String resultText = result.getStdout();
653 if (resultText == null || resultText.length() < 1) {
654 resultText = result.getStderr();
655 }
656 Matcher matcher = fastbootProductPattern.matcher(resultText);
657 if (matcher.find()) {
658 return matcher.group(1);
659 }
660 }
661 return null;
662 }
663
664 /**
665 * {@inheritDoc}
666 */
667 @Override
668 public String getBuildAlias() throws DeviceNotAvailableException {
Julien Desprez52388172019-07-31 16:32:49 -0700669 String alias = getProperty(DeviceProperties.BUILD_ALIAS);
Julien Desprez6961b272016-02-01 09:58:23 +0000670 if (alias == null || alias.isEmpty()) {
671 return getBuildId();
672 }
673 return alias;
674 }
675
676 /**
677 * {@inheritDoc}
678 */
679 @Override
680 public String getBuildId() throws DeviceNotAvailableException {
Julien Desprez52388172019-07-31 16:32:49 -0700681 String bid = getProperty(DeviceProperties.BUILD_ID);
Julien Desprez6961b272016-02-01 09:58:23 +0000682 if (bid == null) {
683 CLog.w("Could not get device %s build id.", getSerialNumber());
684 return IBuildInfo.UNKNOWN_BUILD_ID;
685 }
686 return bid;
687 }
688
689 /**
690 * {@inheritDoc}
691 */
692 @Override
693 public String getBuildFlavor() throws DeviceNotAvailableException {
Julien Desprez52388172019-07-31 16:32:49 -0700694 String buildFlavor = getProperty(DeviceProperties.BUILD_FLAVOR);
Julien Desprez6961b272016-02-01 09:58:23 +0000695 if (buildFlavor != null && !buildFlavor.isEmpty()) {
696 return buildFlavor;
697 }
Julien Desprez52388172019-07-31 16:32:49 -0700698 String productName = getProperty(DeviceProperties.PRODUCT);
699 String buildType = getProperty(DeviceProperties.BUILD_TYPE);
Julien Desprez6961b272016-02-01 09:58:23 +0000700 if (productName == null || buildType == null) {
701 CLog.w("Could not get device %s build flavor.", getSerialNumber());
702 return null;
703 }
704 return String.format("%s-%s", productName, buildType);
705 }
706
707 /**
708 * {@inheritDoc}
709 */
710 @Override
711 public void executeShellCommand(final String command, final IShellOutputReceiver receiver)
712 throws DeviceNotAvailableException {
713 DeviceAction action = new DeviceAction() {
714 @Override
715 public boolean run() throws TimeoutException, IOException,
716 AdbCommandRejectedException, ShellCommandUnresponsiveException {
717 getIDevice().executeShellCommand(command, receiver,
718 mCmdTimeout, TimeUnit.MILLISECONDS);
719 return true;
720 }
721 };
722 performDeviceAction(String.format("shell %s", command), action, MAX_RETRY_ATTEMPTS);
723 }
724
725 /**
726 * {@inheritDoc}
727 */
Julien Desprez6961b272016-02-01 09:58:23 +0000728 @Override
729 public void executeShellCommand(final String command, final IShellOutputReceiver receiver,
730 final long maxTimeToOutputShellResponse, final TimeUnit timeUnit,
731 final int retryAttempts) throws DeviceNotAvailableException {
732 DeviceAction action = new DeviceAction() {
733 @Override
734 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
735 ShellCommandUnresponsiveException {
736 getIDevice().executeShellCommand(command, receiver,
737 maxTimeToOutputShellResponse, timeUnit);
738 return true;
739 }
740 };
741 performDeviceAction(String.format("shell %s", command), action, retryAttempts);
742 }
743
jdesprez2c37cec2017-08-25 12:03:55 -0700744 /** {@inheritDoc} */
745 @Override
746 public void executeShellCommand(
747 final String command,
748 final IShellOutputReceiver receiver,
749 final long maxTimeoutForCommand,
750 final long maxTimeToOutputShellResponse,
751 final TimeUnit timeUnit,
752 final int retryAttempts)
753 throws DeviceNotAvailableException {
754 DeviceAction action =
755 new DeviceAction() {
756 @Override
757 public boolean run()
758 throws TimeoutException, IOException, AdbCommandRejectedException,
759 ShellCommandUnresponsiveException {
760 getIDevice()
761 .executeShellCommand(
762 command,
763 receiver,
764 maxTimeoutForCommand,
765 maxTimeToOutputShellResponse,
766 timeUnit);
767 return true;
768 }
769 };
770 performDeviceAction(String.format("shell %s", command), action, retryAttempts);
771 }
772
Julien Desprez6961b272016-02-01 09:58:23 +0000773 /**
774 * {@inheritDoc}
775 */
776 @Override
777 public String executeShellCommand(String command) throws DeviceNotAvailableException {
778 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
779 executeShellCommand(command, receiver);
780 String output = receiver.getOutput();
Julien Desprez78de5532019-05-17 10:46:27 -0700781 if (mExecuteShellCommandLogs != null) {
782 // Log all output to a dedicated file as it can be very verbose.
783 String formatted =
784 LogUtil.getLogFormatString(
785 LogLevel.VERBOSE,
786 "NativeDevice",
787 String.format(
788 "%s on %s returned %s\n==== END OF OUTPUT ====\n",
789 command, getSerialNumber(), output));
790 try {
791 FileUtil.writeToFile(formatted, mExecuteShellCommandLogs, true);
792 } catch (IOException e) {
793 // Ignore the full error
794 CLog.e("Failed to log to executeShellCommand log: %s", e.getMessage());
795 }
796 }
797 if (output.length() > 80) {
798 CLog.v(
799 "%s on %s returned %s <truncated - See executeShellCommand log for full trace>",
800 command, getSerialNumber(), output.substring(0, 80));
801 } else {
802 CLog.v("%s on %s returned %s", command, getSerialNumber(), output);
803 }
Julien Desprez6961b272016-02-01 09:58:23 +0000804 return output;
805 }
806
jdesprez4de2f552018-02-02 14:38:13 -0800807 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000808 @Override
jdesprez3d7b9142018-03-02 12:11:23 -0800809 public CommandResult executeShellV2Command(String cmd) throws DeviceNotAvailableException {
810 return executeShellV2Command(cmd, getCommandTimeout(), TimeUnit.MILLISECONDS);
811 }
812
813 /** {@inheritDoc} */
814 @Override
Julien Desprez41bd40b2019-02-27 12:32:48 -0800815 public CommandResult executeShellV2Command(String cmd, File pipeAsInput)
816 throws DeviceNotAvailableException {
817 return executeShellV2Command(
jovanakf39a78c2019-04-02 17:08:43 -0700818 cmd,
819 pipeAsInput,
820 null,
821 getCommandTimeout(),
822 TimeUnit.MILLISECONDS,
823 MAX_RETRY_ATTEMPTS);
824 }
825
826 /** {@inheritDoc} */
827 @Override
828 public CommandResult executeShellV2Command(String cmd, OutputStream pipeToOutput)
829 throws DeviceNotAvailableException {
830 return executeShellV2Command(
831 cmd,
832 null,
833 pipeToOutput,
834 getCommandTimeout(),
835 TimeUnit.MILLISECONDS,
836 MAX_RETRY_ATTEMPTS);
Julien Desprez41bd40b2019-02-27 12:32:48 -0800837 }
838
839 /** {@inheritDoc} */
840 @Override
jdesprez3d7b9142018-03-02 12:11:23 -0800841 public CommandResult executeShellV2Command(
842 String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit)
843 throws DeviceNotAvailableException {
jovanakf39a78c2019-04-02 17:08:43 -0700844 return executeShellV2Command(
845 cmd, null, null, maxTimeoutForCommand, timeUnit, MAX_RETRY_ATTEMPTS);
jdesprez3d7b9142018-03-02 12:11:23 -0800846 }
847
848 /** {@inheritDoc} */
849 @Override
850 public CommandResult executeShellV2Command(
851 String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)
852 throws DeviceNotAvailableException {
jovanakf39a78c2019-04-02 17:08:43 -0700853 return executeShellV2Command(
854 cmd, null, null, maxTimeoutForCommand, timeUnit, retryAttempts);
Julien Desprez41bd40b2019-02-27 12:32:48 -0800855 }
856
Paul Trautrim57d82322019-04-12 11:51:03 +0900857 /** {@inheritDoc} */
858 @Override
859 public CommandResult executeShellV2Command(
Julien Desprez41bd40b2019-02-27 12:32:48 -0800860 String cmd,
861 File pipeAsInput,
jovanakf39a78c2019-04-02 17:08:43 -0700862 OutputStream pipeToOutput,
Julien Desprez41bd40b2019-02-27 12:32:48 -0800863 final long maxTimeoutForCommand,
864 final TimeUnit timeUnit,
865 int retryAttempts)
866 throws DeviceNotAvailableException {
jdesprez3d7b9142018-03-02 12:11:23 -0800867 final String[] fullCmd = buildAdbShellCommand(cmd);
868 AdbShellAction adbActionV2 =
jovanakf39a78c2019-04-02 17:08:43 -0700869 new AdbShellAction(
870 fullCmd,
871 pipeAsInput,
872 pipeToOutput,
873 timeUnit.toMillis(maxTimeoutForCommand));
jdesprez3d7b9142018-03-02 12:11:23 -0800874 performDeviceAction(String.format("adb %s", fullCmd[4]), adbActionV2, retryAttempts);
875 return adbActionV2.mResult;
876 }
877
878 /** {@inheritDoc} */
879 @Override
jdesprez4de2f552018-02-02 14:38:13 -0800880 public boolean runInstrumentationTests(
Guang Zhua7dde442019-10-01 19:35:19 +0000881 final IRemoteAndroidTestRunner runner,
882 final Collection<ITestLifeCycleReceiver> listeners)
jdesprez4de2f552018-02-02 14:38:13 -0800883 throws DeviceNotAvailableException {
Guang Zhua7dde442019-10-01 19:35:19 +0000884 RunFailureListener failureListener = new RunFailureListener();
885 List<ITestRunListener> runListeners = new ArrayList<>();
886 runListeners.add(failureListener);
887 runListeners.add(new TestRunToTestInvocationForwarder(listeners));
888
889 DeviceAction runTestsAction =
890 new DeviceAction() {
891 @Override
892 public boolean run()
893 throws IOException, TimeoutException, AdbCommandRejectedException,
894 ShellCommandUnresponsiveException, InstallException,
895 SyncException {
896 runner.run(runListeners);
897 return true;
898 }
899 };
900 boolean result = performDeviceAction(String.format("run %s instrumentation tests",
901 runner.getPackageName()), runTestsAction, 0);
902 if (failureListener.isRunFailure()) {
903 // run failed, might be system crash. Ensure device is up
904 if (mStateMonitor.waitForDeviceAvailable(5 * 1000) == null) {
905 // device isn't up, recover
906 recoverDevice();
907 }
908 }
909 return result;
Julien Desprez6961b272016-02-01 09:58:23 +0000910 }
911
jdesprez4de2f552018-02-02 14:38:13 -0800912 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000913 @Override
Guang Zhua7dde442019-10-01 19:35:19 +0000914 public boolean runInstrumentationTestsAsUser(
915 final IRemoteAndroidTestRunner runner,
916 int userId,
917 final Collection<ITestLifeCycleReceiver> listeners)
jdesprez4de2f552018-02-02 14:38:13 -0800918 throws DeviceNotAvailableException {
Guang Zhua7dde442019-10-01 19:35:19 +0000919 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
920 boolean result = runInstrumentationTests(runner, listeners);
921 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
922 return result;
Julien Desprez6961b272016-02-01 09:58:23 +0000923 }
924
Guang Zhua7dde442019-10-01 19:35:19 +0000925 /**
926 * Helper method to add user run time option to {@link RemoteAndroidTestRunner}
927 *
928 * @param runner {@link IRemoteAndroidTestRunner}
929 * @param userId the integer of the user id to run as.
930 * @return original run time options.
931 */
932 private String appendUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, int userId) {
933 if (runner instanceof RemoteAndroidTestRunner) {
934 String original = ((RemoteAndroidTestRunner) runner).getRunOptions();
935 String userRunTimeOption = String.format("--user %s", Integer.toString(userId));
936 String updated = (original != null) ? (original + " " + userRunTimeOption)
937 : userRunTimeOption;
938 ((RemoteAndroidTestRunner) runner).setRunOptions(updated);
939 return original;
940 } else {
941 throw new IllegalStateException(String.format("%s runner does not support multi-user",
942 runner.getClass().getName()));
943 }
944 }
945
946 /**
947 * Helper method to reset the run time options to {@link RemoteAndroidTestRunner}
948 *
949 * @param runner {@link IRemoteAndroidTestRunner}
950 * @param oldRunTimeOptions
951 */
952 private void resetUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner,
953 String oldRunTimeOptions) {
954 if (runner instanceof RemoteAndroidTestRunner) {
955 if (oldRunTimeOptions != null) {
956 ((RemoteAndroidTestRunner) runner).setRunOptions(oldRunTimeOptions);
957 }
958 } else {
959 throw new IllegalStateException(String.format("%s runner does not support multi-user",
960 runner.getClass().getName()));
961 }
962 }
963
964 private static class RunFailureListener extends StubTestRunListener {
965 private boolean mIsRunFailure = false;
966
967 @Override
968 public void testRunFailed(String message) {
969 mIsRunFailure = true;
970 }
971
972 public boolean isRunFailure() {
973 return mIsRunFailure;
974 }
Julien Desprez6961b272016-02-01 09:58:23 +0000975 }
976
jdesprez4de2f552018-02-02 14:38:13 -0800977 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000978 @Override
jdesprez4de2f552018-02-02 14:38:13 -0800979 public boolean runInstrumentationTests(
980 IRemoteAndroidTestRunner runner, ITestLifeCycleReceiver... listeners)
981 throws DeviceNotAvailableException {
Guang Zhua7dde442019-10-01 19:35:19 +0000982 List<ITestLifeCycleReceiver> listenerList = new ArrayList<>();
983 listenerList.addAll(Arrays.asList(listeners));
984 return runInstrumentationTests(runner, listenerList);
Julien Desprez6961b272016-02-01 09:58:23 +0000985 }
986
jdesprez4de2f552018-02-02 14:38:13 -0800987 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000988 @Override
jdesprez4de2f552018-02-02 14:38:13 -0800989 public boolean runInstrumentationTestsAsUser(
990 IRemoteAndroidTestRunner runner, int userId, ITestLifeCycleReceiver... listeners)
991 throws DeviceNotAvailableException {
Guang Zhua7dde442019-10-01 19:35:19 +0000992 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
993 boolean result = runInstrumentationTests(runner, listeners);
994 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
995 return result;
Julien Desprez6961b272016-02-01 09:58:23 +0000996 }
997
998 /**
999 * {@inheritDoc}
1000 */
1001 @Override
1002 public boolean isRuntimePermissionSupported() throws DeviceNotAvailableException {
Julien Desprezac2ae372020-02-19 10:43:44 -08001003 int apiLevel = getApiLevel();
1004 boolean condition = apiLevel > 22;
1005 if (!condition) {
1006 CLog.w(
1007 "isRuntimePermissionSupported requires api level above 22, device reported "
1008 + "'%s'",
1009 apiLevel);
1010 }
1011 return condition;
Julien Desprez6961b272016-02-01 09:58:23 +00001012 }
1013
1014 /**
Patrick Baumann3395c522020-01-09 12:57:57 -08001015 * {@inheritDoc}
1016 */
1017 @Override
1018 public boolean isAppEnumerationSupported() throws DeviceNotAvailableException {
Patrick Baumann5607d892020-02-18 10:27:11 -08001019 return false;
Patrick Baumann3395c522020-01-09 12:57:57 -08001020 }
1021
1022 /**
Julien Desprez6961b272016-02-01 09:58:23 +00001023 * helper method to throw exception if runtime permission isn't supported
1024 * @throws DeviceNotAvailableException
1025 */
Julien Desprezc8474552016-02-17 10:59:27 +00001026 protected void ensureRuntimePermissionSupported() throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00001027 boolean runtimePermissionSupported = isRuntimePermissionSupported();
1028 if (!runtimePermissionSupported) {
1029 throw new UnsupportedOperationException(
1030 "platform on device does not support runtime permission granting!");
1031 }
1032 }
1033
1034 /**
Julien Desprez6961b272016-02-01 09:58:23 +00001035 * {@inheritDoc}
1036 */
1037 @Override
1038 public String installPackage(final File packageFile, final boolean reinstall,
1039 final String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00001040 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +00001041 }
1042
1043 /**
1044 * {@inheritDoc}
1045 */
1046 @Override
1047 public String installPackage(File packageFile, boolean reinstall, boolean grantPermissions,
1048 String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00001049 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +00001050 }
1051
1052 /**
1053 * {@inheritDoc}
1054 */
1055 @Override
1056 public String installPackageForUser(File packageFile, boolean reinstall, int userId,
1057 String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00001058 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +00001059 }
1060
1061 /**
1062 * {@inheritDoc}
1063 */
1064 @Override
1065 public String installPackageForUser(File packageFile, boolean reinstall,
1066 boolean grantPermissions, int userId, String... extraArgs)
1067 throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00001068 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +00001069 }
1070
1071 /**
1072 * {@inheritDoc}
1073 */
1074 @Override
1075 public String uninstallPackage(final String packageName) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00001076 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +00001077 }
1078
1079 /**
1080 * {@inheritDoc}
1081 */
1082 @Override
1083 public boolean pullFile(final String remoteFilePath, final File localFile)
1084 throws DeviceNotAvailableException {
1085
jovanakf39a78c2019-04-02 17:08:43 -07001086 if (remoteFilePath.startsWith(SD_CARD)) {
1087 ContentProviderHandler handler = getContentProvider();
1088 if (handler != null) {
1089 return handler.pullFile(remoteFilePath, localFile);
1090 }
1091 }
1092
Julien Desprez6961b272016-02-01 09:58:23 +00001093 DeviceAction pullAction = new DeviceAction() {
1094 @Override
1095 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1096 SyncException {
1097 SyncService syncService = null;
1098 boolean status = false;
1099 try {
1100 syncService = getIDevice().getSyncService();
1101 syncService.pullFile(interpolatePathVariables(remoteFilePath),
1102 localFile.getAbsolutePath(), SyncService.getNullProgressMonitor());
1103 status = true;
1104 } catch (SyncException e) {
1105 CLog.w("Failed to pull %s from %s to %s. Message %s", remoteFilePath,
1106 getSerialNumber(), localFile.getAbsolutePath(), e.getMessage());
1107 throw e;
1108 } finally {
1109 if (syncService != null) {
1110 syncService.close();
1111 }
1112 }
1113 return status;
1114 }
1115 };
1116 return performDeviceAction(String.format("pull %s to %s", remoteFilePath,
1117 localFile.getAbsolutePath()), pullAction, MAX_RETRY_ATTEMPTS);
1118 }
1119
1120 /**
1121 * {@inheritDoc}
1122 */
1123 @Override
1124 public File pullFile(String remoteFilePath) throws DeviceNotAvailableException {
1125 File localFile = null;
1126 boolean success = false;
1127 try {
1128 localFile = FileUtil.createTempFileForRemote(remoteFilePath, null);
1129 if (pullFile(remoteFilePath, localFile)) {
1130 success = true;
1131 return localFile;
1132 }
1133 } catch (IOException e) {
Julien Desprez9cd6ca72016-12-19 12:35:15 +00001134 CLog.w("Encountered IOException while trying to pull '%s':", remoteFilePath);
1135 CLog.e(e);
Julien Desprez6961b272016-02-01 09:58:23 +00001136 } finally {
1137 if (!success) {
1138 FileUtil.deleteFile(localFile);
1139 }
1140 }
1141 return null;
1142 }
1143
1144 /**
1145 * {@inheritDoc}
1146 */
1147 @Override
Zach Riggle8c6fce62018-03-03 16:19:28 -06001148 public String pullFileContents(String remoteFilePath) throws DeviceNotAvailableException {
1149 File temp = pullFile(remoteFilePath);
1150
1151 if (temp != null) {
1152 try {
1153 return FileUtil.readStringFromFile(temp);
1154 } catch (IOException e) {
1155 CLog.e(String.format("Could not pull file: %s", remoteFilePath));
1156 } finally {
1157 FileUtil.deleteFile(temp);
1158 }
1159 }
1160
1161 return null;
1162 }
1163
1164 /**
1165 * {@inheritDoc}
1166 */
1167 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001168 public File pullFileFromExternal(String remoteFilePath) throws DeviceNotAvailableException {
1169 String externalPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
Julien Desprez31cf8482020-02-10 11:47:52 -08001170 String fullPath = new File(externalPath, remoteFilePath).getPath();
Julien Desprez6961b272016-02-01 09:58:23 +00001171 return pullFile(fullPath);
1172 }
1173
1174 /**
1175 * Helper function that watches for the string "${EXTERNAL_STORAGE}" and replaces it with the
1176 * pathname of the EXTERNAL_STORAGE mountpoint. Specifically intended to be used for pathnames
1177 * that are being passed to SyncService, which does not support variables inside of filenames.
1178 */
1179 String interpolatePathVariables(String path) {
1180 final String esString = "${EXTERNAL_STORAGE}";
1181 if (path.contains(esString)) {
1182 final String esPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
1183 path = path.replace(esString, esPath);
1184 }
1185 return path;
1186 }
1187
1188 /**
1189 * {@inheritDoc}
1190 */
1191 @Override
1192 public boolean pushFile(final File localFile, final String remoteFilePath)
1193 throws DeviceNotAvailableException {
Julien Desprez1dc6b392019-03-18 11:49:13 -07001194 if (remoteFilePath.startsWith(SD_CARD)) {
1195 ContentProviderHandler handler = getContentProvider();
1196 if (handler != null) {
Julien Desprez1dc6b392019-03-18 11:49:13 -07001197 return handler.pushFile(localFile, remoteFilePath);
1198 }
1199 }
1200
jdesprez3dff70b2017-04-18 10:39:13 -07001201 DeviceAction pushAction =
1202 new DeviceAction() {
1203 @Override
1204 public boolean run()
1205 throws TimeoutException, IOException, AdbCommandRejectedException,
1206 SyncException {
1207 SyncService syncService = null;
1208 boolean status = false;
1209 try {
1210 syncService = getIDevice().getSyncService();
1211 if (syncService == null) {
1212 throw new IOException("SyncService returned null.");
1213 }
1214 syncService.pushFile(
1215 localFile.getAbsolutePath(),
1216 interpolatePathVariables(remoteFilePath),
1217 SyncService.getNullProgressMonitor());
1218 status = true;
1219 } catch (SyncException e) {
1220 CLog.w(
jdesprez791fc5d2017-08-02 11:25:48 -07001221 "Failed to push %s to %s on device %s. Message: '%s'. "
1222 + "Error code: %s",
jdesprez3dff70b2017-04-18 10:39:13 -07001223 localFile.getAbsolutePath(),
1224 remoteFilePath,
1225 getSerialNumber(),
jdesprez791fc5d2017-08-02 11:25:48 -07001226 e.getMessage(),
1227 e.getErrorCode());
1228 // TODO: check if ddmlib can report a better error
1229 if (SyncError.TRANSFER_PROTOCOL_ERROR.equals(e.getErrorCode())) {
1230 if (e.getMessage().contains("Permission denied")) {
1231 return false;
1232 }
1233 }
jdesprez3dff70b2017-04-18 10:39:13 -07001234 throw e;
1235 } finally {
1236 if (syncService != null) {
1237 syncService.close();
1238 }
1239 }
1240 return status;
Julien Desprez6961b272016-02-01 09:58:23 +00001241 }
jdesprez3dff70b2017-04-18 10:39:13 -07001242 };
Julien Desprez6961b272016-02-01 09:58:23 +00001243 return performDeviceAction(String.format("push %s to %s", localFile.getAbsolutePath(),
1244 remoteFilePath), pushAction, MAX_RETRY_ATTEMPTS);
1245 }
1246
1247 /**
1248 * {@inheritDoc}
1249 */
1250 @Override
1251 public boolean pushString(final String contents, final String remoteFilePath)
1252 throws DeviceNotAvailableException {
1253 File tmpFile = null;
1254 try {
1255 tmpFile = FileUtil.createTempFile("temp", ".txt");
1256 FileUtil.writeToFile(contents, tmpFile);
1257 return pushFile(tmpFile, remoteFilePath);
1258 } catch (IOException e) {
1259 CLog.e(e);
1260 return false;
1261 } finally {
Julien Desprez1320e592016-12-06 09:51:53 +00001262 FileUtil.deleteFile(tmpFile);
Julien Desprez6961b272016-02-01 09:58:23 +00001263 }
1264 }
1265
Julien Desprez901d4962019-03-27 10:10:23 -07001266 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00001267 @Override
Julien Desprez901d4962019-03-27 10:10:23 -07001268 public boolean doesFileExist(String deviceFilePath) throws DeviceNotAvailableException {
1269 String lsGrep = executeShellCommand(String.format("ls \"%s\"", deviceFilePath));
Julien Desprez6961b272016-02-01 09:58:23 +00001270 return !lsGrep.contains("No such file or directory");
1271 }
1272
Julien Desprez901d4962019-03-27 10:10:23 -07001273 /** {@inheritDoc} */
1274 @Override
1275 public void deleteFile(String deviceFilePath) throws DeviceNotAvailableException {
Julien Desprez6e5d83c2019-04-02 09:27:22 -07001276 if (deviceFilePath.startsWith(SD_CARD)) {
1277 ContentProviderHandler handler = getContentProvider();
1278 if (handler != null) {
1279 if (handler.deleteFile(deviceFilePath)) {
1280 return;
1281 }
1282 }
1283 }
1284 // Fallback to the direct command if content provider is unsuccessful
Julien Desprezed378b92019-04-19 12:31:57 -07001285 String path = StringEscapeUtils.escapeShell(deviceFilePath);
1286 // Escape spaces to handle filename with spaces
1287 path = path.replaceAll(" ", "\\ ");
1288 executeShellCommand(String.format("rm -rf %s", StringEscapeUtils.escapeShell(path)));
Julien Desprez901d4962019-03-27 10:10:23 -07001289 }
1290
Julien Desprez6961b272016-02-01 09:58:23 +00001291 /**
1292 * {@inheritDoc}
1293 */
1294 @Override
1295 public long getExternalStoreFreeSpace() throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00001296 String externalStorePath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
Arthur Eubankse4531612017-10-23 11:30:52 -07001297 return getPartitionFreeSpace(externalStorePath);
1298 }
1299
1300 /** {@inheritDoc} */
1301 @Override
1302 public long getPartitionFreeSpace(String partition) throws DeviceNotAvailableException {
1303 CLog.i("Checking free space for %s on partition %s", getSerialNumber(), partition);
1304 String output = getDfOutput(partition);
Julien Desprez6961b272016-02-01 09:58:23 +00001305 // Try coreutils/toybox style output first.
1306 Long available = parseFreeSpaceFromModernOutput(output);
1307 if (available != null) {
1308 return available;
1309 }
1310 // Then the two legacy toolbox formats.
1311 available = parseFreeSpaceFromAvailable(output);
1312 if (available != null) {
1313 return available;
1314 }
Arthur Eubankse4531612017-10-23 11:30:52 -07001315 available = parseFreeSpaceFromFree(partition, output);
Julien Desprez6961b272016-02-01 09:58:23 +00001316 if (available != null) {
1317 return available;
1318 }
1319
1320 CLog.e("free space command output \"%s\" did not match expected patterns", output);
1321 return 0;
1322 }
1323
1324 /**
1325 * Run the 'df' shell command and return output, making multiple attempts if necessary.
1326 *
1327 * @param externalStorePath the path to check
1328 * @return the output from 'shell df path'
1329 * @throws DeviceNotAvailableException
1330 */
1331 private String getDfOutput(String externalStorePath) throws DeviceNotAvailableException {
1332 for (int i=0; i < MAX_RETRY_ATTEMPTS; i++) {
1333 String output = executeShellCommand(String.format("df %s", externalStorePath));
1334 if (output.trim().length() > 0) {
1335 return output;
1336 }
1337 }
1338 throw new DeviceUnresponsiveException(String.format(
1339 "Device %s not returning output from df command after %d attempts",
Julien Desprez0c6c77c2016-05-31 16:35:57 +01001340 getSerialNumber(), MAX_RETRY_ATTEMPTS), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00001341 }
1342
1343 /**
1344 * Parses a partition's available space from the legacy output of a 'df' command, used
1345 * pre-gingerbread.
1346 * <p/>
1347 * Assumes output format of:
1348 * <br>/
1349 * <code>
1350 * [partition]: 15659168K total, 51584K used, 15607584K available (block size 32768)
1351 * </code>
1352 * @param dfOutput the output of df command to parse
1353 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1354 */
1355 private Long parseFreeSpaceFromAvailable(String dfOutput) {
1356 final Pattern freeSpacePattern = Pattern.compile("(\\d+)K available");
1357 Matcher patternMatcher = freeSpacePattern.matcher(dfOutput);
1358 if (patternMatcher.find()) {
1359 String freeSpaceString = patternMatcher.group(1);
1360 try {
1361 return Long.parseLong(freeSpaceString);
1362 } catch (NumberFormatException e) {
1363 // fall through
1364 }
1365 }
1366 return null;
1367 }
1368
1369 /**
1370 * Parses a partition's available space from the 'table-formatted' output of a toolbox 'df'
1371 * command, used from gingerbread to lollipop.
1372 * <p/>
1373 * Assumes output format of:
1374 * <br/>
1375 * <code>
1376 * Filesystem Size Used Free Blksize
1377 * <br/>
1378 * [partition]: 3G 790M 2G 4096
1379 * </code>
1380 * @param dfOutput the output of df command to parse
1381 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1382 */
1383 Long parseFreeSpaceFromFree(String externalStorePath, String dfOutput) {
1384 Long freeSpace = null;
1385 final Pattern freeSpaceTablePattern = Pattern.compile(String.format(
1386 //fs Size Used Free
1387 "%s\\s+[\\w\\d\\.]+\\s+[\\w\\d\\.]+\\s+([\\d\\.]+)(\\w)", externalStorePath));
1388 Matcher tablePatternMatcher = freeSpaceTablePattern.matcher(dfOutput);
1389 if (tablePatternMatcher.find()) {
1390 String numericValueString = tablePatternMatcher.group(1);
1391 String unitType = tablePatternMatcher.group(2);
1392 try {
1393 Float freeSpaceFloat = Float.parseFloat(numericValueString);
1394 if (unitType.equals("M")) {
1395 freeSpaceFloat = freeSpaceFloat * 1024;
1396 } else if (unitType.equals("G")) {
1397 freeSpaceFloat = freeSpaceFloat * 1024 * 1024;
1398 }
1399 freeSpace = freeSpaceFloat.longValue();
1400 } catch (NumberFormatException e) {
1401 // fall through
1402 }
1403 }
1404 return freeSpace;
1405 }
1406
1407 /**
1408 * Parses a partition's available space from the modern coreutils/toybox 'df' output, used
1409 * after lollipop.
1410 * <p/>
1411 * Assumes output format of:
1412 * <br/>
1413 * <code>
1414 * Filesystem 1K-blocks Used Available Use% Mounted on
1415 * <br/>
1416 * /dev/fuse 11585536 1316348 10269188 12% /mnt/shell/emulated
1417 * </code>
1418 * @param dfOutput the output of df command to parse
1419 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1420 */
1421 Long parseFreeSpaceFromModernOutput(String dfOutput) {
1422 Matcher matcher = DF_PATTERN.matcher(dfOutput);
1423 if (matcher.find()) {
1424 try {
1425 return Long.parseLong(matcher.group(1));
1426 } catch (NumberFormatException e) {
1427 // fall through
1428 }
1429 }
1430 return null;
1431 }
1432
1433 /**
1434 * {@inheritDoc}
1435 */
1436 @Override
1437 public String getMountPoint(String mountName) {
1438 return mStateMonitor.getMountPoint(mountName);
1439 }
1440
1441 /**
1442 * {@inheritDoc}
1443 */
1444 @Override
1445 public List<MountPointInfo> getMountPointInfo() throws DeviceNotAvailableException {
1446 final String mountInfo = executeShellCommand("cat /proc/mounts");
1447 final String[] mountInfoLines = mountInfo.split("\r?\n");
Eric Rowe1abf2c02017-03-20 17:12:33 -07001448 List<MountPointInfo> list = new ArrayList<>(mountInfoLines.length);
Julien Desprez6961b272016-02-01 09:58:23 +00001449
1450 for (String line : mountInfoLines) {
1451 // We ignore the last two fields
1452 // /dev/block/mtdblock4 /cache yaffs2 rw,nosuid,nodev,relatime 0 0
1453 final String[] parts = line.split("\\s+", 5);
1454 list.add(new MountPointInfo(parts[0], parts[1], parts[2], parts[3]));
1455 }
1456
1457 return list;
1458 }
1459
1460 /**
1461 * {@inheritDoc}
1462 */
1463 @Override
1464 public MountPointInfo getMountPointInfo(String mountpoint) throws DeviceNotAvailableException {
1465 // The overhead of parsing all of the lines should be minimal
1466 List<MountPointInfo> mountpoints = getMountPointInfo();
1467 for (MountPointInfo info : mountpoints) {
1468 if (mountpoint.equals(info.mountpoint)) return info;
1469 }
1470 return null;
1471 }
1472
1473 /**
1474 * {@inheritDoc}
1475 */
1476 @Override
1477 public IFileEntry getFileEntry(String path) throws DeviceNotAvailableException {
1478 path = interpolatePathVariables(path);
1479 String[] pathComponents = path.split(FileListingService.FILE_SEPARATOR);
1480 FileListingService service = getFileListingService();
1481 IFileEntry rootFile = new FileEntryWrapper(this, service.getRoot());
1482 return FileEntryWrapper.getDescendant(rootFile, Arrays.asList(pathComponents));
1483 }
1484
1485 /**
jdesprez934653e2018-02-13 12:48:48 -08001486 * Unofficial helper to get a {@link FileEntry} from a non-root path. FIXME: Refactor the
1487 * FileEntry system to have it available from any path. (even non root).
1488 *
1489 * @param entry a {@link FileEntry} not necessarily root as Ddmlib requires.
1490 * @return a {@link FileEntryWrapper} representing the FileEntry.
1491 * @throws DeviceNotAvailableException
1492 */
1493 public IFileEntry getFileEntry(FileEntry entry) throws DeviceNotAvailableException {
Julien Desprez41e09ab2018-07-24 11:07:17 -07001494 // FileEntryWrapper is going to construct the list of child file internally.
jdesprez934653e2018-02-13 12:48:48 -08001495 return new FileEntryWrapper(this, entry);
1496 }
1497
Julien Desprez41e09ab2018-07-24 11:07:17 -07001498 /** {@inheritDoc} */
1499 @Override
1500 public boolean isExecutable(String fullPath) throws DeviceNotAvailableException {
1501 String fileMode = executeShellCommand(String.format("ls -l %s", fullPath));
1502 if (fileMode != null) {
1503 return EXE_FILE.matcher(fileMode).find();
1504 }
1505 return false;
1506 }
1507
jdesprez934653e2018-02-13 12:48:48 -08001508 /**
Julien Desprez56f18e02016-03-11 14:40:18 +00001509 * {@inheritDoc}
1510 */
1511 @Override
1512 public boolean isDirectory(String path) throws DeviceNotAvailableException {
1513 return executeShellCommand(String.format("ls -ld %s", path)).charAt(0) == 'd';
1514 }
1515
1516 /**
1517 * {@inheritDoc}
1518 */
1519 @Override
1520 public String[] getChildren(String path) throws DeviceNotAvailableException {
1521 String lsOutput = executeShellCommand(String.format("ls -A1 %s", path));
1522 if (lsOutput.trim().isEmpty()) {
1523 return new String[0];
1524 }
1525 return lsOutput.split("\r?\n");
1526 }
1527
1528 /**
Julien Desprez6961b272016-02-01 09:58:23 +00001529 * Retrieve the {@link FileListingService} for the {@link IDevice}, making multiple attempts
1530 * and recovery operations if necessary.
1531 * <p/>
1532 * This is necessary because {@link IDevice#getFileListingService()} can return
1533 * <code>null</code> if device is in fastboot. The symptom of this condition is that the
1534 * current {@link #getIDevice()} is a {@link StubDevice}.
1535 *
1536 * @return the {@link FileListingService}
1537 * @throws DeviceNotAvailableException if device communication is lost.
1538 */
1539 private FileListingService getFileListingService() throws DeviceNotAvailableException {
1540 final FileListingService[] service = new FileListingService[1];
1541 DeviceAction serviceAction = new DeviceAction() {
1542 @Override
1543 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
1544 ShellCommandUnresponsiveException, InstallException, SyncException {
1545 service[0] = getIDevice().getFileListingService();
1546 if (service[0] == null) {
1547 // could not get file listing service - must be a stub device - enter recovery
1548 throw new IOException("Could not get file listing service");
1549 }
1550 return true;
1551 }
1552 };
1553 performDeviceAction("getFileListingService", serviceAction, MAX_RETRY_ATTEMPTS);
1554 return service[0];
1555 }
1556
1557 /**
1558 * {@inheritDoc}
1559 */
1560 @Override
1561 public boolean pushDir(File localFileDir, String deviceFilePath)
1562 throws DeviceNotAvailableException {
Julien Desprez69324072018-08-14 14:08:47 -07001563 return pushDir(localFileDir, deviceFilePath, new HashSet<>());
1564 }
1565
1566 /** {@inheritDoc} */
1567 @Override
1568 public boolean pushDir(
1569 File localFileDir, String deviceFilePath, Set<String> excludedDirectories)
1570 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00001571 if (!localFileDir.isDirectory()) {
1572 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
1573 return false;
1574 }
1575 File[] childFiles = localFileDir.listFiles();
1576 if (childFiles == null) {
1577 CLog.e("Could not read files in %s", localFileDir.getAbsolutePath());
1578 return false;
1579 }
1580 for (File childFile : childFiles) {
1581 String remotePath = String.format("%s/%s", deviceFilePath, childFile.getName());
1582 if (childFile.isDirectory()) {
Julien Desprez69324072018-08-14 14:08:47 -07001583 // If we encounter a filtered directory do not push it.
1584 if (excludedDirectories.contains(childFile.getName())) {
1585 CLog.d(
1586 "%s directory was not pushed because it was filtered.",
1587 childFile.getAbsolutePath());
1588 continue;
1589 }
Hyungtae Tim Kim4cff14e2016-08-01 18:29:48 +09001590 executeShellCommand(String.format("mkdir -p \"%s\"", remotePath));
Julien Desprez69324072018-08-14 14:08:47 -07001591 if (!pushDir(childFile, remotePath, excludedDirectories)) {
Julien Desprez6961b272016-02-01 09:58:23 +00001592 return false;
1593 }
1594 } else if (childFile.isFile()) {
1595 if (!pushFile(childFile, remotePath)) {
1596 return false;
1597 }
1598 }
1599 }
1600 return true;
1601 }
1602
1603 /**
1604 * {@inheritDoc}
1605 */
1606 @Override
Guang Zhud7088362016-06-28 18:41:10 -07001607 public boolean pullDir(String deviceFilePath, File localDir)
1608 throws DeviceNotAvailableException {
jovanak0eaec7e2019-04-12 17:54:46 -07001609 if (deviceFilePath.startsWith(SD_CARD)) {
1610 ContentProviderHandler handler = getContentProvider();
1611 if (handler != null) {
1612 return handler.pullDir(deviceFilePath, localDir);
1613 }
1614 }
1615
Guang Zhud7088362016-06-28 18:41:10 -07001616 if (!localDir.isDirectory()) {
1617 CLog.e("Local path %s is not a directory", localDir.getAbsolutePath());
1618 return false;
1619 }
Julien Desprez45078b72019-11-21 09:55:59 -08001620 if (!doesFileExist(deviceFilePath)) {
1621 CLog.e("Device path %s does not exists to be pulled.", deviceFilePath);
1622 }
Guang Zhud7088362016-06-28 18:41:10 -07001623 if (!isDirectory(deviceFilePath)) {
1624 CLog.e("Device path %s is not a directory", deviceFilePath);
1625 return false;
1626 }
jdesprezb1469112018-02-15 09:57:25 -08001627 FileEntry entryRoot =
1628 new FileEntry(null, deviceFilePath, FileListingService.TYPE_DIRECTORY, false);
jdesprez934653e2018-02-13 12:48:48 -08001629 IFileEntry entry = getFileEntry(entryRoot);
jdesprez104458a2018-02-06 11:32:59 -08001630 Collection<IFileEntry> children = entry.getChildren(false);
1631 if (children.isEmpty()) {
Guang Zhud7088362016-06-28 18:41:10 -07001632 CLog.i("Device path is empty, nothing to do.");
1633 return true;
1634 }
jdesprez104458a2018-02-06 11:32:59 -08001635 for (IFileEntry item : children) {
1636 if (item.isDirectory()) {
Guang Zhud7088362016-06-28 18:41:10 -07001637 // handle sub dir
jdesprez104458a2018-02-06 11:32:59 -08001638 File subDir = new File(localDir, item.getName());
Guang Zhud7088362016-06-28 18:41:10 -07001639 if (!subDir.mkdir()) {
1640 CLog.w("Failed to create sub directory %s, aborting.",
1641 subDir.getAbsolutePath());
1642 return false;
1643 }
jdesprez104458a2018-02-06 11:32:59 -08001644 String deviceSubDir = item.getFullPath();
Guang Zhud7088362016-06-28 18:41:10 -07001645 if (!pullDir(deviceSubDir, subDir)) {
1646 CLog.w("Failed to pull sub directory %s from device, aborting", deviceSubDir);
1647 return false;
1648 }
1649 } else {
1650 // handle regular file
jdesprez104458a2018-02-06 11:32:59 -08001651 File localFile = new File(localDir, item.getName());
1652 String fullPath = item.getFullPath();
1653 if (!pullFile(fullPath, localFile)) {
1654 CLog.w("Failed to pull file %s from device, aborting", fullPath);
Guang Zhud7088362016-06-28 18:41:10 -07001655 return false;
1656 }
1657 }
1658 }
1659 return true;
1660 }
1661
1662 /**
1663 * {@inheritDoc}
1664 */
1665 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001666 public boolean syncFiles(File localFileDir, String deviceFilePath)
1667 throws DeviceNotAvailableException {
1668 if (localFileDir == null || deviceFilePath == null) {
1669 throw new IllegalArgumentException("syncFiles does not take null arguments");
1670 }
1671 CLog.i("Syncing %s to %s on device %s",
1672 localFileDir.getAbsolutePath(), deviceFilePath, getSerialNumber());
1673 if (!localFileDir.isDirectory()) {
1674 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
1675 return false;
1676 }
1677 // get the real destination path. This is done because underlying syncService.push
1678 // implementation will add localFileDir.getName() to destination path
1679 deviceFilePath = String.format("%s/%s", interpolatePathVariables(deviceFilePath),
1680 localFileDir.getName());
1681 if (!doesFileExist(deviceFilePath)) {
Hyungtae Tim Kim4cff14e2016-08-01 18:29:48 +09001682 executeShellCommand(String.format("mkdir -p \"%s\"", deviceFilePath));
Julien Desprez6961b272016-02-01 09:58:23 +00001683 }
1684 IFileEntry remoteFileEntry = getFileEntry(deviceFilePath);
1685 if (remoteFileEntry == null) {
1686 CLog.e("Could not find remote file entry %s ", deviceFilePath);
1687 return false;
1688 }
1689
1690 return syncFiles(localFileDir, remoteFileEntry);
1691 }
1692
1693 /**
1694 * Recursively sync newer files.
1695 *
1696 * @param localFileDir the local {@link File} directory to sync
1697 * @param remoteFileEntry the remote destination {@link IFileEntry}
1698 * @return <code>true</code> if files were synced successfully
1699 * @throws DeviceNotAvailableException
1700 */
1701 private boolean syncFiles(File localFileDir, final IFileEntry remoteFileEntry)
1702 throws DeviceNotAvailableException {
1703 CLog.d("Syncing %s to %s on %s", localFileDir.getAbsolutePath(),
1704 remoteFileEntry.getFullPath(), getSerialNumber());
1705 // find newer files to sync
1706 File[] localFiles = localFileDir.listFiles(new NoHiddenFilesFilter());
Eric Rowe1abf2c02017-03-20 17:12:33 -07001707 ArrayList<String> filePathsToSync = new ArrayList<>();
Julien Desprez6961b272016-02-01 09:58:23 +00001708 for (File localFile : localFiles) {
1709 IFileEntry entry = remoteFileEntry.findChild(localFile.getName());
1710 if (entry == null) {
1711 CLog.d("Detected missing file path %s", localFile.getAbsolutePath());
1712 filePathsToSync.add(localFile.getAbsolutePath());
1713 } else if (localFile.isDirectory()) {
1714 // This directory exists remotely. recursively sync it to sync only its newer files
1715 // contents
1716 if (!syncFiles(localFile, entry)) {
1717 return false;
1718 }
1719 } else if (isNewer(localFile, entry)) {
1720 CLog.d("Detected newer file %s", localFile.getAbsolutePath());
1721 filePathsToSync.add(localFile.getAbsolutePath());
1722 }
1723 }
1724
1725 if (filePathsToSync.size() == 0) {
1726 CLog.d("No files to sync");
1727 return true;
1728 }
1729 final String files[] = filePathsToSync.toArray(new String[filePathsToSync.size()]);
1730 DeviceAction syncAction = new DeviceAction() {
1731 @Override
1732 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1733 SyncException {
1734 SyncService syncService = null;
1735 boolean status = false;
1736 try {
1737 syncService = getIDevice().getSyncService();
1738 syncService.push(files, remoteFileEntry.getFileEntry(),
1739 SyncService.getNullProgressMonitor());
1740 status = true;
1741 } catch (SyncException e) {
1742 CLog.w("Failed to sync files to %s on device %s. Message %s",
1743 remoteFileEntry.getFullPath(), getSerialNumber(), e.getMessage());
1744 throw e;
1745 } finally {
1746 if (syncService != null) {
1747 syncService.close();
1748 }
1749 }
1750 return status;
1751 }
1752 };
1753 return performDeviceAction(String.format("sync files %s", remoteFileEntry.getFullPath()),
1754 syncAction, MAX_RETRY_ATTEMPTS);
1755 }
1756
1757 /**
1758 * Queries the file listing service for a given directory
1759 *
1760 * @param remoteFileEntry
1761 * @throws DeviceNotAvailableException
1762 */
1763 FileEntry[] getFileChildren(final FileEntry remoteFileEntry)
1764 throws DeviceNotAvailableException {
1765 // time this operation because its known to hang
1766 FileQueryAction action = new FileQueryAction(remoteFileEntry,
1767 getIDevice().getFileListingService());
1768 performDeviceAction("buildFileCache", action, MAX_RETRY_ATTEMPTS);
1769 return action.mFileContents;
1770 }
1771
1772 private class FileQueryAction implements DeviceAction {
1773
1774 FileEntry[] mFileContents = null;
1775 private final FileEntry mRemoteFileEntry;
1776 private final FileListingService mService;
1777
1778 FileQueryAction(FileEntry remoteFileEntry, FileListingService service) {
1779 throwIfNull(remoteFileEntry);
1780 throwIfNull(service);
1781 mRemoteFileEntry = remoteFileEntry;
1782 mService = service;
1783 }
1784
1785 @Override
1786 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1787 ShellCommandUnresponsiveException {
1788 mFileContents = mService.getChildrenSync(mRemoteFileEntry);
1789 return true;
1790 }
1791 }
1792
1793 /**
1794 * A {@link FilenameFilter} that rejects hidden (ie starts with ".") files.
1795 */
1796 private static class NoHiddenFilesFilter implements FilenameFilter {
1797 /**
1798 * {@inheritDoc}
1799 */
1800 @Override
1801 public boolean accept(File dir, String name) {
1802 return !name.startsWith(".");
1803 }
1804 }
1805
1806 /**
Julien Desprez7c15ac42016-08-10 16:45:44 +01001807 * helper to get the timezone from the device. Example: "Europe/London"
Julien Desprez6961b272016-02-01 09:58:23 +00001808 */
Julien Desprez7c15ac42016-08-10 16:45:44 +01001809 private String getDeviceTimezone() {
Julien Desprez6961b272016-02-01 09:58:23 +00001810 try {
Julien Desprez7c15ac42016-08-10 16:45:44 +01001811 // This may not be set at first, default to GMT in this case.
1812 String timezone = getProperty("persist.sys.timezone");
1813 if (timezone != null) {
1814 return timezone.trim();
1815 }
1816 } catch (DeviceNotAvailableException e) {
1817 // Fall through on purpose
1818 }
1819 return "GMT";
1820 }
1821
1822 /**
Julien Desprez75518d32016-11-18 09:54:34 +00001823 * Return <code>true</code> if local file is newer than remote file. {@link IFileEntry} being
1824 * accurate to the minute, in case of equal times, the file will be considered newer.
Julien Desprez7c15ac42016-08-10 16:45:44 +01001825 */
Jeffrey Lu279122f2018-01-29 17:25:08 -08001826 @VisibleForTesting
Julien Desprez7c15ac42016-08-10 16:45:44 +01001827 protected boolean isNewer(File localFile, IFileEntry entry) {
1828 final String entryTimeString = String.format("%s %s", entry.getDate(), entry.getTime());
1829 try {
1830 String timezone = getDeviceTimezone();
Julien Desprez6961b272016-02-01 09:58:23 +00001831 // expected format of a FileEntry's date and time
Julien Desprez7c15ac42016-08-10 16:45:44 +01001832 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
1833 format.setTimeZone(TimeZone.getTimeZone(timezone));
Julien Desprez6961b272016-02-01 09:58:23 +00001834 Date remoteDate = format.parse(entryTimeString);
Julien Desprez7c15ac42016-08-10 16:45:44 +01001835
1836 long offset = 0;
1837 try {
1838 offset = getDeviceTimeOffset(null);
1839 } catch (DeviceNotAvailableException e) {
1840 offset = 0;
1841 }
1842 CLog.i("Device offset time: %s", offset);
1843
Julien Desprez6961b272016-02-01 09:58:23 +00001844 // localFile.lastModified has granularity of ms, but remoteDate.getTime only has
1845 // granularity of minutes. Shift remoteDate.getTime() backward by one minute so newly
1846 // modified files get synced
Julien Desprez5aa8c6e2016-10-27 10:12:13 +01001847 return localFile.lastModified() > (remoteDate.getTime() - 60 * 1000 + offset);
Julien Desprez6961b272016-02-01 09:58:23 +00001848 } catch (ParseException e) {
1849 CLog.e("Error converting remote time stamp %s for %s on device %s", entryTimeString,
1850 entry.getFullPath(), getSerialNumber());
1851 }
1852 // sync file by default
1853 return true;
1854 }
1855
1856 /**
1857 * {@inheritDoc}
1858 */
1859 @Override
1860 public String executeAdbCommand(String... cmdArgs) throws DeviceNotAvailableException {
Guang Zhu6e5df882019-05-24 14:20:06 -07001861 return executeAdbCommand(getCommandTimeout(), cmdArgs);
1862 }
1863
1864 /** {@inheritDoc} */
1865 @Override
1866 public String executeAdbCommand(long timeout, String... cmdArgs)
1867 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00001868 final String[] fullCmd = buildAdbCommand(cmdArgs);
Julien Desprezfab28732019-08-28 09:01:11 -07001869 AdbAction adbAction = new AdbAction(timeout, fullCmd, "shell".equals(cmdArgs[0]));
Julien Desprez6961b272016-02-01 09:58:23 +00001870 performDeviceAction(String.format("adb %s", cmdArgs[0]), adbAction, MAX_RETRY_ATTEMPTS);
1871 return adbAction.mOutput;
1872 }
1873
1874 /**
1875 * {@inheritDoc}
1876 */
1877 @Override
1878 public CommandResult executeFastbootCommand(String... cmdArgs)
1879 throws DeviceNotAvailableException, UnsupportedOperationException {
1880 return doFastbootCommand(getCommandTimeout(), cmdArgs);
1881 }
1882
1883 /**
1884 * {@inheritDoc}
1885 */
1886 @Override
Julien Desprezf7d1e0d2016-06-01 09:32:38 +01001887 public CommandResult executeFastbootCommand(long timeout, String... cmdArgs)
1888 throws DeviceNotAvailableException, UnsupportedOperationException {
1889 return doFastbootCommand(timeout, cmdArgs);
1890 }
1891
1892 /**
1893 * {@inheritDoc}
1894 */
1895 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001896 public CommandResult executeLongFastbootCommand(String... cmdArgs)
1897 throws DeviceNotAvailableException, UnsupportedOperationException {
1898 return doFastbootCommand(getLongCommandTimeout(), cmdArgs);
1899 }
1900
1901 /**
1902 * @param cmdArgs
1903 * @throws DeviceNotAvailableException
1904 */
1905 private CommandResult doFastbootCommand(final long timeout, String... cmdArgs)
1906 throws DeviceNotAvailableException, UnsupportedOperationException {
1907 if (!mFastbootEnabled) {
1908 throw new UnsupportedOperationException(String.format(
1909 "Attempted to fastboot on device %s , but fastboot is not available. Aborting.",
1910 getSerialNumber()));
1911 }
1912 final String[] fullCmd = buildFastbootCommand(cmdArgs);
1913 for (int i = 0; i < MAX_RETRY_ATTEMPTS; i++) {
Guang Zhu98e1e5c2017-11-03 09:47:37 -07001914 File fastbootTmpDir = getHostOptions().getFastbootTmpDir();
Guang Zhu26cca482017-11-01 18:18:08 -07001915 IRunUtil runUtil = null;
1916 if (fastbootTmpDir != null) {
1917 runUtil = new RunUtil();
Guang Zhu98e1e5c2017-11-03 09:47:37 -07001918 runUtil.setEnvVariable("TMPDIR", fastbootTmpDir.getAbsolutePath());
Guang Zhu26cca482017-11-01 18:18:08 -07001919 } else {
1920 runUtil = getRunUtil();
1921 }
Julien Desprez6961b272016-02-01 09:58:23 +00001922 CommandResult result = new CommandResult(CommandStatus.EXCEPTION);
1923 // block state changes while executing a fastboot command, since
1924 // device will disappear from fastboot devices while command is being executed
1925 mFastbootLock.lock();
1926 try {
Guang Zhu26cca482017-11-01 18:18:08 -07001927 result = runUtil.runTimedCmd(timeout, fullCmd);
Julien Desprez6961b272016-02-01 09:58:23 +00001928 } finally {
1929 mFastbootLock.unlock();
1930 }
1931 if (!isRecoveryNeeded(result)) {
1932 return result;
1933 }
1934 CLog.w("Recovery needed after executing fastboot command");
1935 if (result != null) {
1936 CLog.v("fastboot command output:\nstdout: %s\nstderr:%s",
1937 result.getStdout(), result.getStderr());
1938 }
1939 recoverDeviceFromBootloader();
1940 }
1941 throw new DeviceUnresponsiveException(String.format("Attempted fastboot %s multiple "
1942 + "times on device %s without communication success. Aborting.", cmdArgs[0],
Julien Desprez0c6c77c2016-05-31 16:35:57 +01001943 getSerialNumber()), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00001944 }
1945
1946 /**
1947 * {@inheritDoc}
1948 */
1949 @Override
1950 public boolean getUseFastbootErase() {
1951 return mOptions.getUseFastbootErase();
1952 }
1953
1954 /**
1955 * {@inheritDoc}
1956 */
1957 @Override
1958 public void setUseFastbootErase(boolean useFastbootErase) {
1959 mOptions.setUseFastbootErase(useFastbootErase);
1960 }
1961
1962 /**
1963 * {@inheritDoc}
1964 */
1965 @Override
1966 public CommandResult fastbootWipePartition(String partition)
1967 throws DeviceNotAvailableException {
1968 if (mOptions.getUseFastbootErase()) {
1969 return executeLongFastbootCommand("erase", partition);
1970 } else {
1971 return executeLongFastbootCommand("format", partition);
1972 }
1973 }
1974
1975 /**
1976 * Evaluate the given fastboot result to determine if recovery mode needs to be entered
1977 *
1978 * @param fastbootResult the {@link CommandResult} from a fastboot command
1979 * @return <code>true</code> if recovery mode should be entered, <code>false</code> otherwise.
1980 */
1981 private boolean isRecoveryNeeded(CommandResult fastbootResult) {
1982 if (fastbootResult.getStatus().equals(CommandStatus.TIMED_OUT)) {
1983 // fastboot commands always time out if devices is not present
1984 return true;
1985 } else {
1986 // check for specific error messages in result that indicate bad device communication
1987 // and recovery mode is needed
1988 if (fastbootResult.getStderr() == null ||
1989 fastbootResult.getStderr().contains("data transfer failure (Protocol error)") ||
1990 fastbootResult.getStderr().contains("status read failed (No such device)")) {
1991 CLog.w("Bad fastboot response from device %s. stderr: %s. Entering recovery",
1992 getSerialNumber(), fastbootResult.getStderr());
1993 return true;
1994 }
1995 }
1996 return false;
1997 }
1998
jdesprez3d7b9142018-03-02 12:11:23 -08001999 /** Get the max time allowed in ms for commands. */
2000 long getCommandTimeout() {
Julien Desprez6961b272016-02-01 09:58:23 +00002001 return mCmdTimeout;
2002 }
2003
2004 /**
2005 * Set the max time allowed in ms for commands.
2006 */
2007 void setLongCommandTimeout(long timeout) {
2008 mLongCmdTimeout = timeout;
2009 }
2010
2011 /**
2012 * Get the max time allowed in ms for commands.
2013 */
2014 long getLongCommandTimeout() {
2015 return mLongCmdTimeout;
2016 }
2017
jdesprez3d7b9142018-03-02 12:11:23 -08002018 /** Set the max time allowed in ms for commands. */
2019 void setCommandTimeout(long timeout) {
Julien Desprez6961b272016-02-01 09:58:23 +00002020 mCmdTimeout = timeout;
2021 }
2022
2023 /**
2024 * Builds the OS command for the given adb command and args
2025 */
2026 private String[] buildAdbCommand(String... commandArgs) {
2027 return ArrayUtil.buildArray(new String[] {"adb", "-s", getSerialNumber()},
2028 commandArgs);
2029 }
2030
jdesprez3d7b9142018-03-02 12:11:23 -08002031 /** Builds the OS command for the given adb shell command session and args */
2032 private String[] buildAdbShellCommand(String command) {
2033 // TODO: implement the shell v2 support in ddmlib itself.
Julien Desprez1dc6b392019-03-18 11:49:13 -07002034 String[] commandArgs =
2035 QuotationAwareTokenizer.tokenizeLine(
2036 command,
2037 /** No logging */
2038 false);
jdesprez3d7b9142018-03-02 12:11:23 -08002039 return ArrayUtil.buildArray(
2040 new String[] {"adb", "-s", getSerialNumber(), "shell"}, commandArgs);
2041 }
2042
Julien Desprez6961b272016-02-01 09:58:23 +00002043 /**
2044 * Builds the OS command for the given fastboot command and args
2045 */
2046 private String[] buildFastbootCommand(String... commandArgs) {
Julien Desprez0a7d67d2016-07-21 16:05:57 +01002047 return ArrayUtil.buildArray(new String[] {getFastbootPath(), "-s", getSerialNumber()},
Julien Desprez6961b272016-02-01 09:58:23 +00002048 commandArgs);
2049 }
2050
2051 /**
2052 * Performs an action on this device. Attempts to recover device and optionally retry command
2053 * if action fails.
2054 *
2055 * @param actionDescription a short description of action to be performed. Used for logging
2056 * purposes only.
2057 * @param action the action to be performed
2058 * @param retryAttempts the retry attempts to make for action if it fails but
2059 * recovery succeeds
Julien Desprezd0c379a2016-11-04 11:00:54 +00002060 * @return <code>true</code> if action was performed successfully
Julien Desprez6961b272016-02-01 09:58:23 +00002061 * @throws DeviceNotAvailableException if recovery attempt fails or max attempts done without
2062 * success
2063 */
Julien Desprezc8474552016-02-17 10:59:27 +00002064 protected boolean performDeviceAction(String actionDescription, final DeviceAction action,
Julien Desprez6961b272016-02-01 09:58:23 +00002065 int retryAttempts) throws DeviceNotAvailableException {
2066
2067 for (int i = 0; i < retryAttempts + 1; i++) {
2068 try {
2069 return action.run();
2070 } catch (TimeoutException e) {
2071 logDeviceActionException(actionDescription, e);
2072 } catch (IOException e) {
2073 logDeviceActionException(actionDescription, e);
2074 } catch (InstallException e) {
2075 logDeviceActionException(actionDescription, e);
2076 } catch (SyncException e) {
2077 logDeviceActionException(actionDescription, e);
2078 // a SyncException is not necessarily a device communication problem
2079 // do additional diagnosis
2080 if (!e.getErrorCode().equals(SyncError.BUFFER_OVERRUN) &&
2081 !e.getErrorCode().equals(SyncError.TRANSFER_PROTOCOL_ERROR)) {
2082 // this is a logic problem, doesn't need recovery or to be retried
2083 return false;
2084 }
2085 } catch (AdbCommandRejectedException e) {
2086 logDeviceActionException(actionDescription, e);
2087 } catch (ShellCommandUnresponsiveException e) {
2088 CLog.w("Device %s stopped responding when attempting %s", getSerialNumber(),
2089 actionDescription);
2090 }
2091 // TODO: currently treat all exceptions the same. In future consider different recovery
2092 // mechanisms for time out's vs IOExceptions
2093 recoverDevice();
2094 }
2095 if (retryAttempts > 0) {
2096 throw new DeviceUnresponsiveException(String.format("Attempted %s multiple times "
2097 + "on device %s without communication success. Aborting.", actionDescription,
Julien Desprez0c6c77c2016-05-31 16:35:57 +01002098 getSerialNumber()), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00002099 }
2100 return false;
2101 }
2102
2103 /**
2104 * Log an entry for given exception
2105 *
2106 * @param actionDescription the action's description
2107 * @param e the exception
2108 */
2109 private void logDeviceActionException(String actionDescription, Exception e) {
2110 CLog.w("%s (%s) when attempting %s on device %s", e.getClass().getSimpleName(),
2111 getExceptionMessage(e), actionDescription, getSerialNumber());
2112 }
2113
2114 /**
2115 * Make a best effort attempt to retrieve a meaningful short descriptive message for given
2116 * {@link Exception}
2117 *
2118 * @param e the {@link Exception}
2119 * @return a short message
2120 */
2121 private String getExceptionMessage(Exception e) {
2122 StringBuilder msgBuilder = new StringBuilder();
2123 if (e.getMessage() != null) {
2124 msgBuilder.append(e.getMessage());
2125 }
2126 if (e.getCause() != null) {
2127 msgBuilder.append(" cause: ");
2128 msgBuilder.append(e.getCause().getClass().getSimpleName());
2129 if (e.getCause().getMessage() != null) {
2130 msgBuilder.append(" (");
2131 msgBuilder.append(e.getCause().getMessage());
2132 msgBuilder.append(")");
2133 }
2134 }
2135 return msgBuilder.toString();
2136 }
2137
2138 /**
2139 * Attempts to recover device communication.
2140 *
2141 * @throws DeviceNotAvailableException if device is not longer available
2142 */
2143 @Override
2144 public void recoverDevice() throws DeviceNotAvailableException {
2145 if (mRecoveryMode.equals(RecoveryMode.NONE)) {
2146 CLog.i("Skipping recovery on %s", getSerialNumber());
2147 getRunUtil().sleep(NONE_RECOVERY_MODE_DELAY);
2148 return;
2149 }
2150 CLog.i("Attempting recovery on %s", getSerialNumber());
Julien Desprez7a7d97e2016-02-05 12:27:49 +00002151 try {
2152 mRecovery.recoverDevice(mStateMonitor, mRecoveryMode.equals(RecoveryMode.ONLINE));
2153 } catch (DeviceUnresponsiveException due) {
2154 RecoveryMode previousRecoveryMode = mRecoveryMode;
2155 mRecoveryMode = RecoveryMode.NONE;
Julien Desprez83f93f02019-04-22 08:59:35 -07002156 try {
2157 boolean enabled = enableAdbRoot();
2158 CLog.d("Device Unresponsive during recovery, is root still enabled: %s", enabled);
2159 } catch (DeviceUnresponsiveException e) {
2160 // Ignore exception thrown here to rethrow original exception.
2161 CLog.e("Exception occurred during recovery adb root:");
2162 CLog.e(e);
2163 }
Julien Desprez7a7d97e2016-02-05 12:27:49 +00002164 mRecoveryMode = previousRecoveryMode;
2165 throw due;
2166 }
Julien Desprez6961b272016-02-01 09:58:23 +00002167 if (mRecoveryMode.equals(RecoveryMode.AVAILABLE)) {
2168 // turn off recovery mode to prevent reentrant recovery
2169 // TODO: look for a better way to handle this, such as doing postBootUp steps in
2170 // recovery itself
2171 mRecoveryMode = RecoveryMode.NONE;
2172 // this might be a runtime reset - still need to run post boot setup steps
2173 if (isEncryptionSupported() && isDeviceEncrypted()) {
2174 unlockDevice();
2175 }
2176 postBootSetup();
2177 mRecoveryMode = RecoveryMode.AVAILABLE;
2178 } else if (mRecoveryMode.equals(RecoveryMode.ONLINE)) {
2179 // turn off recovery mode to prevent reentrant recovery
2180 // TODO: look for a better way to handle this, such as doing postBootUp steps in
2181 // recovery itself
2182 mRecoveryMode = RecoveryMode.NONE;
2183 enableAdbRoot();
2184 mRecoveryMode = RecoveryMode.ONLINE;
2185 }
2186 CLog.i("Recovery successful for %s", getSerialNumber());
2187 }
2188
2189 /**
2190 * Attempts to recover device fastboot communication.
2191 *
2192 * @throws DeviceNotAvailableException if device is not longer available
2193 */
2194 private void recoverDeviceFromBootloader() throws DeviceNotAvailableException {
2195 CLog.i("Attempting recovery on %s in bootloader", getSerialNumber());
2196 mRecovery.recoverDeviceBootloader(mStateMonitor);
2197 CLog.i("Bootloader recovery successful for %s", getSerialNumber());
2198 }
2199
2200 private void recoverDeviceInRecovery() throws DeviceNotAvailableException {
2201 CLog.i("Attempting recovery on %s in recovery", getSerialNumber());
2202 mRecovery.recoverDeviceRecovery(mStateMonitor);
2203 CLog.i("Recovery mode recovery successful for %s", getSerialNumber());
2204 }
2205
2206 /**
2207 * {@inheritDoc}
2208 */
2209 @Override
2210 public void startLogcat() {
2211 if (mLogcatReceiver != null) {
2212 CLog.d("Already capturing logcat for %s, ignoring", getSerialNumber());
2213 return;
2214 }
2215 mLogcatReceiver = createLogcatReceiver();
2216 mLogcatReceiver.start();
2217 }
2218
2219 /**
2220 * {@inheritDoc}
2221 */
2222 @Override
2223 public void clearLogcat() {
2224 if (mLogcatReceiver != null) {
2225 mLogcatReceiver.clear();
2226 }
2227 }
2228
jdesprezd0e00da2017-09-07 18:54:49 -07002229 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00002230 @Override
jdesprezd0e00da2017-09-07 18:54:49 -07002231 @SuppressWarnings("MustBeClosedChecker")
Julien Desprez6961b272016-02-01 09:58:23 +00002232 public InputStreamSource getLogcat() {
2233 if (mLogcatReceiver == null) {
2234 CLog.w("Not capturing logcat for %s in background, returning a logcat dump",
2235 getSerialNumber());
2236 return getLogcatDump();
2237 } else {
2238 return mLogcatReceiver.getLogcatData();
2239 }
2240 }
2241
jdesprezd0e00da2017-09-07 18:54:49 -07002242 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00002243 @Override
jdesprezcdd03352017-10-06 18:04:49 -07002244 @SuppressWarnings("MustBeClosedChecker")
Julien Desprez6961b272016-02-01 09:58:23 +00002245 public InputStreamSource getLogcat(int maxBytes) {
2246 if (mLogcatReceiver == null) {
2247 CLog.w("Not capturing logcat for %s in background, returning a logcat dump "
2248 + "ignoring size", getSerialNumber());
2249 return getLogcatDump();
2250 } else {
2251 return mLogcatReceiver.getLogcatData(maxBytes);
2252 }
2253 }
2254
2255 /**
2256 * {@inheritDoc}
2257 */
2258 @Override
Julien Desprezdcb19d52016-06-20 12:23:12 +01002259 public InputStreamSource getLogcatSince(long date) {
2260 try {
2261 if (getApiLevel() <= 22) {
2262 CLog.i("Api level too low to use logcat -t 'time' reverting to dump");
2263 return getLogcatDump();
2264 }
2265 } catch (DeviceNotAvailableException e) {
2266 // For convenience of interface, we catch the DNAE here.
2267 CLog.e(e);
2268 return getLogcatDump();
2269 }
2270
jdesprez0b9d69e2017-12-11 03:40:39 -08002271 // Convert date to format needed by the command:
jdesprez6a2f1d22018-03-20 11:31:54 -07002272 // 'MM-DD HH:mm:ss.mmm' or 'YYYY-MM-DD HH:mm:ss.mmm'
2273 SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss.mmm");
jdesprez0b9d69e2017-12-11 03:40:39 -08002274 String dateFormatted = format.format(new Date(date));
2275
Julien Desprezdcb19d52016-06-20 12:23:12 +01002276 byte[] output = new byte[0];
2277 try {
2278 // use IDevice directly because we don't want callers to handle
2279 // DeviceNotAvailableException for this method
2280 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
jdesprez0b9d69e2017-12-11 03:40:39 -08002281 String command = String.format("%s -t '%s'", LogcatReceiver.LOGCAT_CMD, dateFormatted);
Julien Desprezdcb19d52016-06-20 12:23:12 +01002282 getIDevice().executeShellCommand(command, receiver);
2283 output = receiver.getOutput();
2284 } catch (IOException|AdbCommandRejectedException|
2285 ShellCommandUnresponsiveException|TimeoutException e) {
2286 CLog.w("Failed to get logcat dump from %s: %s", getSerialNumber(), e.getMessage());
2287 CLog.e(e);
2288 }
2289 return new ByteArrayInputStreamSource(output);
2290 }
2291
2292 /**
2293 * {@inheritDoc}
2294 */
2295 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00002296 public InputStreamSource getLogcatDump() {
2297 byte[] output = new byte[0];
2298 try {
2299 // use IDevice directly because we don't want callers to handle
2300 // DeviceNotAvailableException for this method
2301 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
2302 // add -d parameter to make this a non blocking call
Julien Desprez73c55bf2016-09-01 09:27:37 +01002303 getIDevice().executeShellCommand(LogcatReceiver.LOGCAT_CMD + " -d", receiver,
2304 LOGCAT_DUMP_TIMEOUT, TimeUnit.MILLISECONDS);
Julien Desprez6961b272016-02-01 09:58:23 +00002305 output = receiver.getOutput();
2306 } catch (IOException e) {
2307 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2308 } catch (TimeoutException e) {
2309 CLog.w("Failed to get logcat dump from %s: timeout", getSerialNumber());
2310 } catch (AdbCommandRejectedException e) {
2311 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2312 } catch (ShellCommandUnresponsiveException e) {
2313 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2314 }
2315 return new ByteArrayInputStreamSource(output);
2316 }
2317
2318 /**
2319 * {@inheritDoc}
2320 */
2321 @Override
2322 public void stopLogcat() {
2323 if (mLogcatReceiver != null) {
2324 mLogcatReceiver.stop();
2325 mLogcatReceiver = null;
2326 } else {
2327 CLog.w("Attempting to stop logcat when not capturing for %s", getSerialNumber());
2328 }
2329 }
2330
Jeffrey Lu279122f2018-01-29 17:25:08 -08002331 /** Factory method to create a {@link LogcatReceiver}. */
2332 @VisibleForTesting
Julien Desprez6961b272016-02-01 09:58:23 +00002333 LogcatReceiver createLogcatReceiver() {
2334 String logcatOptions = mOptions.getLogcatOptions();
2335 if (logcatOptions == null) {
2336 return new LogcatReceiver(this, mOptions.getMaxLogcatDataSize(), mLogStartDelay);
2337 } else {
2338 return new LogcatReceiver(this,
2339 String.format("%s %s", LogcatReceiver.LOGCAT_CMD, logcatOptions),
2340 mOptions.getMaxLogcatDataSize(), mLogStartDelay);
2341 }
2342 }
2343
2344 /**
2345 * {@inheritDoc}
2346 */
2347 @Override
2348 public InputStreamSource getBugreport() {
jdesprezc3537f72017-09-05 17:27:14 -07002349 if (getApiLevelSafe() < 24) {
2350 InputStreamSource bugreport = getBugreportInternal();
2351 if (bugreport == null) {
2352 // Safe call so we don't return null but an empty resource.
2353 return new ByteArrayInputStreamSource("".getBytes());
2354 }
2355 return bugreport;
Julien Desprez16184162016-06-10 08:56:17 +01002356 }
jdesprezc3537f72017-09-05 17:27:14 -07002357 CLog.d("Api level above 24, using bugreportz instead.");
2358 File mainEntry = null;
2359 File bugreportzFile = null;
2360 try {
2361 bugreportzFile = getBugreportzInternal();
2362 if (bugreportzFile == null) {
2363 bugreportzFile = bugreportzFallback();
Julien Desprez15de6812016-08-08 15:08:06 +01002364 }
jdesprezc3537f72017-09-05 17:27:14 -07002365 if (bugreportzFile == null) {
2366 // return empty buffer
2367 return new ByteArrayInputStreamSource("".getBytes());
Julien Desprez15de6812016-08-08 15:08:06 +01002368 }
jdesprezc3537f72017-09-05 17:27:14 -07002369 try (ZipFile zip = new ZipFile(bugreportzFile)) {
2370 // We get the main_entry.txt that contains the bugreport name.
2371 mainEntry = ZipUtil2.extractFileFromZip(zip, "main_entry.txt");
2372 String bugreportName = FileUtil.readStringFromFile(mainEntry).trim();
2373 CLog.d("bugreport name: '%s'", bugreportName);
2374 File bugreport = ZipUtil2.extractFileFromZip(zip, bugreportName);
2375 return new FileInputStreamSource(bugreport, true);
2376 }
2377 } catch (IOException e) {
2378 CLog.e("Error while unzipping bugreportz");
2379 CLog.e(e);
2380 return new ByteArrayInputStreamSource("corrupted bugreport.".getBytes());
2381 } finally {
2382 FileUtil.deleteFile(bugreportzFile);
2383 FileUtil.deleteFile(mainEntry);
Julien Desprez15de6812016-08-08 15:08:06 +01002384 }
Julien Desprez16184162016-06-10 08:56:17 +01002385 }
2386
2387 /**
jdesprezc3537f72017-09-05 17:27:14 -07002388 * If first bugreportz collection was interrupted for any reasons, the temporary file where the
2389 * dumpstate is redirected could exists if it started. We attempt to get it to have some partial
2390 * data.
Julien Desprez56d044d2016-08-17 12:28:10 +01002391 */
jdesprezc3537f72017-09-05 17:27:14 -07002392 private File bugreportzFallback() {
Julien Desprez56d044d2016-08-17 12:28:10 +01002393 try {
2394 IFileEntry entries = getFileEntry(BUGREPORTZ_TMP_PATH);
2395 if (entries != null) {
2396 for (IFileEntry f : entries.getChildren(false)) {
2397 String name = f.getName();
Julien Desprezf20644b2016-08-31 09:31:27 +01002398 CLog.d("bugreport entry: %s", name);
jdesprezc3537f72017-09-05 17:27:14 -07002399 // Only get left-over zipped data to avoid confusing data types.
2400 if (name.endsWith(".zip")) {
Julien Desprez96c00252018-06-06 03:25:40 -07002401 File pulledZip = pullFile(BUGREPORTZ_TMP_PATH + name);
2402 try {
2403 // Validate the zip before returning it.
2404 if (ZipUtil.isZipFileValid(pulledZip, false)) {
2405 return pulledZip;
2406 }
2407 } catch (IOException e) {
2408 CLog.e(e);
2409 }
2410 CLog.w("Failed to get a valid bugreportz.");
2411 // if zip validation failed, delete it and return null.
2412 FileUtil.deleteFile(pulledZip);
2413 return null;
2414
Julien Desprez56d044d2016-08-17 12:28:10 +01002415 }
2416 }
Julien Desprezf20644b2016-08-31 09:31:27 +01002417 CLog.w("Could not find a tmp bugreport file in the directory.");
2418 } else {
2419 CLog.w("Could not find the file entry: '%s' on the device.", BUGREPORTZ_TMP_PATH);
Julien Desprez56d044d2016-08-17 12:28:10 +01002420 }
2421 } catch (DeviceNotAvailableException e) {
2422 CLog.e(e);
2423 }
jdesprezc3537f72017-09-05 17:27:14 -07002424 return null;
Julien Desprez56d044d2016-08-17 12:28:10 +01002425 }
2426
2427 /**
Julien Desprez16184162016-06-10 08:56:17 +01002428 * {@inheritDoc}
2429 */
2430 @Override
Julien Desprez0c836c92016-08-10 14:40:41 +01002431 public boolean logBugreport(String dataName, ITestLogger listener) {
jdesprezc3537f72017-09-05 17:27:14 -07002432 InputStreamSource bugreport = null;
2433 LogDataType type = null;
Julien Desprez0c836c92016-08-10 14:40:41 +01002434 try {
jdesprezc3537f72017-09-05 17:27:14 -07002435 bugreport = getBugreportz();
2436 type = LogDataType.BUGREPORTZ;
2437
Julien Desprez0c836c92016-08-10 14:40:41 +01002438 if (bugreport == null) {
jdesprezc3537f72017-09-05 17:27:14 -07002439 CLog.d("Bugreportz failed, attempting bugreport collection instead.");
2440 bugreport = getBugreportInternal();
Julien Desprez0c836c92016-08-10 14:40:41 +01002441 type = LogDataType.BUGREPORT;
2442 }
jdesprezc3537f72017-09-05 17:27:14 -07002443 // log what we managed to capture.
Julien Desprez0c836c92016-08-10 14:40:41 +01002444 if (bugreport != null) {
2445 listener.testLog(dataName, type, bugreport);
2446 return true;
2447 }
2448 } finally {
2449 StreamUtil.cancel(bugreport);
2450 }
jdesprezc3537f72017-09-05 17:27:14 -07002451 CLog.d(
2452 "logBugreport() was not successful in collecting and logging the bugreport "
2453 + "for device %s",
2454 getSerialNumber());
Julien Desprez0c836c92016-08-10 14:40:41 +01002455 return false;
2456 }
2457
2458 /**
2459 * {@inheritDoc}
2460 */
2461 @Override
2462 public Bugreport takeBugreport() {
jdesprezc3537f72017-09-05 17:27:14 -07002463 File bugreportFile = null;
2464 int apiLevel = getApiLevelSafe();
2465 if (apiLevel == UNKNOWN_API_LEVEL) {
Julien Desprez0c836c92016-08-10 14:40:41 +01002466 return null;
2467 }
jdesprezc3537f72017-09-05 17:27:14 -07002468 if (apiLevel >= 24) {
2469 CLog.d("Api level above 24, using bugreportz.");
Julien Desprez0c836c92016-08-10 14:40:41 +01002470 bugreportFile = getBugreportzInternal();
2471 if (bugreportFile != null) {
2472 return new Bugreport(bugreportFile, true);
Julien Desprez0c836c92016-08-10 14:40:41 +01002473 }
jdesprezc3537f72017-09-05 17:27:14 -07002474 return null;
Julien Desprez0c836c92016-08-10 14:40:41 +01002475 }
jdesprezc3537f72017-09-05 17:27:14 -07002476 // fall back to regular bugreport
2477 InputStreamSource bugreport = getBugreportInternal();
2478 if (bugreport == null) {
2479 CLog.e("Error when collecting the bugreport.");
2480 return null;
2481 }
2482 try {
2483 bugreportFile = FileUtil.createTempFile("bugreport", ".txt");
2484 FileUtil.writeToFile(bugreport.createInputStream(), bugreportFile);
2485 return new Bugreport(bugreportFile, false);
2486 } catch (IOException e) {
2487 CLog.e("Error when writing the bugreport file");
2488 CLog.e(e);
2489 }
2490 return null;
Julien Desprez0c836c92016-08-10 14:40:41 +01002491 }
2492
2493 /**
2494 * {@inheritDoc}
2495 */
2496 @Override
Julien Desprez16184162016-06-10 08:56:17 +01002497 public InputStreamSource getBugreportz() {
jdesprezc3537f72017-09-05 17:27:14 -07002498 if (getApiLevelSafe() < 24) {
2499 return null;
2500 }
2501 File bugreportZip = getBugreportzInternal();
2502 if (bugreportZip == null) {
2503 bugreportZip = bugreportzFallback();
2504 }
2505 if (bugreportZip != null) {
2506 return new FileInputStreamSource(bugreportZip, true);
Julien Desprez15de6812016-08-08 15:08:06 +01002507 }
2508 return null;
2509 }
2510
Jeffrey Lu279122f2018-01-29 17:25:08 -08002511 /** Internal Helper method to get the bugreportz zip file as a {@link File}. */
2512 @VisibleForTesting
Julien Desprez0c836c92016-08-10 14:40:41 +01002513 protected File getBugreportzInternal() {
Julien Desprez15de6812016-08-08 15:08:06 +01002514 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
Julien Desprez16184162016-06-10 08:56:17 +01002515 // Does not rely on {@link ITestDevice#executeAdbCommand(String...)} because it does not
2516 // provide a timeout.
Julien Desprez16184162016-06-10 08:56:17 +01002517 try {
2518 executeShellCommand(BUGREPORTZ_CMD, receiver,
Julien Desprezf54735c2016-08-16 09:17:07 +01002519 BUGREPORTZ_TIMEOUT, TimeUnit.MILLISECONDS, 0 /* don't retry */);
Julien Desprez16184162016-06-10 08:56:17 +01002520 String output = receiver.getOutput().trim();
2521 Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
2522 if (!match.find()) {
2523 CLog.e("Something went went wrong during bugreportz collection: '%s'", output);
2524 return null;
2525 } else {
2526 String remoteFilePath = match.group(2);
Julien Desprezd9977892020-04-14 09:01:15 -07002527 if (Strings.isNullOrEmpty(remoteFilePath)) {
2528 CLog.e("Invalid bugreportz path found from output: %s", output);
2529 return null;
2530 }
Julien Desprez16184162016-06-10 08:56:17 +01002531 File zipFile = null;
2532 try {
2533 if (!doesFileExist(remoteFilePath)) {
Julien Desprezd9977892020-04-14 09:01:15 -07002534 CLog.e("Did not find bugreportz at: '%s'", remoteFilePath);
Julien Desprez16184162016-06-10 08:56:17 +01002535 return null;
2536 }
2537 // Create a placeholder to replace the file
2538 zipFile = FileUtil.createTempFile("bugreportz", ".zip");
2539 pullFile(remoteFilePath, zipFile);
2540 String bugreportDir =
2541 remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/'));
2542 if (!bugreportDir.isEmpty()) {
2543 // clean bugreport files directory on device
Julien Desprez901d4962019-03-27 10:10:23 -07002544 deleteFile(String.format("%s/*", bugreportDir));
Julien Desprez16184162016-06-10 08:56:17 +01002545 }
2546
Julien Desprez15de6812016-08-08 15:08:06 +01002547 return zipFile;
Julien Desprez16184162016-06-10 08:56:17 +01002548 } catch (IOException e) {
2549 CLog.e("Failed to create the temporary file.");
2550 return null;
2551 }
2552 }
2553 } catch (DeviceNotAvailableException e) {
2554 CLog.e("Device %s became unresponsive while retrieving bugreportz", getSerialNumber());
2555 CLog.e(e);
2556 }
2557 return null;
Julien Desprez6961b272016-02-01 09:58:23 +00002558 }
2559
jdesprezc3537f72017-09-05 17:27:14 -07002560 protected InputStreamSource getBugreportInternal() {
2561 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
2562 try {
2563 executeShellCommand(
2564 BUGREPORT_CMD,
2565 receiver,
2566 BUGREPORT_TIMEOUT,
2567 TimeUnit.MILLISECONDS,
2568 0 /* don't retry */);
2569 } catch (DeviceNotAvailableException e) {
2570 // Log, but don't throw, so the caller can get the bugreport contents even
2571 // if the device goes away
2572 CLog.e("Device %s became unresponsive while retrieving bugreport", getSerialNumber());
2573 return null;
2574 }
2575 return new ByteArrayInputStreamSource(receiver.getOutput());
2576 }
2577
Julien Desprez6961b272016-02-01 09:58:23 +00002578 /**
2579 * {@inheritDoc}
2580 */
2581 @Override
2582 public InputStreamSource getScreenshot() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002583 throw new UnsupportedOperationException("No support for Screenshot");
Julien Desprez6961b272016-02-01 09:58:23 +00002584 }
2585
2586 /**
2587 * {@inheritDoc}
2588 */
2589 @Override
2590 public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002591 throw new UnsupportedOperationException("No support for Screenshot");
Julien Desprez6961b272016-02-01 09:58:23 +00002592 }
2593
Insaf Latypov73f7aeb2017-03-03 11:05:08 +00002594 /** {@inheritDoc} */
2595 @Override
2596 public InputStreamSource getScreenshot(String format, boolean rescale)
2597 throws DeviceNotAvailableException {
2598 throw new UnsupportedOperationException("No support for Screenshot");
2599 }
2600
2601 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00002602 @Override
Julien Desprez1aacf862019-11-04 09:56:25 -08002603 public InputStreamSource getScreenshot(long displayId) throws DeviceNotAvailableException {
Julien Despreze722b232019-03-28 18:47:14 -07002604 throw new UnsupportedOperationException("No support for Screenshot");
2605 }
2606
2607 /** {@inheritDoc} */
2608 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00002609 public void clearLastConnectedWifiNetwork() {
2610 mLastConnectedWifiSsid = null;
2611 mLastConnectedWifiPsk = null;
2612 }
2613
2614 /**
2615 * {@inheritDoc}
2616 */
2617 @Override
2618 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk)
2619 throws DeviceNotAvailableException {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002620 return connectToWifiNetwork(wifiSsid, wifiPsk, false);
2621 }
2622
2623 /**
2624 * {@inheritDoc}
2625 */
2626 @Override
2627 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)
2628 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00002629 // Clears the last connected wifi network.
2630 mLastConnectedWifiSsid = null;
2631 mLastConnectedWifiPsk = null;
2632
2633 // Connects to wifi network. It retries up to {@link TestDeviceOptions@getWifiAttempts()}
Julien Desprez8539c082016-03-04 13:39:19 +00002634 // times
Julien Desprez6961b272016-02-01 09:58:23 +00002635 Random rnd = new Random();
2636 int backoffSlotCount = 2;
Jeffrey Lu279122f2018-01-29 17:25:08 -08002637 int slotTime = mOptions.getWifiRetryWaitTime();
2638 int waitTime = 0;
Julien Desprez6961b272016-02-01 09:58:23 +00002639 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002640 long startTime = mClock.millis();
2641 for (int i = 1; i <= mOptions.getWifiAttempts(); i++) {
2642 CLog.i("Connecting to wifi network %s on %s", wifiSsid, getSerialNumber());
2643 boolean success =
2644 wifi.connectToNetwork(wifiSsid, wifiPsk, mOptions.getConnCheckUrl(), scanSsid);
2645 final Map<String, String> wifiInfo = wifi.getWifiInfo();
2646 if (success) {
2647 CLog.i(
2648 "Successfully connected to wifi network %s(%s) on %s",
2649 wifiSsid, wifiInfo.get("bssid"), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00002650
Julien Desprezb1301842018-04-16 10:12:52 -07002651 mLastConnectedWifiSsid = wifiSsid;
2652 mLastConnectedWifiPsk = wifiPsk;
Julien Desprez6961b272016-02-01 09:58:23 +00002653
Julien Desprezb1301842018-04-16 10:12:52 -07002654 return true;
2655 } else {
2656 CLog.w(
2657 "Failed to connect to wifi network %s(%s) on %s on attempt %d of %d",
2658 wifiSsid,
2659 wifiInfo.get("bssid"),
2660 getSerialNumber(),
2661 i,
2662 mOptions.getWifiAttempts());
Julien Desprez6961b272016-02-01 09:58:23 +00002663 }
Julien Desprezb1301842018-04-16 10:12:52 -07002664 if (mClock.millis() - startTime >= mOptions.getMaxWifiConnectTime()) {
2665 CLog.e(
2666 "Failed to connect to wifi after %d ms. Aborting.",
2667 mOptions.getMaxWifiConnectTime());
2668 break;
2669 }
2670 if (i < mOptions.getWifiAttempts()) {
2671 if (mOptions.isWifiExpoRetryEnabled()) {
2672 // use binary exponential back-offs when retrying.
2673 waitTime = rnd.nextInt(backoffSlotCount) * slotTime;
2674 backoffSlotCount *= 2;
2675 }
2676 CLog.e("Waiting for %d ms before reconnecting to %s...", waitTime, wifiSsid);
2677 getRunUtil().sleep(waitTime);
2678 }
Julien Desprez6961b272016-02-01 09:58:23 +00002679 }
2680 return false;
2681 }
2682
2683 /**
2684 * {@inheritDoc}
2685 */
2686 @Override
2687 public boolean checkConnectivity() throws DeviceNotAvailableException {
2688 IWifiHelper wifi = createWifiHelper();
2689 return wifi.checkConnectivity(mOptions.getConnCheckUrl());
2690 }
2691
2692 /**
2693 * {@inheritDoc}
2694 */
2695 @Override
2696 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)
2697 throws DeviceNotAvailableException {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002698 return connectToWifiNetworkIfNeeded(wifiSsid, wifiPsk, false);
2699 }
2700
2701 /**
2702 * {@inheritDoc}
2703 */
2704 @Override
2705 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)
2706 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00002707 if (!checkConnectivity()) {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002708 return connectToWifiNetwork(wifiSsid, wifiPsk, scanSsid);
Julien Desprez6961b272016-02-01 09:58:23 +00002709 }
2710 return true;
2711 }
2712
2713 /**
2714 * {@inheritDoc}
2715 */
2716 @Override
2717 public boolean isWifiEnabled() throws DeviceNotAvailableException {
Julien Desprezac5f37f2017-02-13 10:41:22 +00002718 final IWifiHelper wifi = createWifiHelper();
Julien Desprez6961b272016-02-01 09:58:23 +00002719 try {
Julien Desprez6961b272016-02-01 09:58:23 +00002720 return wifi.isWifiEnabled();
2721 } catch (RuntimeException e) {
2722 CLog.w("Failed to create WifiHelper: %s", e.getMessage());
2723 return false;
2724 }
2725 }
2726
2727 /**
2728 * Checks that the device is currently successfully connected to given wifi SSID.
2729 *
2730 * @param wifiSSID the wifi ssid
2731 * @return <code>true</code> if device is currently connected to wifiSSID and has network
2732 * connectivity. <code>false</code> otherwise
2733 * @throws DeviceNotAvailableException if connection with device was lost
2734 */
2735 boolean checkWifiConnection(String wifiSSID) throws DeviceNotAvailableException {
2736 CLog.i("Checking connection with wifi network %s on %s", wifiSSID, getSerialNumber());
2737 final IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002738 // getSSID returns SSID as "SSID"
2739 final String quotedSSID = String.format("\"%s\"", wifiSSID);
Julien Desprez6961b272016-02-01 09:58:23 +00002740
Julien Desprezb1301842018-04-16 10:12:52 -07002741 boolean test = wifi.isWifiEnabled();
2742 CLog.v("%s: wifi enabled? %b", getSerialNumber(), test);
Julien Desprez6961b272016-02-01 09:58:23 +00002743
Julien Desprezb1301842018-04-16 10:12:52 -07002744 if (test) {
2745 final String actualSSID = wifi.getSSID();
2746 test = quotedSSID.equals(actualSSID);
2747 CLog.v("%s: SSID match (%s, %s, %b)", getSerialNumber(), quotedSSID, actualSSID, test);
Julien Desprez6961b272016-02-01 09:58:23 +00002748 }
Julien Desprezb1301842018-04-16 10:12:52 -07002749 if (test) {
2750 test = wifi.hasValidIp();
2751 CLog.v("%s: validIP? %b", getSerialNumber(), test);
2752 }
2753 if (test) {
2754 test = checkConnectivity();
2755 CLog.v("%s: checkConnectivity returned %b", getSerialNumber(), test);
2756 }
2757 return test;
Julien Desprez6961b272016-02-01 09:58:23 +00002758 }
2759
2760 /**
2761 * {@inheritDoc}
2762 */
2763 @Override
2764 public boolean disconnectFromWifi() throws DeviceNotAvailableException {
2765 CLog.i("Disconnecting from wifi on %s", getSerialNumber());
2766 // Clears the last connected wifi network.
2767 mLastConnectedWifiSsid = null;
2768 mLastConnectedWifiPsk = null;
2769
2770 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002771 return wifi.disconnectFromNetwork();
Julien Desprez6961b272016-02-01 09:58:23 +00002772 }
2773
2774 /**
2775 * {@inheritDoc}
2776 */
2777 @Override
2778 public String getIpAddress() throws DeviceNotAvailableException {
2779 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002780 return wifi.getIpAddress();
Julien Desprez6961b272016-02-01 09:58:23 +00002781 }
2782
2783 /**
2784 * {@inheritDoc}
2785 */
2786 @Override
2787 public boolean enableNetworkMonitor() throws DeviceNotAvailableException {
2788 mNetworkMonitorEnabled = false;
2789
2790 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002791 wifi.stopMonitor();
2792 if (wifi.startMonitor(NETWORK_MONITOR_INTERVAL, mOptions.getConnCheckUrl())) {
2793 mNetworkMonitorEnabled = true;
2794 return true;
Julien Desprez6961b272016-02-01 09:58:23 +00002795 }
2796 return false;
2797 }
2798
2799 /**
2800 * {@inheritDoc}
2801 */
2802 @Override
2803 public boolean disableNetworkMonitor() throws DeviceNotAvailableException {
2804 mNetworkMonitorEnabled = false;
2805
2806 IWifiHelper wifi = createWifiHelper();
2807 List<Long> samples = wifi.stopMonitor();
2808 if (!samples.isEmpty()) {
2809 int failures = 0;
2810 long totalLatency = 0;
2811 for (Long sample : samples) {
2812 if (sample < 0) {
2813 failures += 1;
2814 } else {
2815 totalLatency += sample;
2816 }
2817 }
2818 double failureRate = failures * 100.0 / samples.size();
2819 double avgLatency = 0.0;
2820 if (failures < samples.size()) {
2821 avgLatency = totalLatency / (samples.size() - failures);
2822 }
2823 CLog.d("[metric] url=%s, window=%ss, failure_rate=%.2f%%, latency_avg=%.2f",
2824 mOptions.getConnCheckUrl(), samples.size() * NETWORK_MONITOR_INTERVAL / 1000,
2825 failureRate, avgLatency);
2826 }
2827 return true;
2828 }
2829
2830 /**
2831 * Create a {@link WifiHelper} to use
Jeffrey Lu279122f2018-01-29 17:25:08 -08002832 *
2833 * <p>
2834 *
Julien Desprez021af1d2016-11-29 14:54:07 +00002835 * @throws DeviceNotAvailableException
Julien Desprez6961b272016-02-01 09:58:23 +00002836 */
Jeffrey Lu279122f2018-01-29 17:25:08 -08002837 @VisibleForTesting
Julien Desprez6961b272016-02-01 09:58:23 +00002838 IWifiHelper createWifiHelper() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002839 // current wifi helper won't work on AndroidNativeDevice
2840 // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when
2841 // we learn what is available.
2842 throw new UnsupportedOperationException("Wifi helper is not supported.");
Julien Desprez6961b272016-02-01 09:58:23 +00002843 }
2844
2845 /**
2846 * {@inheritDoc}
2847 */
2848 @Override
2849 public boolean clearErrorDialogs() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002850 throw new UnsupportedOperationException("No support for Screen's features");
Julien Desprez6961b272016-02-01 09:58:23 +00002851 }
2852
Julien Desprez14e96692017-01-12 12:31:29 +00002853 /** {@inheritDoc} */
2854 @Override
2855 public KeyguardControllerState getKeyguardState() throws DeviceNotAvailableException {
2856 throw new UnsupportedOperationException("No support for keyguard querying.");
2857 }
2858
Julien Desprez6961b272016-02-01 09:58:23 +00002859 IDeviceStateMonitor getDeviceStateMonitor() {
2860 return mStateMonitor;
2861 }
2862
2863 /**
2864 * {@inheritDoc}
2865 */
2866 @Override
2867 public void postBootSetup() throws DeviceNotAvailableException {
2868 enableAdbRoot();
Julien Desprezc8474552016-02-17 10:59:27 +00002869 prePostBootSetup();
Julien Desprez6961b272016-02-01 09:58:23 +00002870 for (String command : mOptions.getPostBootCommands()) {
2871 executeShellCommand(command);
2872 }
2873 }
2874
2875 /**
Julien Desprezc8474552016-02-17 10:59:27 +00002876 * Allows each device type (AndroidNativeDevice, TestDevice) to override this method for
2877 * specific post boot setup.
2878 * @throws DeviceNotAvailableException
2879 */
2880 protected void prePostBootSetup() throws DeviceNotAvailableException {
2881 // Empty on purpose.
2882 }
2883
2884 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002885 * Ensure wifi connection is re-established after boot. This is intended to be called after TF
2886 * initiated reboots(ones triggered by {@link #reboot()}) only.
2887 *
2888 * @throws DeviceNotAvailableException
2889 */
2890 void postBootWifiSetup() throws DeviceNotAvailableException {
2891 if (mLastConnectedWifiSsid != null) {
2892 reconnectToWifiNetwork();
2893 }
2894 if (mNetworkMonitorEnabled) {
2895 if (!enableNetworkMonitor()) {
2896 CLog.w("Failed to enable network monitor on %s after reboot", getSerialNumber());
2897 }
2898 }
2899 }
2900
2901 void reconnectToWifiNetwork() throws DeviceNotAvailableException {
2902 // First, wait for wifi to re-connect automatically.
2903 long startTime = System.currentTimeMillis();
2904 boolean isConnected = checkConnectivity();
2905 while (!isConnected && (System.currentTimeMillis() - startTime) < WIFI_RECONNECT_TIMEOUT) {
2906 getRunUtil().sleep(WIFI_RECONNECT_CHECK_INTERVAL);
2907 isConnected = checkConnectivity();
2908 }
2909
2910 if (isConnected) {
2911 return;
2912 }
2913
2914 // If wifi is still not connected, try to re-connect on our own.
2915 final String wifiSsid = mLastConnectedWifiSsid;
2916 if (!connectToWifiNetworkIfNeeded(mLastConnectedWifiSsid, mLastConnectedWifiPsk)) {
2917 throw new NetworkNotAvailableException(
2918 String.format("Failed to connect to wifi network %s on %s after reboot",
2919 wifiSsid, getSerialNumber()));
2920 }
2921 }
2922
2923 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002924 * {@inheritDoc}
2925 */
2926 @Override
2927 public void rebootIntoBootloader()
2928 throws DeviceNotAvailableException, UnsupportedOperationException {
Dan Shifdfbc532020-01-14 10:59:52 -08002929 rebootIntoFastbootInternal(true);
2930 }
2931
2932 /** {@inheritDoc} */
2933 @Override
2934 public void rebootIntoFastbootd()
2935 throws DeviceNotAvailableException, UnsupportedOperationException {
2936 rebootIntoFastbootInternal(false);
2937 }
Julien Despreza16120f2020-04-27 08:54:10 -07002938
Dan Shifdfbc532020-01-14 10:59:52 -08002939 /**
2940 * Reboots the device into bootloader or fastbootd mode.
2941 *
Julien Despreza16120f2020-04-27 08:54:10 -07002942 * @param isBootloader true to boot the device into bootloader mode, false to boot the device
Dan Shifdfbc532020-01-14 10:59:52 -08002943 * into fastbootd mode.
2944 * @throws DeviceNotAvailableException if connection with device is lost and cannot be
2945 * recovered.
2946 */
2947 private void rebootIntoFastbootInternal(boolean isBootloader)
2948 throws DeviceNotAvailableException {
2949 final RebootMode mode =
Julien Despreza16120f2020-04-27 08:54:10 -07002950 isBootloader ? RebootMode.REBOOT_INTO_BOOTLOADER : RebootMode.REBOOT_INTO_FASTBOOTD;
Julien Desprez6961b272016-02-01 09:58:23 +00002951 if (!mFastbootEnabled) {
2952 throw new UnsupportedOperationException(
Dan Shifdfbc532020-01-14 10:59:52 -08002953 String.format("Fastboot is not available and cannot reboot into %s", mode));
Julien Desprez6961b272016-02-01 09:58:23 +00002954 }
Julien Desprez13399b92019-04-05 12:31:18 -07002955 // If we go to bootloader, it's probably for flashing so ensure we re-check the provider
2956 mShouldSkipContentProviderSetup = false;
Dan Shi8dc28a22019-12-20 10:12:54 -08002957 CLog.i(
Dan Shifdfbc532020-01-14 10:59:52 -08002958 "Rebooting device %s in state %s into %s",
2959 getSerialNumber(), getDeviceState(), mode);
Julien Despreza16120f2020-04-27 08:54:10 -07002960 if (isStateBootloaderOrFastbootd()) {
Julien Desprez6961b272016-02-01 09:58:23 +00002961 CLog.i("device %s already in fastboot. Rebooting anyway", getSerialNumber());
Dan Shifdfbc532020-01-14 10:59:52 -08002962 executeFastbootCommand(String.format("reboot-%s", mode));
Julien Desprez6961b272016-02-01 09:58:23 +00002963 } else {
Dan Shifdfbc532020-01-14 10:59:52 -08002964 CLog.i("Booting device %s into %s", getSerialNumber(), mode);
2965 doAdbReboot(mode, null);
Julien Desprez6961b272016-02-01 09:58:23 +00002966 }
Julien Despreza16120f2020-04-27 08:54:10 -07002967
Julien Desprez6961b272016-02-01 09:58:23 +00002968 if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) {
2969 recoverDeviceFromBootloader();
2970 }
2971 }
2972
Julien Desprez6e6b0232020-04-22 14:16:51 -07002973 /** {@inheritDoc} */
2974 @Override
2975 public boolean isStateBootloaderOrFastbootd() {
2976 return TestDeviceState.FASTBOOT.equals(getDeviceState())
2977 || TestDeviceState.FASTBOOTD.equals(getDeviceState());
Jeffrey Lua6abe552020-01-14 02:56:38 +00002978 }
2979
Julien Desprez6961b272016-02-01 09:58:23 +00002980 /**
2981 * {@inheritDoc}
2982 */
2983 @Override
2984 public void reboot() throws DeviceNotAvailableException {
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00002985 reboot(null);
2986 }
2987
2988 /** {@inheritDoc} */
2989 @Override
2990 public void reboot(@Nullable String reason) throws DeviceNotAvailableException {
2991 rebootUntilOnline(reason);
Julien Desprez6961b272016-02-01 09:58:23 +00002992
2993 RecoveryMode cachedRecoveryMode = getRecoveryMode();
2994 setRecoveryMode(RecoveryMode.ONLINE);
2995
2996 if (isEncryptionSupported() && isDeviceEncrypted()) {
2997 unlockDevice();
2998 }
2999
3000 setRecoveryMode(cachedRecoveryMode);
3001
3002 if (mStateMonitor.waitForDeviceAvailable(mOptions.getRebootTimeout()) != null) {
3003 postBootSetup();
3004 postBootWifiSetup();
3005 return;
3006 } else {
3007 recoverDevice();
3008 }
3009 }
3010
Nikita Ioffec39a1c02019-10-24 17:54:22 +01003011 @Override
3012 public void rebootUserspace() throws DeviceNotAvailableException {
3013 rebootUserspaceUntilOnline();
3014
3015 RecoveryMode cachedRecoveryMode = getRecoveryMode();
3016 setRecoveryMode(RecoveryMode.ONLINE);
3017
3018 if (isEncryptionSupported()) {
3019 if (isDeviceEncrypted()) {
3020 CLog.e("Device is encrypted after userspace reboot!");
3021 unlockDevice();
3022 }
3023 }
3024
3025 setRecoveryMode(cachedRecoveryMode);
3026
3027 if (mStateMonitor.waitForDeviceAvailable(mOptions.getRebootTimeout()) != null) {
3028 postBootSetup();
3029 postBootWifiSetup();
3030 } else {
3031 recoverDevice();
3032 }
3033 }
3034
Julien Desprez6961b272016-02-01 09:58:23 +00003035 @Override
3036 public void rebootUntilOnline() throws DeviceNotAvailableException {
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003037 rebootUntilOnline(null);
3038 }
3039
3040 /** {@inheritDoc} */
3041 @Override
3042 public void rebootUntilOnline(@Nullable String reason) throws DeviceNotAvailableException {
3043 doReboot(RebootMode.REBOOT_FULL, reason);
Nikita Ioffec39a1c02019-10-24 17:54:22 +01003044 RecoveryMode cachedRecoveryMode = getRecoveryMode();
3045 setRecoveryMode(RecoveryMode.ONLINE);
3046 waitForDeviceOnline();
3047 enableAdbRoot();
3048 setRecoveryMode(cachedRecoveryMode);
3049 }
3050
3051 @Override
3052 public void rebootUserspaceUntilOnline() throws DeviceNotAvailableException {
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003053 doReboot(RebootMode.REBOOT_USERSPACE, null);
Julien Desprez6961b272016-02-01 09:58:23 +00003054 RecoveryMode cachedRecoveryMode = getRecoveryMode();
3055 setRecoveryMode(RecoveryMode.ONLINE);
Julien Desprez088e5c92019-03-20 09:10:25 -07003056 waitForDeviceOnline();
3057 enableAdbRoot();
Julien Desprez6961b272016-02-01 09:58:23 +00003058 setRecoveryMode(cachedRecoveryMode);
3059 }
3060
3061 /**
3062 * {@inheritDoc}
3063 */
3064 @Override
3065 public void rebootIntoRecovery() throws DeviceNotAvailableException {
Julien Desprez6e6b0232020-04-22 14:16:51 -07003066 if (isStateBootloaderOrFastbootd()) {
Julien Desprez6961b272016-02-01 09:58:23 +00003067 CLog.w("device %s in fastboot when requesting boot to recovery. " +
3068 "Rebooting to userspace first.", getSerialNumber());
3069 rebootUntilOnline();
3070 }
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003071 doAdbReboot(RebootMode.REBOOT_INTO_RECOVERY, null);
Julien Desprez6961b272016-02-01 09:58:23 +00003072 if (!waitForDeviceInRecovery(mOptions.getAdbRecoveryTimeout())) {
3073 recoverDeviceInRecovery();
3074 }
3075 }
3076
Guang Zhuac76f7a2019-05-22 16:46:23 -07003077
3078 /** {@inheritDoc} */
3079 @Override
3080 public void rebootIntoSideload() throws DeviceNotAvailableException {
Guang Zhu847b5ca2019-12-19 15:27:24 -08003081 rebootIntoSideload(false);
3082 }
3083 /** {@inheritDoc} */
3084 @Override
3085 public void rebootIntoSideload(boolean autoReboot) throws DeviceNotAvailableException {
Julien Desprez6e6b0232020-04-22 14:16:51 -07003086 if (isStateBootloaderOrFastbootd()) {
Guang Zhuac76f7a2019-05-22 16:46:23 -07003087 CLog.w(
3088 "device %s in fastboot when requesting boot to sideload. "
3089 + "Rebooting to userspace first.",
3090 getSerialNumber());
3091 rebootUntilOnline();
3092 }
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003093 final RebootMode rebootMode;
Guang Zhu847b5ca2019-12-19 15:27:24 -08003094 if (autoReboot) {
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003095 rebootMode = RebootMode.REBOOT_INTO_SIDELOAD_AUTO_REBOOT;
Guang Zhu847b5ca2019-12-19 15:27:24 -08003096 } else {
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003097 rebootMode = RebootMode.REBOOT_INTO_SIDELOAD;
Guang Zhu847b5ca2019-12-19 15:27:24 -08003098 }
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003099 doAdbReboot(rebootMode, null);
Guang Zhuac76f7a2019-05-22 16:46:23 -07003100 if (!waitForDeviceInSideload(mOptions.getAdbRecoveryTimeout())) {
3101 // using recovery mode because sideload is a sub-mode under recovery
3102 recoverDeviceInRecovery();
3103 }
3104 }
3105
Julien Desprez6961b272016-02-01 09:58:23 +00003106 /**
3107 * {@inheritDoc}
3108 */
3109 @Override
3110 public void nonBlockingReboot() throws DeviceNotAvailableException {
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003111 doReboot(RebootMode.REBOOT_FULL, null);
3112 }
3113
3114 /**
3115 * A mode of a reboot.
3116 *
3117 * <p>Source of truth for available modes is defined in init.
3118 */
3119 @VisibleForTesting
3120 protected enum RebootMode {
3121 REBOOT_FULL(""),
3122 REBOOT_USERSPACE("userspace"),
Julien Despreza16120f2020-04-27 08:54:10 -07003123 REBOOT_INTO_FASTBOOTD("fastboot"),
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003124 REBOOT_INTO_BOOTLOADER("bootloader"),
3125 REBOOT_INTO_SIDELOAD("sideload"),
3126 REBOOT_INTO_SIDELOAD_AUTO_REBOOT("sideload-auto-reboot"),
3127 REBOOT_INTO_RECOVERY("recovery");
3128
3129 private final String mRebootTarget;
3130
3131 RebootMode(String rebootTarget) {
3132 mRebootTarget = rebootTarget;
3133 }
3134
3135 @Nullable
3136 String formatRebootCommand(@Nullable String reason) {
3137 if (this == REBOOT_FULL) {
3138 return Strings.isNullOrEmpty(reason) ? null : reason;
3139 } else {
3140 return Strings.isNullOrEmpty(reason) ? mRebootTarget : mRebootTarget + "," + reason;
3141 }
3142 }
Dan Shifdfbc532020-01-14 10:59:52 -08003143
3144 @Override
3145 public String toString() {
3146 return mRebootTarget;
3147 }
Julien Desprez6961b272016-02-01 09:58:23 +00003148 }
3149
Julien Desprez78344aa2018-09-04 16:06:05 -07003150 /**
3151 * Trigger a reboot of the device, offers no guarantee of the device state after the call.
3152 *
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003153 * @param rebootMode a mode of this reboot
3154 * @param reason reason for this reboot
Julien Desprez78344aa2018-09-04 16:06:05 -07003155 * @throws DeviceNotAvailableException
3156 * @throws UnsupportedOperationException
3157 */
Jeffrey Lu279122f2018-01-29 17:25:08 -08003158 @VisibleForTesting
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003159 void doReboot(RebootMode rebootMode, @Nullable final String reason)
Nikita Ioffec39a1c02019-10-24 17:54:22 +01003160 throws DeviceNotAvailableException, UnsupportedOperationException {
Julien Despreza5397f12019-03-28 09:46:16 -07003161 // Track Tradefed reboot time
3162 mLastTradefedRebootTime = System.currentTimeMillis();
3163
Julien Desprez47b0a542020-04-23 09:40:17 -07003164 if (isStateBootloaderOrFastbootd()) {
3165 CLog.i("device %s in %s. Rebooting to userspace.", getSerialNumber(), getDeviceState());
Julien Desprez6961b272016-02-01 09:58:23 +00003166 executeFastbootCommand("reboot");
3167 } else {
Guang Zhu120ed1c2016-02-24 23:31:49 -08003168 if (mOptions.shouldDisableReboot()) {
3169 CLog.i("Device reboot disabled by options, skipped.");
3170 return;
3171 }
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003172 if (reason == null) {
Julien Desprez5dd5c232020-03-24 09:46:38 -07003173 CLog.i("Rebooting device %s mode: %s", getSerialNumber(), rebootMode.name());
Nikita Ioffec39a1c02019-10-24 17:54:22 +01003174 } else {
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003175 CLog.i(
3176 "Rebooting device %s mode: %s reason: %s",
Julien Desprez5dd5c232020-03-24 09:46:38 -07003177 getSerialNumber(), rebootMode.name(), reason);
Nikita Ioffec39a1c02019-10-24 17:54:22 +01003178 }
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003179 doAdbReboot(rebootMode, reason);
Julien Desprez78344aa2018-09-04 16:06:05 -07003180 // Check if device shows as unavailable (as expected after reboot).
3181 boolean notAvailable = waitForDeviceNotAvailable(DEFAULT_UNAVAILABLE_TIMEOUT);
3182 if (notAvailable) {
3183 postAdbReboot();
3184 } else {
3185 CLog.w(
3186 "Did not detect device %s becoming unavailable after reboot",
3187 getSerialNumber());
3188 }
Julien Desprez6961b272016-02-01 09:58:23 +00003189 }
3190 }
3191
3192 /**
Julien Desprez78344aa2018-09-04 16:06:05 -07003193 * Possible extra actions that can be taken after a reboot.
3194 *
3195 * @throws DeviceNotAvailableException
3196 */
3197 protected void postAdbReboot() throws DeviceNotAvailableException {
3198 // Default implementation empty on purpose.
3199 }
3200
3201 /**
Julien Desprez6961b272016-02-01 09:58:23 +00003202 * Perform a adb reboot.
3203 *
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003204 * @param rebootMode a mode of this reboot.
3205 * @param reason for this reboot.
Julien Desprez6961b272016-02-01 09:58:23 +00003206 * @throws DeviceNotAvailableException
3207 */
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003208 protected void doAdbReboot(RebootMode rebootMode, @Nullable final String reason)
3209 throws DeviceNotAvailableException {
3210 DeviceAction rebootAction = createRebootDeviceAction(rebootMode, reason);
Julien Desprezc8474552016-02-17 10:59:27 +00003211 performDeviceAction("reboot", rebootAction, MAX_RETRY_ATTEMPTS);
Julien Desprez78344aa2018-09-04 16:06:05 -07003212 }
Julien Desprezc8474552016-02-17 10:59:27 +00003213
Julien Desprez78344aa2018-09-04 16:06:05 -07003214 /**
3215 * Create a {@link RebootDeviceAction} to be used when performing a reboot action.
3216 *
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003217 * @param rebootMode a mode of this reboot.
3218 * @param reason for this reboot.
Julien Desprez78344aa2018-09-04 16:06:05 -07003219 * @return the created {@link RebootDeviceAction}.
3220 */
Nikita Ioffef0aa00a2020-01-16 00:11:37 +00003221 protected RebootDeviceAction createRebootDeviceAction(
3222 RebootMode rebootMode, @Nullable final String reason) {
3223 return new RebootDeviceAction(rebootMode, reason);
Julien Desprez6961b272016-02-01 09:58:23 +00003224 }
3225
Julien Desprezc8474552016-02-17 10:59:27 +00003226 protected void waitForDeviceNotAvailable(String operationDesc, long time) {
Julien Desprez6961b272016-02-01 09:58:23 +00003227 // TODO: a bit of a race condition here. Would be better to start a
3228 // before the operation
3229 if (!mStateMonitor.waitForDeviceNotAvailable(time)) {
3230 // above check is flaky, ignore till better solution is found
3231 CLog.w("Did not detect device %s becoming unavailable after %s", getSerialNumber(),
3232 operationDesc);
3233 }
3234 }
3235
3236 /**
3237 * {@inheritDoc}
3238 */
3239 @Override
3240 public boolean enableAdbRoot() throws DeviceNotAvailableException {
3241 // adb root is a relatively intensive command, so do a brief check first to see
3242 // if its necessary or not
3243 if (isAdbRoot()) {
3244 CLog.i("adb is already running as root on %s", getSerialNumber());
jdesprezdcc60fc2017-08-07 14:51:00 -07003245 // Still check for online, in some case we could see the root, but device could be
3246 // very early in its cycle.
3247 waitForDeviceOnline();
Julien Desprez6961b272016-02-01 09:58:23 +00003248 return true;
3249 }
3250 // Don't enable root if user requested no root
3251 if (!isEnableAdbRoot()) {
3252 CLog.i("\"enable-root\" set to false; ignoring 'adb root' request");
3253 return false;
3254 }
3255 CLog.i("adb root on device %s", getSerialNumber());
3256 int attempts = MAX_RETRY_ATTEMPTS + 1;
3257 for (int i=1; i <= attempts; i++) {
3258 String output = executeAdbCommand("root");
3259 // wait for device to disappear from adb
3260 waitForDeviceNotAvailable("root", 20 * 1000);
Julien Desprez8539c082016-03-04 13:39:19 +00003261
3262 postAdbRootAction();
3263
Julien Desprez6961b272016-02-01 09:58:23 +00003264 // wait for device to be back online
3265 waitForDeviceOnline();
3266
3267 if (isAdbRoot()) {
3268 return true;
3269 }
3270 CLog.w("'adb root' on %s unsuccessful on attempt %d of %d. Output: '%s'",
3271 getSerialNumber(), i, attempts, output);
3272 }
3273 return false;
3274 }
3275
3276 /**
Julien Desprez22d0c3a2016-04-15 10:38:08 +01003277 * {@inheritDoc}
3278 */
3279 @Override
3280 public boolean disableAdbRoot() throws DeviceNotAvailableException {
3281 if (!isAdbRoot()) {
3282 CLog.i("adb is already unroot on %s", getSerialNumber());
3283 return true;
3284 }
3285
3286 CLog.i("adb unroot on device %s", getSerialNumber());
3287 int attempts = MAX_RETRY_ATTEMPTS + 1;
3288 for (int i=1; i <= attempts; i++) {
3289 String output = executeAdbCommand("unroot");
3290 // wait for device to disappear from adb
3291 waitForDeviceNotAvailable("unroot", 5 * 1000);
3292
3293 postAdbUnrootAction();
3294
3295 // wait for device to be back online
3296 waitForDeviceOnline();
3297
3298 if (!isAdbRoot()) {
3299 return true;
3300 }
3301 CLog.w("'adb unroot' on %s unsuccessful on attempt %d of %d. Output: '%s'",
3302 getSerialNumber(), i, attempts, output);
3303 }
3304 return false;
3305 }
3306
3307 /**
Julien Desprez8539c082016-03-04 13:39:19 +00003308 * Override if the device needs some specific actions to be taken after adb root and before the
3309 * device is back online.
3310 * Default implementation doesn't include any addition actions.
3311 * adb root is not guaranteed to be enabled at this stage.
Julien Desprez021af1d2016-11-29 14:54:07 +00003312 * @throws DeviceNotAvailableException
Julien Desprez8539c082016-03-04 13:39:19 +00003313 */
Julien Desprez9dca62e2016-04-08 14:47:57 +01003314 public void postAdbRootAction() throws DeviceNotAvailableException {
Julien Desprez8539c082016-03-04 13:39:19 +00003315 // Empty on purpose.
3316 }
3317
3318 /**
Julien Desprez22d0c3a2016-04-15 10:38:08 +01003319 * Override if the device needs some specific actions to be taken after adb unroot and before
3320 * the device is back online.
3321 * Default implementation doesn't include any additional actions.
3322 * adb root is not guaranteed to be disabled at this stage.
Julien Desprez021af1d2016-11-29 14:54:07 +00003323 * @throws DeviceNotAvailableException
Julien Desprez22d0c3a2016-04-15 10:38:08 +01003324 */
Julien Desprez5e4fc6b2016-06-06 10:08:46 +01003325 public void postAdbUnrootAction() throws DeviceNotAvailableException {
Julien Desprez22d0c3a2016-04-15 10:38:08 +01003326 // Empty on purpose.
3327 }
3328
3329 /**
Julien Desprez6961b272016-02-01 09:58:23 +00003330 * {@inheritDoc}
3331 */
3332 @Override
3333 public boolean isAdbRoot() throws DeviceNotAvailableException {
3334 String output = executeShellCommand("id");
3335 return output.contains("uid=0(root)");
3336 }
3337
3338 /**
3339 * {@inheritDoc}
3340 */
3341 @Override
3342 public boolean encryptDevice(boolean inplace) throws DeviceNotAvailableException,
3343 UnsupportedOperationException {
3344 if (!isEncryptionSupported()) {
3345 throw new UnsupportedOperationException(String.format("Can't encrypt device %s: "
3346 + "encryption not supported", getSerialNumber()));
3347 }
3348
3349 if (isDeviceEncrypted()) {
3350 CLog.d("Device %s is already encrypted, skipping", getSerialNumber());
3351 return true;
3352 }
3353
3354 enableAdbRoot();
3355
3356 String encryptMethod;
3357 long timeout;
3358 if (inplace) {
3359 encryptMethod = "inplace";
3360 timeout = ENCRYPTION_INPLACE_TIMEOUT_MIN;
3361 } else {
3362 encryptMethod = "wipe";
3363 timeout = ENCRYPTION_WIPE_TIMEOUT_MIN;
3364 }
3365
3366 CLog.i("Encrypting device %s via %s", getSerialNumber(), encryptMethod);
3367
3368 // enable crypto takes one of the following formats:
3369 // cryptfs enablecrypto <wipe|inplace> <passwd>
3370 // cryptfs enablecrypto <wipe|inplace> default|password|pin|pattern [passwd]
3371 // Try the first one first, if it outputs "500 0 Usage: ...", try the second.
3372 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
3373 String command = String.format("vdc cryptfs enablecrypto %s \"%s\"", encryptMethod,
3374 ENCRYPTION_PASSWORD);
3375 executeShellCommand(command, receiver, timeout, TimeUnit.MINUTES, 1);
Julien Desprezac96c812016-08-10 14:57:45 +01003376 if (receiver.getOutput().split(":")[0].matches("500 \\d+ Usage")) {
Julien Desprez6961b272016-02-01 09:58:23 +00003377 command = String.format("vdc cryptfs enablecrypto %s default", encryptMethod);
3378 executeShellCommand(command, new NullOutputReceiver(), timeout, TimeUnit.MINUTES, 1);
3379 }
3380
3381 waitForDeviceNotAvailable("reboot", getCommandTimeout());
3382 waitForDeviceOnline(); // Device will not become available until the user data is unlocked.
3383
3384 return isDeviceEncrypted();
3385 }
3386
3387 /**
3388 * {@inheritDoc}
3389 */
3390 @Override
3391 public boolean unencryptDevice() throws DeviceNotAvailableException,
3392 UnsupportedOperationException {
3393 if (!isEncryptionSupported()) {
3394 throw new UnsupportedOperationException(String.format("Can't unencrypt device %s: "
3395 + "encryption not supported", getSerialNumber()));
3396 }
3397
3398 if (!isDeviceEncrypted()) {
3399 CLog.d("Device %s is already unencrypted, skipping", getSerialNumber());
3400 return true;
3401 }
3402
3403 CLog.i("Unencrypting device %s", getSerialNumber());
3404
3405 // If the device supports fastboot format, then we're done.
3406 if (!mOptions.getUseFastbootErase()) {
3407 rebootIntoBootloader();
3408 fastbootWipePartition("userdata");
3409 rebootUntilOnline();
3410 waitForDeviceAvailable(ENCRYPTION_WIPE_TIMEOUT_MIN * 60 * 1000);
3411 return true;
3412 }
3413
3414 // Determine if we need to format partition instead of wipe.
3415 boolean format = false;
3416 String output = executeShellCommand("vdc volume list");
3417 String[] splitOutput;
3418 if (output != null) {
3419 splitOutput = output.split("\r?\n");
3420 for (String line : splitOutput) {
3421 if (line.startsWith("110 ") && line.contains("sdcard /mnt/sdcard") &&
3422 !line.endsWith("0")) {
3423 format = true;
3424 }
3425 }
3426 }
3427
3428 rebootIntoBootloader();
3429 fastbootWipePartition("userdata");
3430
3431 // If the device requires time to format the filesystem after fastboot erase userdata, wait
3432 // for the device to reboot a second time.
3433 if (mOptions.getUnencryptRebootTimeout() > 0) {
3434 rebootUntilOnline();
3435 if (waitForDeviceNotAvailable(mOptions.getUnencryptRebootTimeout())) {
3436 waitForDeviceOnline();
3437 }
3438 }
3439
3440 if (format) {
3441 CLog.d("Need to format sdcard for device %s", getSerialNumber());
3442
3443 RecoveryMode cachedRecoveryMode = getRecoveryMode();
3444 setRecoveryMode(RecoveryMode.ONLINE);
3445
3446 output = executeShellCommand("vdc volume format sdcard");
3447 if (output == null) {
3448 CLog.e("Command vdc volume format sdcard failed will no output for device %s:\n%s",
3449 getSerialNumber());
3450 setRecoveryMode(cachedRecoveryMode);
3451 return false;
3452 }
3453 splitOutput = output.split("\r?\n");
3454 if (!splitOutput[splitOutput.length - 1].startsWith("200 ")) {
3455 CLog.e("Command vdc volume format sdcard failed for device %s:\n%s",
3456 getSerialNumber(), output);
3457 setRecoveryMode(cachedRecoveryMode);
3458 return false;
3459 }
3460
3461 setRecoveryMode(cachedRecoveryMode);
3462 }
3463
3464 reboot();
3465
3466 return true;
3467 }
3468
3469 /**
3470 * {@inheritDoc}
3471 */
3472 @Override
3473 public boolean unlockDevice() throws DeviceNotAvailableException,
3474 UnsupportedOperationException {
3475 if (!isEncryptionSupported()) {
3476 throw new UnsupportedOperationException(String.format("Can't unlock device %s: "
3477 + "encryption not supported", getSerialNumber()));
3478 }
3479
3480 if (!isDeviceEncrypted()) {
3481 CLog.d("Device %s is not encrypted, skipping", getSerialNumber());
3482 return true;
3483 }
3484
3485 CLog.i("Unlocking device %s", getSerialNumber());
3486
3487 enableAdbRoot();
3488
3489 // FIXME: currently, vcd checkpw can return an empty string when it never should. Try 3
3490 // times.
3491 String output;
3492 int i = 0;
3493 do {
3494 // Enter the password. Output will be:
3495 // "200 [X] -1" if the password has already been entered correctly,
3496 // "200 [X] 0" if the password is entered correctly,
3497 // "200 [X] N" where N is any positive number if the password is incorrect,
3498 // any other string if there is an error.
3499 output = executeShellCommand(String.format("vdc cryptfs checkpw \"%s\"",
3500 ENCRYPTION_PASSWORD)).trim();
3501
3502 if (output.startsWith("200 ") && output.endsWith(" -1")) {
3503 return true;
3504 }
3505
3506 if (!output.isEmpty() && !(output.startsWith("200 ") && output.endsWith(" 0"))) {
3507 CLog.e("checkpw gave output '%s' while trying to unlock device %s",
3508 output, getSerialNumber());
3509 return false;
3510 }
3511
3512 getRunUtil().sleep(500);
3513 } while (output.isEmpty() && ++i < 3);
3514
3515 if (output.isEmpty()) {
3516 CLog.e("checkpw gave no output while trying to unlock device %s");
3517 }
3518
3519 // Restart the framework. Output will be:
3520 // "200 [X] 0" if the user data partition can be mounted,
3521 // "200 [X] -1" if the user data partition can not be mounted (no correct password given),
3522 // any other string if there is an error.
3523 output = executeShellCommand("vdc cryptfs restart").trim();
3524
3525 if (!(output.startsWith("200 ") && output.endsWith(" 0"))) {
3526 CLog.e("restart gave output '%s' while trying to unlock device %s", output,
3527 getSerialNumber());
3528 return false;
3529 }
3530
3531 waitForDeviceAvailable();
3532
3533 return true;
3534 }
3535
3536 /**
3537 * {@inheritDoc}
3538 */
3539 @Override
3540 public boolean isDeviceEncrypted() throws DeviceNotAvailableException {
Julien Desprezaf166b32016-08-08 11:58:47 +01003541 String output = getProperty("ro.crypto.state");
Julien Desprez6961b272016-02-01 09:58:23 +00003542
3543 if (output == null && isEncryptionSupported()) {
3544 CLog.w("Property ro.crypto.state is null on device %s", getSerialNumber());
3545 }
3546
Julien Desprezf10e2052019-05-28 09:09:49 -07003547 return "encrypted".equals(output.trim());
Julien Desprez6961b272016-02-01 09:58:23 +00003548 }
3549
3550 /**
3551 * {@inheritDoc}
3552 */
3553 @Override
3554 public boolean isEncryptionSupported() throws DeviceNotAvailableException {
3555 if (!isEnableAdbRoot()) {
3556 CLog.i("root is required for encryption");
3557 mIsEncryptionSupported = false;
3558 return mIsEncryptionSupported;
3559 }
3560 if (mIsEncryptionSupported != null) {
3561 return mIsEncryptionSupported.booleanValue();
3562 }
3563 enableAdbRoot();
jdesprezdd9d9e92017-07-13 03:34:19 -07003564
Julien Desprezf10e2052019-05-28 09:09:49 -07003565 String output = getProperty("ro.crypto.state");
3566 if (output == null || "unsupported".equals(output.trim())) {
3567 mIsEncryptionSupported = false;
3568 return mIsEncryptionSupported;
3569 }
3570 mIsEncryptionSupported = true;
Julien Desprez6961b272016-02-01 09:58:23 +00003571 return mIsEncryptionSupported;
3572 }
3573
3574 /**
3575 * {@inheritDoc}
3576 */
3577 @Override
3578 public void waitForDeviceOnline(long waitTime) throws DeviceNotAvailableException {
3579 if (mStateMonitor.waitForDeviceOnline(waitTime) == null) {
3580 recoverDevice();
3581 }
3582 }
3583
3584 /**
3585 * {@inheritDoc}
3586 */
3587 @Override
3588 public void waitForDeviceOnline() throws DeviceNotAvailableException {
3589 if (mStateMonitor.waitForDeviceOnline() == null) {
3590 recoverDevice();
3591 }
3592 }
3593
3594 /**
3595 * {@inheritDoc}
3596 */
3597 @Override
3598 public void waitForDeviceAvailable(long waitTime) throws DeviceNotAvailableException {
3599 if (mStateMonitor.waitForDeviceAvailable(waitTime) == null) {
3600 recoverDevice();
3601 }
3602 }
3603
3604 /**
3605 * {@inheritDoc}
3606 */
3607 @Override
3608 public void waitForDeviceAvailable() throws DeviceNotAvailableException {
3609 if (mStateMonitor.waitForDeviceAvailable() == null) {
3610 recoverDevice();
3611 }
3612 }
3613
3614 /**
3615 * {@inheritDoc}
3616 */
3617 @Override
3618 public boolean waitForDeviceNotAvailable(long waitTime) {
3619 return mStateMonitor.waitForDeviceNotAvailable(waitTime);
3620 }
3621
3622 /**
3623 * {@inheritDoc}
3624 */
3625 @Override
3626 public boolean waitForDeviceInRecovery(long waitTime) {
3627 return mStateMonitor.waitForDeviceInRecovery(waitTime);
3628 }
3629
Guang Zhuac76f7a2019-05-22 16:46:23 -07003630 /** {@inheritDoc} */
3631 @Override
3632 public boolean waitForDeviceInSideload(long waitTime) {
3633 return mStateMonitor.waitForDeviceInSideload(waitTime);
3634 }
3635
Julien Desprez6961b272016-02-01 09:58:23 +00003636 /**
3637 * Small helper function to throw an NPE if the passed arg is null. This should be used when
3638 * some value will be stored and used later, in which case it'll avoid hard-to-trace
3639 * asynchronous NullPointerExceptions by throwing the exception synchronously. This is not
3640 * intended to be used where the NPE would be thrown synchronously -- just let the jvm take care
3641 * of it in that case.
3642 */
3643 private void throwIfNull(Object obj) {
3644 if (obj == null) throw new NullPointerException();
3645 }
3646
Jeffrey Lu279122f2018-01-29 17:25:08 -08003647 /** Retrieve this device's recovery mechanism. */
3648 @VisibleForTesting
Julien Desprez6961b272016-02-01 09:58:23 +00003649 IDeviceRecovery getRecovery() {
3650 return mRecovery;
3651 }
3652
3653 /**
3654 * {@inheritDoc}
3655 */
3656 @Override
3657 public void setRecovery(IDeviceRecovery recovery) {
3658 throwIfNull(recovery);
3659 mRecovery = recovery;
3660 }
3661
3662 /**
3663 * {@inheritDoc}
3664 */
3665 @Override
3666 public void setRecoveryMode(RecoveryMode mode) {
3667 throwIfNull(mRecoveryMode);
3668 mRecoveryMode = mode;
3669 }
3670
3671 /**
3672 * {@inheritDoc}
3673 */
3674 @Override
3675 public RecoveryMode getRecoveryMode() {
3676 return mRecoveryMode;
3677 }
3678
3679 /**
3680 * {@inheritDoc}
3681 */
3682 @Override
3683 public void setFastbootEnabled(boolean fastbootEnabled) {
3684 mFastbootEnabled = fastbootEnabled;
3685 }
3686
3687 /**
3688 * {@inheritDoc}
3689 */
3690 @Override
Julien Desprez3a503442016-04-05 15:00:41 +01003691 public boolean isFastbootEnabled() {
3692 return mFastbootEnabled;
3693 }
3694
3695 /**
3696 * {@inheritDoc}
3697 */
3698 @Override
Julien Desprez0a7d67d2016-07-21 16:05:57 +01003699 public void setFastbootPath(String fastbootPath) {
3700 mFastbootPath = fastbootPath;
3701 // ensure the device and its associated recovery use the same fastboot version.
3702 mRecovery.setFastbootPath(fastbootPath);
3703 }
3704
3705 /**
3706 * {@inheritDoc}
3707 */
3708 @Override
3709 public String getFastbootPath() {
3710 return mFastbootPath;
3711 }
3712
Julien Desprez072acdf2019-02-15 14:23:50 -08003713 /** {@inheritDoc} */
3714 @Override
3715 public String getFastbootVersion() {
3716 try {
3717 CommandResult res = executeFastbootCommand("--version");
3718 return res.getStdout().trim();
3719 } catch (DeviceNotAvailableException e) {
3720 // Ignored for host side request
3721 }
3722 return null;
3723 }
3724
Julien Desprez0a7d67d2016-07-21 16:05:57 +01003725 /**
3726 * {@inheritDoc}
3727 */
3728 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003729 public void setDeviceState(final TestDeviceState deviceState) {
3730 if (!deviceState.equals(getDeviceState())) {
3731 // disable state changes while fastboot lock is held, because issuing fastboot command
3732 // will disrupt state
Julien Desprez47b0a542020-04-23 09:40:17 -07003733 if (isStateBootloaderOrFastbootd() && mFastbootLock.isLocked()) {
Julien Desprez6961b272016-02-01 09:58:23 +00003734 return;
3735 }
3736 mState = deviceState;
3737 CLog.d("Device %s state is now %s", getSerialNumber(), deviceState);
3738 mStateMonitor.setState(deviceState);
3739 }
3740 }
3741
3742 /**
3743 * {@inheritDoc}
3744 */
3745 @Override
3746 public TestDeviceState getDeviceState() {
3747 return mState;
3748 }
3749
3750 @Override
3751 public boolean isAdbTcp() {
3752 return mStateMonitor.isAdbTcp();
3753 }
3754
3755 /**
3756 * {@inheritDoc}
3757 */
3758 @Override
3759 public String switchToAdbTcp() throws DeviceNotAvailableException {
3760 String ipAddress = getIpAddress();
3761 if (ipAddress == null) {
3762 CLog.e("connectToTcp failed: Device %s doesn't have an IP", getSerialNumber());
3763 return null;
3764 }
3765 String port = "5555";
3766 executeAdbCommand("tcpip", port);
3767 // TODO: analyze result? wait for device offline?
3768 return String.format("%s:%s", ipAddress, port);
3769 }
3770
3771 /**
3772 * {@inheritDoc}
3773 */
3774 @Override
3775 public boolean switchToAdbUsb() throws DeviceNotAvailableException {
3776 executeAdbCommand("usb");
3777 // TODO: analyze result? wait for device offline?
3778 return true;
3779 }
3780
3781 /**
3782 * {@inheritDoc}
3783 */
3784 @Override
3785 public void setEmulatorProcess(Process p) {
3786 mEmulatorProcess = p;
3787
3788 }
3789
3790 /**
3791 * For emulator set {@link SizeLimitedOutputStream} to log output
3792 * @param output to log the output
3793 */
3794 public void setEmulatorOutputStream(SizeLimitedOutputStream output) {
3795 mEmulatorOutput = output;
3796 }
3797
3798 /**
3799 * {@inheritDoc}
3800 */
3801 @Override
3802 public void stopEmulatorOutput() {
3803 if (mEmulatorOutput != null) {
3804 mEmulatorOutput.delete();
3805 mEmulatorOutput = null;
3806 }
3807 }
3808
3809 /**
3810 * {@inheritDoc}
3811 */
3812 @Override
3813 public InputStreamSource getEmulatorOutput() {
3814 if (getIDevice().isEmulator()) {
3815 if (mEmulatorOutput == null) {
3816 CLog.w("Emulator output for %s was not captured in background",
3817 getSerialNumber());
3818 } else {
3819 try {
jdesprez14084fb2017-07-26 14:36:39 -07003820 return new SnapshotInputStreamSource(
3821 "getEmulatorOutput", mEmulatorOutput.getData());
Julien Desprez6961b272016-02-01 09:58:23 +00003822 } catch (IOException e) {
3823 CLog.e("Failed to get %s data.", getSerialNumber());
3824 CLog.e(e);
3825 }
3826 }
3827 }
3828 return new ByteArrayInputStreamSource(new byte[0]);
3829 }
3830
3831 /**
3832 * {@inheritDoc}
3833 */
3834 @Override
3835 public Process getEmulatorProcess() {
3836 return mEmulatorProcess;
3837 }
3838
3839 /**
3840 * @return <code>true</code> if adb root should be enabled on device
3841 */
3842 public boolean isEnableAdbRoot() {
3843 return mOptions.isEnableAdbRoot();
3844 }
3845
3846 /**
3847 * {@inheritDoc}
3848 */
3849 @Override
3850 public Set<String> getInstalledPackageNames() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003851 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003852 }
3853
Jiyong Park3396a842018-12-17 14:01:54 +09003854 /** {@inheritDoc} */
3855 @Override
Julien Desprez529381a2019-05-02 15:16:35 -07003856 public boolean isPackageInstalled(String packageName) throws DeviceNotAvailableException {
3857 throw new UnsupportedOperationException("No support for Package's feature");
3858 }
3859
3860 /** {@inheritDoc} */
3861 @Override
Julien Desprez0b44ad32019-05-15 19:02:45 -07003862 public boolean isPackageInstalled(String packageName, String userId)
3863 throws DeviceNotAvailableException {
3864 throw new UnsupportedOperationException("No support for Package's feature");
3865 }
3866
3867 /** {@inheritDoc} */
3868 @Override
Jiyong Park3396a842018-12-17 14:01:54 +09003869 public Set<ApexInfo> getActiveApexes() throws DeviceNotAvailableException {
3870 throw new UnsupportedOperationException("No support for Package's feature");
3871 }
3872
Julien Desprez6961b272016-02-01 09:58:23 +00003873 /**
3874 * {@inheritDoc}
3875 */
3876 @Override
3877 public Set<String> getUninstallablePackageNames() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003878 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003879 }
3880
3881 /**
3882 * {@inheritDoc}
3883 */
3884 @Override
3885 public PackageInfo getAppPackageInfo(String packageName) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003886 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003887 }
3888
3889 /**
3890 * {@inheritDoc}
3891 */
3892 @Override
3893 public TestDeviceOptions getOptions() {
3894 return mOptions;
3895 }
3896
3897 /**
3898 * {@inheritDoc}
3899 */
3900 @Override
3901 public int getApiLevel() throws DeviceNotAvailableException {
3902 int apiLevel = UNKNOWN_API_LEVEL;
3903 try {
Julien Desprez52388172019-07-31 16:32:49 -07003904 String prop = getProperty(DeviceProperties.SDK_VERSION);
Julien Desprez6961b272016-02-01 09:58:23 +00003905 apiLevel = Integer.parseInt(prop);
3906 } catch (NumberFormatException nfe) {
Al Sutton52e1a0b2019-10-21 14:19:55 +01003907 CLog.w(
3908 "Unable to get API level from "
3909 + DeviceProperties.SDK_VERSION
3910 + ", falling back to UNKNOWN.",
3911 nfe);
Julien Desprez6961b272016-02-01 09:58:23 +00003912 // ignore, return unknown instead
3913 }
3914 return apiLevel;
3915 }
3916
Julien Desprez3c4fb012019-04-17 09:12:30 -07003917 /** {@inheritDoc} */
3918 @Override
3919 public boolean checkApiLevelAgainstNextRelease(int strictMinLevel)
3920 throws DeviceNotAvailableException {
Julien Despreza76922b2019-12-05 11:50:41 -08003921 String codeName = getProperty(DeviceProperties.BUILD_CODENAME);
3922 if (codeName == null) {
3923 throw new DeviceRuntimeException(
3924 String.format(
3925 "Failed to query property '%s'. device returned null.",
3926 DeviceProperties.BUILD_CODENAME));
3927 }
3928 codeName = codeName.trim();
Julien Desprez3c4fb012019-04-17 09:12:30 -07003929 int apiLevel = getApiLevel() + ("REL".equals(codeName) ? 0 : 1);
3930 if (strictMinLevel > apiLevel) {
3931 return false;
3932 }
3933 return true;
3934 }
3935
jdesprezc3537f72017-09-05 17:27:14 -07003936 private int getApiLevelSafe() {
3937 try {
3938 return getApiLevel();
3939 } catch (DeviceNotAvailableException e) {
3940 CLog.e(e);
3941 return UNKNOWN_API_LEVEL;
3942 }
3943 }
3944
yangbilla24aa2f2020-02-10 17:49:03 +08003945 /** {@inheritDoc} */
3946 @Override
3947 public int getLaunchApiLevel() throws DeviceNotAvailableException {
3948 try {
3949 String prop = getProperty(DeviceProperties.FIRST_API_LEVEL);
3950 return Integer.parseInt(prop);
3951 } catch (NumberFormatException nfe) {
3952 CLog.w(
3953 "Unable to get first launch API level from "
3954 + DeviceProperties.FIRST_API_LEVEL
3955 + ", falling back to getApiLevel().",
3956 nfe);
3957 }
3958 return getApiLevel();
3959 }
3960
Julien Desprez6961b272016-02-01 09:58:23 +00003961 @Override
3962 public IDeviceStateMonitor getMonitor() {
3963 return mStateMonitor;
3964 }
3965
3966 /**
3967 * {@inheritDoc}
3968 */
3969 @Override
3970 public boolean waitForDeviceShell(long waitTime) {
3971 return mStateMonitor.waitForDeviceShell(waitTime);
3972 }
3973
3974 @Override
3975 public DeviceAllocationState getAllocationState() {
3976 return mAllocationState;
3977 }
3978
3979 /**
3980 * {@inheritDoc}
3981 * <p>
3982 * Process the DeviceEvent, which may or may not transition this device to a new allocation
3983 * state.
3984 * </p>
3985 */
3986 @Override
3987 public DeviceEventResponse handleAllocationEvent(DeviceEvent event) {
3988
3989 // keep track of whether state has actually changed or not
3990 boolean stateChanged = false;
3991 DeviceAllocationState newState;
3992 DeviceAllocationState oldState = mAllocationState;
3993 mAllocationStateLock.lock();
3994 try {
3995 // update oldState here, just in case in changed before we got lock
3996 oldState = mAllocationState;
3997 newState = mAllocationState.handleDeviceEvent(event);
3998 if (oldState != newState) {
3999 // state has changed! record this fact, and store the new state
4000 stateChanged = true;
4001 mAllocationState = newState;
4002 }
4003 } finally {
4004 mAllocationStateLock.unlock();
4005 }
4006 if (stateChanged && mAllocationMonitor != null) {
4007 // state has changed! Lets inform the allocation monitor listener
4008 mAllocationMonitor.notifyDeviceStateChange(getSerialNumber(), oldState, newState);
4009 }
4010 return new DeviceEventResponse(newState, stateChanged);
4011 }
4012
jdespreze2d5ed72018-03-07 14:48:16 -08004013 /** {@inheritDoc} */
4014 @Override
4015 public long getDeviceTimeOffset(Date date) throws DeviceNotAvailableException {
Julien Desprezdcb19d52016-06-20 12:23:12 +01004016 Long deviceTime = getDeviceDate();
Julien Desprez6961b272016-02-01 09:58:23 +00004017 long offset = 0;
4018
Julien Desprez6961b272016-02-01 09:58:23 +00004019 if (date == null) {
4020 date = new Date();
4021 }
4022
jdesprez0b9d69e2017-12-11 03:40:39 -08004023 offset = date.getTime() - deviceTime;
Julien Desprez1fadf1a2016-10-20 15:50:46 +01004024 CLog.d("Time offset = %d ms", offset);
Julien Desprez6961b272016-02-01 09:58:23 +00004025 return offset;
4026 }
4027
4028 /**
4029 * {@inheritDoc}
4030 */
4031 @Override
4032 public void setDate(Date date) throws DeviceNotAvailableException {
4033 if (date == null) {
4034 date = new Date();
4035 }
4036 long timeOffset = getDeviceTimeOffset(date);
4037 // no need to set date
4038 if (Math.abs(timeOffset) <= MAX_HOST_DEVICE_TIME_OFFSET) {
4039 return;
4040 }
4041 String dateString = null;
4042 if (getApiLevel() < 23) {
4043 // set date in epoch format
4044 dateString = Long.toString(date.getTime() / 1000); //ms to s
4045 } else {
4046 // set date with POSIX like params
4047 SimpleDateFormat sdf = new java.text.SimpleDateFormat(
4048 "MMddHHmmyyyy.ss");
4049 sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
4050 dateString = sdf.format(date);
4051 }
4052 // best effort, no verification
Julien Desprez0fd9e562019-03-12 14:05:37 -07004053 // Use TZ= to default to UTC timezone (b/128353510 for background)
4054 executeShellCommand("TZ=UTC date -u " + dateString);
Julien Desprez6961b272016-02-01 09:58:23 +00004055 }
4056
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004057 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00004058 @Override
Julien Desprezdcb19d52016-06-20 12:23:12 +01004059 public long getDeviceDate() throws DeviceNotAvailableException {
4060 String deviceTimeString = executeShellCommand("date +%s");
4061 Long deviceTime = null;
4062 try {
4063 deviceTime = Long.valueOf(deviceTimeString.trim());
4064 } catch (NumberFormatException nfe) {
4065 CLog.i("Invalid device time: \"%s\", ignored.", nfe);
4066 return 0;
4067 }
jdesprez0b9d69e2017-12-11 03:40:39 -08004068 // Convert from seconds to milliseconds
4069 return deviceTime * 1000L;
Julien Desprezdcb19d52016-06-20 12:23:12 +01004070 }
4071
4072 /**
4073 * {@inheritDoc}
4074 */
4075 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00004076 public boolean waitForBootComplete(long timeOut) throws DeviceNotAvailableException {
4077 return mStateMonitor.waitForBootComplete(timeOut);
4078 }
4079
4080 /**
4081 * {@inheritDoc}
4082 */
4083 @Override
4084 public ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00004085 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00004086 }
4087
Rett Berge01ca472019-04-29 10:05:46 -07004088 /** {@inheritDoc} */
4089 @Override
4090 public Map<Integer, UserInfo> getUserInfos() throws DeviceNotAvailableException {
4091 throw new UnsupportedOperationException("No support for user's feature.");
4092 }
4093
Julien Desprez6961b272016-02-01 09:58:23 +00004094 /**
4095 * {@inheritDoc}
4096 */
4097 @Override
4098 public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00004099 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00004100 }
4101
Alex Chau93617352018-01-16 15:22:25 +00004102 @Override
4103 public int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException {
4104 throw new UnsupportedOperationException("No support for user's feature.");
4105 }
4106
Julien Desprez6961b272016-02-01 09:58:23 +00004107 /**
4108 * {@inheritDoc}
4109 */
4110 @Override
4111 public boolean isMultiUserSupported() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00004112 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00004113 }
4114
Rett Berg2b683362019-02-13 11:36:34 -08004115 /** {@inheritDoc} */
4116 @Override
4117 public int createUserNoThrow(String name) throws DeviceNotAvailableException {
4118 throw new UnsupportedOperationException("No support for user's feature.");
4119 }
4120
Julien Desprez6961b272016-02-01 09:58:23 +00004121 /**
4122 * {@inheritDoc}
4123 */
4124 @Override
4125 public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException {
Julien Desprezc8474552016-02-17 10:59:27 +00004126 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00004127 }
4128
4129 /**
4130 * {@inheritDoc}
4131 */
4132 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004133 public int createUser(String name, boolean guest, boolean ephemeral)
4134 throws DeviceNotAvailableException, IllegalStateException {
4135 throw new UnsupportedOperationException("No support for user's feature.");
4136 }
4137
4138 /**
4139 * {@inheritDoc}
4140 */
4141 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00004142 public boolean removeUser(int userId) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00004143 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00004144 }
4145
4146 /**
4147 * {@inheritDoc}
4148 */
4149 @Override
4150 public boolean startUser(int userId) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00004151 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00004152 }
4153
Bookatzfd6f6942019-03-05 14:29:52 -08004154 /** {@inheritDoc} */
4155 @Override
4156 public boolean startUser(int userId, boolean waitFlag) throws DeviceNotAvailableException {
4157 throw new UnsupportedOperationException("No support for user's feature.");
4158 }
4159
Julien Desprez6961b272016-02-01 09:58:23 +00004160 /**
4161 * {@inheritDoc}
4162 */
4163 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004164 public boolean stopUser(int userId) throws DeviceNotAvailableException {
4165 throw new UnsupportedOperationException("No support for user's feature.");
4166 }
4167
4168 /**
4169 * {@inheritDoc}
4170 */
4171 @Override
4172 public boolean stopUser(int userId, boolean waitFlag, boolean forceFlag)
4173 throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00004174 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00004175 }
4176
4177 /**
4178 * {@inheritDoc}
4179 */
4180 @Override
4181 public void remountSystemWritable() throws DeviceNotAvailableException {
4182 String verity = getProperty("partition.system.verified");
4183 // have the property set (regardless state) implies verity is enabled, so we send adb
4184 // command to disable verity
4185 if (verity != null && !verity.isEmpty()) {
4186 executeAdbCommand("disable-verity");
4187 reboot();
4188 }
4189 executeAdbCommand("remount");
4190 waitForDeviceAvailable();
4191 }
4192
YiHo Cheng973885b2019-09-18 06:55:51 +08004193 /** {@inheritDoc} */
4194 @Override
4195 public void remountVendorWritable() throws DeviceNotAvailableException {
4196 String verity = getProperty("partition.vendor.verified");
4197 // have the property set (regardless state) implies verity is enabled, so we send adb
4198 // command to disable verity
4199 if (verity != null && !verity.isEmpty()) {
4200 executeAdbCommand("disable-verity");
4201 reboot();
4202 }
4203 executeAdbCommand("remount");
4204 waitForDeviceAvailable();
4205 }
4206
Julien Desprez6961b272016-02-01 09:58:23 +00004207 /**
4208 * {@inheritDoc}
4209 */
4210 @Override
4211 public Integer getPrimaryUserId() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00004212 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00004213 }
4214
4215 /**
4216 * {@inheritDoc}
4217 */
4218 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004219 public int getCurrentUser() throws DeviceNotAvailableException {
4220 throw new UnsupportedOperationException("No support for user's feature.");
4221 }
4222
Rett Berg2b683362019-02-13 11:36:34 -08004223 /** {@inheritDoc} */
4224 @Override
4225 public boolean isUserSecondary(int userId) throws DeviceNotAvailableException {
4226 throw new UnsupportedOperationException("No support for user's feature.");
4227 }
4228
4229
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004230 /**
4231 * {@inheritDoc}
4232 */
4233 @Override
4234 public int getUserFlags(int userId) throws DeviceNotAvailableException {
4235 throw new UnsupportedOperationException("No support for user's feature.");
4236 }
4237
4238 /**
4239 * {@inheritDoc}
4240 */
4241 @Override
4242 public int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
4243 throw new UnsupportedOperationException("No support for user's feature.");
4244 }
4245
4246 /**
4247 * {@inheritDoc}
4248 */
4249 @Override
4250 public boolean switchUser(int userId) throws DeviceNotAvailableException {
4251 throw new UnsupportedOperationException("No support for user's feature.");
4252 }
4253
4254 /**
4255 * {@inheritDoc}
4256 */
4257 @Override
4258 public boolean switchUser(int userId, long timeout) throws DeviceNotAvailableException {
4259 throw new UnsupportedOperationException("No support for user's feature.");
4260 }
4261
4262 /**
4263 * {@inheritDoc}
4264 */
4265 @Override
4266 public boolean isUserRunning(int userId) throws DeviceNotAvailableException {
4267 throw new UnsupportedOperationException("No support for user's feature.");
4268 }
4269
4270 /**
4271 * {@inheritDoc}
4272 */
4273 @Override
4274 public boolean hasFeature(String feature) throws DeviceNotAvailableException {
4275 throw new UnsupportedOperationException("No support pm's features.");
4276 }
4277
4278 /**
4279 * {@inheritDoc}
4280 */
4281 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00004282 public String getSetting(String namespace, String key)
4283 throws DeviceNotAvailableException {
4284 throw new UnsupportedOperationException("No support for setting's feature.");
4285 }
4286
4287 /**
4288 * {@inheritDoc}
4289 */
4290 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004291 public String getSetting(int userId, String namespace, String key)
4292 throws DeviceNotAvailableException {
4293 throw new UnsupportedOperationException("No support for setting's feature.");
4294 }
4295
Yichun Lib1900022018-05-11 09:54:48 -07004296 /** {@inheritDoc} */
4297 @Override
4298 public Map<String, String> getAllSettings(String namespace) throws DeviceNotAvailableException {
4299 throw new UnsupportedOperationException("No support for setting's feature.");
4300 }
4301
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004302 /**
4303 * {@inheritDoc}
4304 */
4305 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00004306 public void setSetting(String namespace, String key, String value)
4307 throws DeviceNotAvailableException {
4308 throw new UnsupportedOperationException("No support for setting's feature.");
4309 }
4310
4311 /**
4312 * {@inheritDoc}
4313 */
4314 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004315 public void setSetting(int userId, String namespace, String key, String value)
4316 throws DeviceNotAvailableException {
4317 throw new UnsupportedOperationException("No support for setting's feature.");
4318 }
4319
4320 /**
4321 * {@inheritDoc}
4322 */
4323 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00004324 public String getBuildSigningKeys() throws DeviceNotAvailableException {
Julien Desprez52388172019-07-31 16:32:49 -07004325 String buildTags = getProperty(DeviceProperties.BUILD_TAGS);
Julien Desprez6961b272016-02-01 09:58:23 +00004326 if (buildTags != null) {
4327 String[] tags = buildTags.split(",");
4328 for (String tag : tags) {
4329 Matcher m = KEYS_PATTERN.matcher(tag);
4330 if (m.matches()) {
4331 return tag;
4332 }
4333 }
4334 }
4335 return null;
4336 }
4337
4338 /**
4339 * {@inheritDoc}
4340 */
4341 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004342 public String getAndroidId(int userId) throws DeviceNotAvailableException {
4343 throw new UnsupportedOperationException("No support for user's feature.");
4344 }
4345
4346 /**
4347 * {@inheritDoc}
4348 */
4349 @Override
4350 public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException {
4351 throw new UnsupportedOperationException("No support for user's feature.");
4352 }
4353
Tony Mak14e4dee2017-04-12 14:37:34 +01004354 /** {@inheritDoc} */
4355 @Override
4356 public boolean setDeviceOwner(String componentName, int userId)
4357 throws DeviceNotAvailableException {
4358 throw new UnsupportedOperationException("No support for user's feature.");
4359 }
4360
4361 /** {@inheritDoc} */
4362 @Override
4363 public boolean removeAdmin(String componentName, int userId)
4364 throws DeviceNotAvailableException {
4365 throw new UnsupportedOperationException("No support for user's feature.");
4366 }
4367
4368 /** {@inheritDoc} */
4369 @Override
4370 public void removeOwners() throws DeviceNotAvailableException {
4371 throw new UnsupportedOperationException("No support for user's feature.");
4372 }
4373
Guang Zhu03c985e2017-04-28 14:53:55 -07004374 /**
4375 * {@inheritDoc}
4376 */
4377 @Override
4378 public void disableKeyguard() throws DeviceNotAvailableException {
4379 throw new UnsupportedOperationException("No support for Window Manager's features");
4380 }
4381
Tony Mak14e4dee2017-04-12 14:37:34 +01004382 /** {@inheritDoc} */
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004383 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00004384 public String getDeviceClass() {
4385 IDevice device = getIDevice();
4386 if (device == null) {
4387 CLog.w("No IDevice instance, cannot determine device class.");
4388 return "";
4389 }
4390 return device.getClass().getSimpleName();
4391 }
Julien Desprez26bee8d2016-03-29 12:09:48 +01004392
Julien Desprezcb444a92019-06-10 14:17:39 -07004393 /** {@inheritDoc} */
Julien Desprez26bee8d2016-03-29 12:09:48 +01004394 @Override
Julien Desprezcb444a92019-06-10 14:17:39 -07004395 public void preInvocationSetup(IBuildInfo info, List<IBuildInfo> testResourceBuildInfos)
Julien Desprez26bee8d2016-03-29 12:09:48 +01004396 throws TargetSetupError, DeviceNotAvailableException {
Julien Desprezfd2aac02019-04-12 17:49:29 -07004397 // Default implementation
4398 mContentProvider = null;
4399 mShouldSkipContentProviderSetup = false;
Julien Desprez78de5532019-05-17 10:46:27 -07004400 try {
4401 mExecuteShellCommandLogs =
4402 FileUtil.createTempFile("TestDevice_ExecuteShellCommands", ".txt");
4403 } catch (IOException e) {
4404 throw new TargetSetupError(
4405 "Failed to create the executeShellCommand log file.", e, getDeviceDescriptor());
4406 }
Julien Desprez26bee8d2016-03-29 12:09:48 +01004407 }
4408
Julien Desprez195f1db2019-07-15 09:33:48 -07004409 /** {@inheritDoc} */
Julien Desprez26bee8d2016-03-29 12:09:48 +01004410 @Override
Julien Desprez195f1db2019-07-15 09:33:48 -07004411 public void postInvocationTearDown(Throwable exception) {
Julien Desprezf10e2052019-05-28 09:09:49 -07004412 mIsEncryptionSupported = null;
Julien Desprez78de5532019-05-17 10:46:27 -07004413 FileUtil.deleteFile(mExecuteShellCommandLogs);
4414 mExecuteShellCommandLogs = null;
Julien Desprez1dc6b392019-03-18 11:49:13 -07004415 // Default implementation
4416 if (getIDevice() instanceof StubDevice) {
4417 return;
4418 }
Julien Desprez3cca2bc2019-04-05 17:15:16 -07004419 // Reset the Content Provider bit.
4420 mShouldSkipContentProviderSetup = false;
Julien Desprez1dc6b392019-03-18 11:49:13 -07004421 try {
Julien Desprezd7ad0d52019-03-28 10:57:55 -07004422 // If we never installed it, don't even bother checking for it during tear down.
4423 if (mContentProvider == null) {
4424 return;
4425 }
Julien Desprez195f1db2019-07-15 09:33:48 -07004426 if (exception instanceof DeviceNotAvailableException) {
4427 CLog.e(
4428 "Skip Tradefed Content Provider teardown due to DeviceNotAvailableException.");
4429 return;
4430 }
Julien Desprez7aad51b2019-04-18 09:15:19 -07004431 if (TestDeviceState.ONLINE.equals(getDeviceState())) {
4432 mContentProvider.tearDown();
4433 }
Julien Desprez1dc6b392019-03-18 11:49:13 -07004434 } catch (DeviceNotAvailableException e) {
4435 CLog.e(e);
4436 }
Julien Desprez26bee8d2016-03-29 12:09:48 +01004437 }
Julien Despreze824e8b2016-06-08 17:21:58 +01004438
4439 /**
4440 * {@inheritDoc}
4441 */
4442 @Override
4443 public boolean isHeadless() throws DeviceNotAvailableException {
Julien Desprez52388172019-07-31 16:32:49 -07004444 if (getProperty(DeviceProperties.BUILD_HEADLESS) != null) {
Julien Despreze824e8b2016-06-08 17:21:58 +01004445 return true;
4446 }
4447 return false;
4448 }
Julien Desprez16184162016-06-10 08:56:17 +01004449
4450 protected void checkApiLevelAgainst(String feature, int strictMinLevel) {
4451 try {
4452 if (getApiLevel() < strictMinLevel){
4453 throw new IllegalArgumentException(String.format("%s not supported on %s. "
4454 + "Must be API %d.", feature, getSerialNumber(), strictMinLevel));
4455 }
4456 } catch (DeviceNotAvailableException e) {
4457 throw new RuntimeException("Device became unavailable while checking API level", e);
4458 }
4459 }
Julien Desprez07fe1532016-08-12 11:48:32 +01004460
Julien Desprez58e9d972020-03-17 11:57:14 -07004461 /** {@inheritDoc} */
4462 @Override
4463 public DeviceDescriptor getCachedDeviceDescriptor() {
4464 synchronized (mCacheLock) {
4465 if (DeviceAllocationState.Allocated.equals(getAllocationState())) {
4466 if (mCachedDeviceDescriptor == null) {
4467 // Create the cache the very first time when it's allocated.
4468 mCachedDeviceDescriptor = getDeviceDescriptor();
4469 return mCachedDeviceDescriptor;
4470 }
4471 return mCachedDeviceDescriptor;
4472 }
4473 // If device is not allocated, just return current information
4474 mCachedDeviceDescriptor = null;
4475 return getDeviceDescriptor();
4476 }
4477 }
4478
Julien Desprez07fe1532016-08-12 11:48:32 +01004479 /**
4480 * {@inheritDoc}
4481 */
4482 @Override
4483 public DeviceDescriptor getDeviceDescriptor() {
4484 IDeviceSelection selector = new DeviceSelectionOptions();
4485 IDevice idevice = getIDevice();
Julien Desprezc98669f2018-11-19 14:05:59 -08004486 try {
Julien Desprez057d0c42019-01-18 11:48:22 -08004487 boolean isTemporary = false;
4488 if (idevice instanceof NullDevice) {
4489 isTemporary = ((NullDevice) idevice).isTemporary();
4490 }
Julien Desprezb18fb8a2020-01-14 13:29:11 -08004491 // All the operations to create the descriptor need to be safe (should not trigger any
4492 // device side effects like recovery)
Julien Desprezc98669f2018-11-19 14:05:59 -08004493 return new DeviceDescriptor(
4494 idevice.getSerialNumber(),
Julien Desprezb9efa742019-11-20 15:58:51 -08004495 null,
Julien Desprezc98669f2018-11-19 14:05:59 -08004496 idevice instanceof StubDevice,
4497 idevice.getState(),
4498 getAllocationState(),
4499 getDisplayString(selector.getDeviceProductType(idevice)),
4500 getDisplayString(selector.getDeviceProductVariant(idevice)),
Julien Desprez52388172019-07-31 16:32:49 -07004501 getDisplayString(idevice.getProperty(DeviceProperties.SDK_VERSION)),
4502 getDisplayString(idevice.getProperty(DeviceProperties.BUILD_ALIAS)),
Julien Desprez6f028672018-11-29 14:41:01 -08004503 getDisplayString(getBattery()),
Julien Desprezc98669f2018-11-19 14:05:59 -08004504 getDeviceClass(),
4505 getDisplayString(getMacAddress()),
4506 getDisplayString(getSimState()),
4507 getDisplayString(getSimOperator()),
Julien Desprez057d0c42019-01-18 11:48:22 -08004508 isTemporary,
Julien Desprezc98669f2018-11-19 14:05:59 -08004509 idevice);
4510 } catch (RuntimeException e) {
4511 CLog.e("Exception while building device '%s' description:", getSerialNumber());
4512 CLog.e(e);
4513 }
4514 return null;
Julien Desprez07fe1532016-08-12 11:48:32 +01004515 }
4516
4517 /**
4518 * Return the displayable string for given object
4519 */
4520 private String getDisplayString(Object o) {
4521 return o == null ? "unknown" : o.toString();
4522 }
Gopinath65e837a2016-10-06 17:55:12 -07004523
Betty Zhou8fd29192019-05-30 10:52:32 -07004524 /** {@inheritDoc} */
Gopinath65e837a2016-10-06 17:55:12 -07004525 @Override
4526 public ProcessInfo getProcessByName(String processName) throws DeviceNotAvailableException {
Betty Zhou8fd29192019-05-30 10:52:32 -07004527 String pidString = getProcessPid(processName);
4528 if (pidString == null) {
4529 return null;
4530 }
Betty Zhou35804ce2019-08-01 10:52:55 -07004531 long startTime = getProcessStartTimeByPid(pidString);
4532 if (startTime == -1L) {
4533 return null;
4534 }
Betty Zhou8fd29192019-05-30 10:52:32 -07004535 return new ProcessInfo(
4536 getProcessUserByPid(pidString),
4537 Integer.parseInt(pidString),
4538 processName,
Betty Zhou35804ce2019-08-01 10:52:55 -07004539 startTime);
Betty Zhou8fd29192019-05-30 10:52:32 -07004540 }
4541
4542 /** Return the process start time since epoch for the given pid string */
4543 private long getProcessStartTimeByPid(String pidString) throws DeviceNotAvailableException {
Julien Desprez509c1872019-12-20 10:43:48 -08004544 String output = executeShellCommand(String.format("ps -p %s -o stime=", pidString));
Betty Zhou8fd29192019-05-30 10:52:32 -07004545 if (output != null && !output.trim().isEmpty()) {
Julien Desprez509c1872019-12-20 10:43:48 -08004546 output = output.trim();
4547 String dateInSecond = executeShellCommand("date -d\"" + output + "\" +%s");
4548 if (Strings.isNullOrEmpty(dateInSecond)) {
4549 return -1L;
4550 }
Betty Zhou8fd29192019-05-30 10:52:32 -07004551 try {
Julien Desprez509c1872019-12-20 10:43:48 -08004552 return Long.parseLong(dateInSecond.trim());
Betty Zhou8fd29192019-05-30 10:52:32 -07004553 } catch (NumberFormatException e) {
Julien Desprez509c1872019-12-20 10:43:48 -08004554 CLog.e("Failed to parse the start time for process:");
4555 CLog.e(e);
Betty Zhou8fd29192019-05-30 10:52:32 -07004556 return -1L;
4557 }
4558 }
4559 return -1L;
4560 }
4561
4562 /** Return the process user for the given pid string */
4563 private String getProcessUserByPid(String pidString) throws DeviceNotAvailableException {
4564 String output = executeShellCommand("stat -c%U /proc/" + pidString);
4565 if (output != null && !output.trim().isEmpty()) {
4566 try {
4567 return output.trim();
4568 } catch (NumberFormatException e) {
4569 return null;
Gopinath65e837a2016-10-06 17:55:12 -07004570 }
4571 }
4572 return null;
4573 }
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004574
Betty Zhou8fd29192019-05-30 10:52:32 -07004575 /** {@inheritDoc} */
4576 @Override
4577 public Map<Long, String> getBootHistory() throws DeviceNotAvailableException {
Julien Desprez810bcfd2019-09-05 08:50:56 -07004578 String output = getProperty(DeviceProperties.BOOT_REASON_HISTORY);
Betty Zhou8fd29192019-05-30 10:52:32 -07004579 /* Sample output:
4580 kernel_panic,1556587278
4581 reboot,,1556238008
4582 reboot,,1556237796
4583 reboot,,1556237725
4584 */
4585 Map<Long, String> bootHistory = new LinkedHashMap<Long, String>();
4586 if (Strings.isNullOrEmpty(output)) {
4587 return bootHistory;
4588 }
4589 for (String line : output.split("\\n")) {
4590 String infoStr[] = line.split(",");
4591 String startStr = infoStr[infoStr.length - 1];
4592 try {
4593 long startTime = Long.parseLong(startStr.trim());
4594 bootHistory.put(startTime, infoStr[0].trim());
4595 } catch (NumberFormatException e) {
4596 CLog.e("Fail to parse boot time from line %s", line);
4597 }
4598 }
4599 return bootHistory;
4600 }
4601
4602 /** {@inheritDoc} */
4603 @Override
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004604 public Map<Long, String> getBootHistorySince(long utcEpochTime, TimeUnit timeUnit)
Betty Zhou8fd29192019-05-30 10:52:32 -07004605 throws DeviceNotAvailableException {
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004606 long utcEpochTimeSec = TimeUnit.SECONDS.convert(utcEpochTime, timeUnit);
Betty Zhou8fd29192019-05-30 10:52:32 -07004607 Map<Long, String> bootHistory = new LinkedHashMap<Long, String>();
4608 for (Map.Entry<Long, String> entry : getBootHistory().entrySet()) {
Julien Desprezf8a664f2020-01-22 14:27:55 -08004609 if (entry.getKey() >= utcEpochTimeSec) {
Betty Zhou8fd29192019-05-30 10:52:32 -07004610 bootHistory.put(entry.getKey(), entry.getValue());
4611 }
4612 }
4613 return bootHistory;
4614 }
4615
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004616 private boolean hasNormalRebootSince(long utcEpochTime, TimeUnit timeUnit)
4617 throws DeviceNotAvailableException {
4618 Map<Long, String> bootHistory = getBootHistorySince(utcEpochTime, timeUnit);
4619 if (bootHistory.isEmpty()) {
4620 CLog.w("There is no reboot history since %s", utcEpochTime);
4621 return false;
4622 }
4623
4624 CLog.i(
4625 "There are new boot history since %d. NewBootHistory = %s",
4626 utcEpochTime, bootHistory);
4627 // Check if there is reboot reason other than "reboot".
4628 // Raise RuntimeException if there is abnormal reboot.
4629 for (Map.Entry<Long, String> entry : bootHistory.entrySet()) {
4630 if (!"reboot".equals(entry.getValue())) {
4631 throw new RuntimeException(
4632 String.format(
4633 "Device %s has abnormal reboot reason %s at %d",
4634 getSerialNumber(), entry.getValue(), entry.getKey()));
4635 }
4636 }
4637 return true;
4638 }
4639
Betty Zhou35804ce2019-08-01 10:52:55 -07004640 /**
4641 * Check current system process is restarted after last reboot
4642 *
Julien Desprez810bcfd2019-09-05 08:50:56 -07004643 * @param systemServerProcess the system_server {@link ProcessInfo}
Betty Zhou35804ce2019-08-01 10:52:55 -07004644 * @return true if system_server process restarted after last reboot; false if not
4645 * @throws DeviceNotAvailableException
4646 */
4647 private boolean checkSystemProcessRestartedAfterLastReboot(ProcessInfo systemServerProcess)
4648 throws DeviceNotAvailableException {
4649 // If time gap from last reboot to current system_server process start time is more than
4650 // MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP seconds, we conclude the system_server restarted
4651 // after boot up.
4652 if (!hasNormalRebootSince(
4653 systemServerProcess.getStartTime() - MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC,
4654 TimeUnit.SECONDS)) {
4655 CLog.i(
4656 "Device last reboot is more than %s seconds away from current system_server "
4657 + "process start time. The system_server process restarted after "
4658 + "last boot up",
4659 MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC);
4660 return true;
4661 } else {
4662 // Current system_server start within MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP
4663 // seconds after device last boot up
4664 return false;
4665 }
4666 }
4667
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004668 /** {@inheritDoc} */
4669 @Override
4670 public boolean deviceSoftRestartedSince(long utcEpochTime, TimeUnit timeUnit)
4671 throws DeviceNotAvailableException {
4672 ProcessInfo currSystemServerProcess = getProcessByName("system_server");
4673 if (currSystemServerProcess == null) {
4674 CLog.i("The system_server process is not available on the device.");
4675 return true;
4676 }
4677
4678 // The system_server process started at or before utcEpochTime, there is no soft-restart
Julien Desprez6be1d372020-03-11 15:34:45 -07004679 if (Math.abs(
4680 currSystemServerProcess.getStartTime()
4681 - TimeUnit.SECONDS.convert(utcEpochTime, timeUnit))
4682 <= 1) {
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004683 return false;
4684 }
4685
4686 // The system_server process restarted after device utcEpochTime in second.
4687 // Check if there is new reboot history, if no new reboot, device soft-restarted.
Betty Zhou35804ce2019-08-01 10:52:55 -07004688 // If there is no normal reboot, soft-restart is detected.
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004689 if (!hasNormalRebootSince(utcEpochTime, timeUnit)) {
4690 return true;
4691 }
4692
Betty Zhou35804ce2019-08-01 10:52:55 -07004693 // There is new reboot since utcEpochTime. Check if system_server restarted after boot up.
4694 return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess);
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004695 }
4696
4697 /** {@inheritDoc} */
4698 @Override
4699 public boolean deviceSoftRestarted(ProcessInfo prevSystemServerProcess)
4700 throws DeviceNotAvailableException {
Betty Zhou35804ce2019-08-01 10:52:55 -07004701 if (prevSystemServerProcess == null) {
4702 CLog.i("The given system_server process is null. Abort deviceSoftRestarted check.");
4703 return false;
4704 }
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004705 ProcessInfo currSystemServerProcess = getProcessByName("system_server");
4706 if (currSystemServerProcess == null) {
4707 CLog.i("The system_server process is not available on the device.");
4708 return true;
4709 }
4710
Julien Desprez16851ab2020-01-10 08:26:05 -08004711 // Compare the start time with a 1 seconds accuracy due to how the date is computed
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004712 if (currSystemServerProcess.getPid() == prevSystemServerProcess.getPid()
Julien Desprez16851ab2020-01-10 08:26:05 -08004713 && Math.abs(
4714 currSystemServerProcess.getStartTime()
4715 - prevSystemServerProcess.getStartTime())
4716 <= 1) {
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004717 return false;
4718 }
4719
frankfengba7436c2020-03-23 13:59:25 -07004720 CLog.v(
4721 "current system_server: %s; prev system_server: %s",
4722 currSystemServerProcess, prevSystemServerProcess);
4723
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004724 // The system_server process restarted.
4725 // Check boot history with previous system_server start time.
Betty Zhou35804ce2019-08-01 10:52:55 -07004726 // If there is no normal reboot, soft-restart is detected
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004727 if (!hasNormalRebootSince(prevSystemServerProcess.getStartTime(), TimeUnit.SECONDS)) {
4728 return true;
4729 }
Betty Zhou35804ce2019-08-01 10:52:55 -07004730
4731 // There is reboot since prevSystemServerProcess.getStartTime().
4732 // Check if system_server restarted after boot up.
4733 return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess);
4734
Betty Zhoua9ad95c2019-07-15 18:21:05 -07004735 }
4736
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004737 /**
4738 * Validates that the given input is a valid MAC address
4739 *
4740 * @param address input to validate
4741 * @return true if the input is a valid MAC address
4742 */
4743 boolean isMacAddress(String address) {
4744 Pattern macPattern = Pattern.compile(MAC_ADDRESS_PATTERN);
4745 Matcher macMatcher = macPattern.matcher(address);
4746 return macMatcher.find();
4747 }
4748
4749 /**
4750 * {@inheritDoc}
4751 */
4752 @Override
4753 public String getMacAddress() {
Julien Desprez30715d62018-10-12 12:01:53 -07004754 if (getIDevice() instanceof StubDevice) {
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004755 // Do not query MAC addresses from stub devices.
4756 return null;
4757 }
Kevin Lau Fangfeff6d62016-12-14 17:29:18 -08004758 if (!TestDeviceState.ONLINE.equals(mState)) {
4759 // Only query MAC addresses from online devices.
4760 return null;
4761 }
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004762 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
4763 try {
4764 mIDevice.executeShellCommand(MAC_ADDRESS_COMMAND, receiver);
4765 } catch (IOException | TimeoutException | AdbCommandRejectedException |
4766 ShellCommandUnresponsiveException e) {
4767 CLog.w("Failed to query MAC address for %s", mIDevice.getSerialNumber());
Kevin Lau Fange94d8972017-03-13 15:11:30 -07004768 CLog.w(e);
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004769 }
4770 String output = receiver.getOutput().trim();
4771 if (isMacAddress(output)) {
4772 return output;
4773 }
4774 CLog.d("No valid MAC address queried from device %s", mIDevice.getSerialNumber());
4775 return null;
4776 }
jdesprezbc580f92017-06-02 11:41:40 -07004777
4778 /** {@inheritDoc} */
4779 @Override
4780 public String getSimState() {
Julien Desprezb18fb8a2020-01-14 13:29:11 -08004781 // Use ddmlib getProperty directly to avoid possible recovery path
4782 return getIDevice().getProperty(SIM_STATE_PROP);
jdesprezbc580f92017-06-02 11:41:40 -07004783 }
4784
4785 /** {@inheritDoc} */
4786 @Override
4787 public String getSimOperator() {
Julien Desprezb18fb8a2020-01-14 13:29:11 -08004788 // Use ddmlib getProperty directly to avoid possible recovery path
4789 return getIDevice().getProperty(SIM_OPERATOR_PROP);
jdesprezbc580f92017-06-02 11:41:40 -07004790 }
jdesprezd7670322017-08-30 14:12:28 -07004791
jdesprez1fbb2472018-03-07 11:15:38 -08004792 /** {@inheritDoc} */
jdesprezd7670322017-08-30 14:12:28 -07004793 @Override
4794 public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException {
4795 throw new UnsupportedOperationException("dumpHeap is not supported.");
4796 }
4797
jdesprez1fbb2472018-03-07 11:15:38 -08004798 /** {@inheritDoc} */
jdesprezd7670322017-08-30 14:12:28 -07004799 @Override
4800 public String getProcessPid(String process) throws DeviceNotAvailableException {
4801 String output = executeShellCommand(String.format("pidof %s", process)).trim();
4802 if (checkValidPid(output)) {
4803 return output;
4804 }
4805 CLog.e("Failed to find a valid pid for process.");
4806 return null;
4807 }
4808
jdesprez1fbb2472018-03-07 11:15:38 -08004809 /** {@inheritDoc} */
4810 @Override
4811 public void logOnDevice(String tag, LogLevel level, String format, Object... args) {
4812 String message = String.format(format, args);
4813 try {
4814 String levelLetter = logLevelToLogcatLevel(level);
4815 String command = String.format("log -t %s -p %s '%s'", tag, levelLetter, message);
4816 executeShellCommand(command);
4817 } catch (DeviceNotAvailableException e) {
4818 CLog.e("Device went not available when attempting to log '%s'", message);
4819 CLog.e(e);
4820 }
4821 }
4822
4823 /** Convert the {@link LogLevel} to the letter used in log (see 'adb shell log --help'). */
4824 private String logLevelToLogcatLevel(LogLevel level) {
4825 switch (level) {
4826 case DEBUG:
4827 return "d";
4828 case ERROR:
4829 return "e";
4830 case INFO:
4831 return "i";
4832 case VERBOSE:
4833 return "v";
4834 case WARN:
4835 return "w";
4836 default:
4837 return "i";
4838 }
4839 }
4840
Yuji Hachiya7bcf9ca2018-07-16 18:27:49 -07004841 /** {@inheritDoc} */
4842 @Override
4843 public long getTotalMemory() {
4844 // "/proc/meminfo" always returns value in kilobytes.
4845 long totalMemory = 0;
4846 String output = null;
4847 try {
4848 output = executeShellCommand("cat /proc/meminfo | grep MemTotal");
4849 } catch (DeviceNotAvailableException e) {
4850 CLog.e(e);
4851 return -1;
4852 }
4853 if (output.isEmpty()) {
4854 return -1;
4855 }
4856 String[] results = output.split("\\s+");
4857 try {
4858 totalMemory = Long.parseLong(results[1].replaceAll("\\D+", ""));
4859 } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
4860 CLog.e(e);
4861 return -1;
4862 }
4863 return totalMemory * 1024;
4864 }
4865
Julien Desprezb0e88012018-11-19 11:49:48 -08004866 /** {@inheritDoc} */
4867 @Override
4868 public Integer getBattery() {
4869 if (getIDevice() instanceof StubDevice) {
4870 return null;
4871 }
Julien Desprez47b0a542020-04-23 09:40:17 -07004872 if (isStateBootloaderOrFastbootd()) {
Julien Desprez395e3b42020-01-24 14:30:38 -08004873 return null;
4874 }
Julien Desprezb0e88012018-11-19 11:49:48 -08004875 try {
4876 // Use default 5 minutes freshness
4877 Future<Integer> batteryFuture = getIDevice().getBattery();
4878 // Get cached value or wait up to 500ms for battery level query
4879 return batteryFuture.get(500, TimeUnit.MILLISECONDS);
4880 } catch (InterruptedException
4881 | ExecutionException
4882 | java.util.concurrent.TimeoutException e) {
4883 CLog.w(
4884 "Failed to query battery level for %s: %s",
4885 getIDevice().getSerialNumber(), e.toString());
4886 }
4887 return null;
4888 }
4889
Julien Desprez467a41c2019-03-26 09:04:40 -07004890 /** {@inheritDoc} */
4891 @Override
Julien Desprez1aacf862019-11-04 09:56:25 -08004892 public Set<Long> listDisplayIds() throws DeviceNotAvailableException {
Julien Desprez467a41c2019-03-26 09:04:40 -07004893 throw new UnsupportedOperationException("dumpsys SurfaceFlinger is not supported.");
4894 }
4895
Julien Despreza5397f12019-03-28 09:46:16 -07004896 /** {@inheritDoc} */
4897 @Override
4898 public long getLastExpectedRebootTimeMillis() {
4899 return mLastTradefedRebootTime;
4900 }
4901
Julien Desprezb48b9e22019-05-16 18:52:22 -07004902 /** {@inheritDoc} */
4903 @Override
4904 public List<File> getTombstones() throws DeviceNotAvailableException {
4905 List<File> tombstones = new ArrayList<>();
4906 if (!isAdbRoot()) {
4907 CLog.w("Device was not root, cannot collect tombstones.");
4908 return tombstones;
4909 }
4910 for (String tombName : getChildren(TOMBSTONE_PATH)) {
4911 File tombFile = pullFile(TOMBSTONE_PATH + tombName);
4912 if (tombFile != null) {
4913 tombstones.add(tombFile);
4914 }
4915 }
4916 return tombstones;
4917 }
4918
jdesprezd7670322017-08-30 14:12:28 -07004919 /** Validate that pid is an integer and not empty. */
4920 private boolean checkValidPid(String output) {
4921 if (output.isEmpty()) {
4922 return false;
4923 }
4924 try {
4925 Integer.parseInt(output);
4926 } catch (NumberFormatException e) {
4927 CLog.e(e);
4928 return false;
4929 }
4930 return true;
4931 }
Guang Zhu26cca482017-11-01 18:18:08 -07004932
Jeffrey Lu279122f2018-01-29 17:25:08 -08004933 /** Gets the {@link IHostOptions} instance to use. */
4934 @VisibleForTesting
Guang Zhu26cca482017-11-01 18:18:08 -07004935 IHostOptions getHostOptions() {
4936 return GlobalConfiguration.getInstance().getHostOptions();
4937 }
Julien Desprez1dc6b392019-03-18 11:49:13 -07004938
4939 /** Returns the {@link ContentProviderHandler} or null if not available. */
4940 @VisibleForTesting
4941 ContentProviderHandler getContentProvider() throws DeviceNotAvailableException {
Julien Desprez13399b92019-04-05 12:31:18 -07004942 // If disabled at the device level, don't attempt any checks.
4943 if (!getOptions().shouldUseContentProvider()) {
4944 return null;
4945 }
Julien Desprezddc36132019-04-11 08:30:22 -07004946 // Prevent usage of content provider before API 28 as it would not work well since content
4947 // tool is not working before P.
4948 if (getApiLevel() < 28) {
Julien Desprez2bfb8422019-03-28 08:28:58 -07004949 return null;
4950 }
Julien Desprez1dc6b392019-03-18 11:49:13 -07004951 if (mContentProvider == null) {
4952 mContentProvider = new ContentProviderHandler(this);
4953 }
Julien Desprezad23b602019-11-21 14:12:00 -08004954 // Force the install if we saw an error with content provider installation.
4955 if (mContentProvider.contentProviderNotFound()) {
4956 mShouldSkipContentProviderSetup = false;
4957 }
Julien Desprez1dc6b392019-03-18 11:49:13 -07004958 if (!mShouldSkipContentProviderSetup) {
4959 boolean res = mContentProvider.setUp();
4960 if (!res) {
jovanakf39a78c2019-04-02 17:08:43 -07004961 // TODO: once CP becomes a requirement, throw/fail the test if CP can't be found
Julien Desprez1dc6b392019-03-18 11:49:13 -07004962 return null;
4963 }
jovanakf39a78c2019-04-02 17:08:43 -07004964 mShouldSkipContentProviderSetup = true;
Julien Desprez1dc6b392019-03-18 11:49:13 -07004965 }
4966 return mContentProvider;
4967 }
Julien Desprez0b44ad32019-05-15 19:02:45 -07004968
4969 /** Reset the flag for content provider setup in order to trigger it again. */
4970 void resetContentProviderSetup() {
4971 mShouldSkipContentProviderSetup = false;
4972 }
Julien Desprez78de5532019-05-17 10:46:27 -07004973
4974 /** The log that contains all the {@link #executeShellCommand(String)} logs. */
4975 public final File getExecuteShellCommandLog() {
4976 return mExecuteShellCommandLogs;
4977 }
Julien Desprez6961b272016-02-01 09:58:23 +00004978}