blob: 43faf5c1a3ffd368b6b08d91764deeffa7a9f41d [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;
32import com.android.ddmlib.testrunner.ITestRunListener;
33import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
34import 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;
48import com.android.tradefed.result.StubTestRunListener;
jdesprez4de2f552018-02-02 14:38:13 -080049import 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;
59import com.android.tradefed.util.PsParser;
jdesprez3d7b9142018-03-02 12:11:23 -080060import com.android.tradefed.util.QuotationAwareTokenizer;
Julien Desprez6961b272016-02-01 09:58:23 +000061import com.android.tradefed.util.RunUtil;
62import com.android.tradefed.util.SizeLimitedOutputStream;
Julien Desprez0c836c92016-08-10 14:40:41 +010063import com.android.tradefed.util.StreamUtil;
Julien Desprezed378b92019-04-19 12:31:57 -070064import com.android.tradefed.util.StringEscapeUtils;
Julien Desprez96c00252018-06-06 03:25:40 -070065import com.android.tradefed.util.ZipUtil;
Guang Zhu91fdf442017-01-03 17:59:55 -080066import com.android.tradefed.util.ZipUtil2;
Julien Desprez6961b272016-02-01 09:58:23 +000067
Jeffrey Lu279122f2018-01-29 17:25:08 -080068import com.google.common.annotations.VisibleForTesting;
Guang Zhuc0a99082019-02-27 15:21:27 -080069import com.google.common.base.Strings;
Jeffrey Lu279122f2018-01-29 17:25:08 -080070
Guang Zhu91fdf442017-01-03 17:59:55 -080071import org.apache.commons.compress.archivers.zip.ZipFile;
72
Julien Desprez6961b272016-02-01 09:58:23 +000073import java.io.File;
74import java.io.FilenameFilter;
75import java.io.IOException;
jovanakf39a78c2019-04-02 17:08:43 -070076import java.io.OutputStream;
Julien Desprez6961b272016-02-01 09:58:23 +000077import java.text.ParseException;
78import java.text.SimpleDateFormat;
Jeffrey Lu279122f2018-01-29 17:25:08 -080079import java.time.Clock;
Julien Desprez6961b272016-02-01 09:58:23 +000080import java.util.ArrayList;
81import java.util.Arrays;
82import java.util.Collection;
83import java.util.Date;
Julien Desprez69324072018-08-14 14:08:47 -070084import java.util.HashSet;
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
97import javax.annotation.concurrent.GuardedBy;
Julien Desprez6961b272016-02-01 09:58:23 +000098
99/**
100 * Default implementation of a {@link ITestDevice}
101 * Non-full stack android devices.
102 */
Julien Desprez2f34e382016-06-21 12:30:39 +0100103public class NativeDevice implements IManagedTestDevice {
Julien Desprez6961b272016-02-01 09:58:23 +0000104
Julien Desprez1dc6b392019-03-18 11:49:13 -0700105 private static final String SD_CARD = "/sdcard/";
Julien Desprez16184162016-06-10 08:56:17 +0100106 /**
107 * Allow pauses of up to 2 minutes while receiving bugreport.
108 * <p/>
109 * Note that dumpsys may pause up to a minute while waiting for unresponsive components.
110 * It still should bail after that minute, if it will ever terminate on its own.
111 */
112 private static final int BUGREPORT_TIMEOUT = 2 * 60 * 1000;
Julien Desprezf54735c2016-08-16 09:17:07 +0100113 /**
114 * Allow a little more time for bugreportz because there are extra steps.
115 */
116 private static final int BUGREPORTZ_TIMEOUT = 5 * 60 * 1000;
Julien Desprez16184162016-06-10 08:56:17 +0100117 private static final String BUGREPORT_CMD = "bugreport";
118 private static final String BUGREPORTZ_CMD = "bugreportz";
Nick Kralevich5487ad22016-11-19 12:16:15 -0800119 private static final String BUGREPORTZ_TMP_PATH = "/bugreports/";
Julien Desprez16184162016-06-10 08:56:17 +0100120
Julien Desprez73c55bf2016-09-01 09:27:37 +0100121 /**
122 * Allow up to 2 minutes to receives the full logcat dump.
123 */
124 private static final int LOGCAT_DUMP_TIMEOUT = 2 * 60 * 1000;
125
Julien Desprez6961b272016-02-01 09:58:23 +0000126 /** the default number of command retry attempts to perform */
Julien Desprezc8474552016-02-17 10:59:27 +0000127 protected static final int MAX_RETRY_ATTEMPTS = 2;
128
Julien Desprez3172c9b2019-04-24 15:58:04 -0700129 /** Value returned for any invalid/not found user id: UserHandle defined the -10000 value */
130 public static final int INVALID_USER_ID = -10000;
Julien Desprez4c8aabc2016-03-14 15:53:48 +0000131
Julien Desprez6961b272016-02-01 09:58:23 +0000132 /** regex to match input dispatch readiness line **/
133 static final Pattern INPUT_DISPATCH_STATE_REGEX =
134 Pattern.compile("DispatchEnabled:\\s?([01])");
135 /** regex to match build signing key type */
136 private static final Pattern KEYS_PATTERN = Pattern.compile("^.*-keys$");
137 private static final Pattern DF_PATTERN = Pattern.compile(
138 //Fs 1K-blks Used Available Use% Mounted on
Julien Desprez3d8c1472016-09-19 11:18:09 +0100139 "^/\\S+\\s+\\d+\\s+\\d+\\s+(\\d+)\\s+\\d+%\\s+/\\S*$", Pattern.MULTILINE);
Julien Desprez16184162016-06-10 08:56:17 +0100140 private static final Pattern BUGREPORTZ_RESPONSE_PATTERN = Pattern.compile("(OK:)(.*)");
Julien Desprez6961b272016-02-01 09:58:23 +0000141
Julien Desprez1fadf1a2016-10-20 15:50:46 +0100142 protected static final long MAX_HOST_DEVICE_TIME_OFFSET = 5 * 1000;
Julien Desprez6961b272016-02-01 09:58:23 +0000143
144 /** The password for encrypting and decrypting the device. */
145 private static final String ENCRYPTION_PASSWORD = "android";
146 /** Encrypting with inplace can take up to 2 hours. */
147 private static final int ENCRYPTION_INPLACE_TIMEOUT_MIN = 2 * 60;
148 /** Encrypting with wipe can take up to 20 minutes. */
149 private static final long ENCRYPTION_WIPE_TIMEOUT_MIN = 20;
Julien Desprez6961b272016-02-01 09:58:23 +0000150
151 /** The time in ms to wait before starting logcat for a device */
152 private int mLogStartDelay = 5*1000;
153
154 /** The time in ms to wait for a device to become unavailable. Should usually be short */
155 private static final int DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000;
156 /** The time in ms to wait for a recovery that we skip because of the NONE mode */
157 static final int NONE_RECOVERY_MODE_DELAY = 1000;
Julien Desprez6961b272016-02-01 09:58:23 +0000158
159 static final String BUILD_ID_PROP = "ro.build.version.incremental";
160 private static final String PRODUCT_NAME_PROP = "ro.product.name";
161 private static final String BUILD_TYPE_PROP = "ro.build.type";
162 private static final String BUILD_ALIAS_PROP = "ro.build.id";
163 private static final String BUILD_FLAVOR = "ro.build.flavor";
Julien Despreze824e8b2016-06-08 17:21:58 +0100164 private static final String HEADLESS_PROP = "ro.build.headless";
Julien Desprez6961b272016-02-01 09:58:23 +0000165 static final String BUILD_CODENAME_PROP = "ro.build.version.codename";
166 static final String BUILD_TAGS = "ro.build.tags";
Gopinath65e837a2016-10-06 17:55:12 -0700167 private static final String PS_COMMAND = "ps -A || ps";
Julien Desprez6961b272016-02-01 09:58:23 +0000168
jdesprezbc580f92017-06-02 11:41:40 -0700169 private static final String SIM_STATE_PROP = "gsm.sim.state";
170 private static final String SIM_OPERATOR_PROP = "gsm.operator.alpha";
171
Kevin Lau Fang805161d2016-12-07 21:06:38 -0800172 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 -0700173 static final String MAC_ADDRESS_COMMAND = "su root cat /sys/class/net/wlan0/address";
Kevin Lau Fang805161d2016-12-07 21:06:38 -0800174
Julien Desprez6961b272016-02-01 09:58:23 +0000175 /** The network monitoring interval in ms. */
176 private static final int NETWORK_MONITOR_INTERVAL = 10 * 1000;
177
178 /** Wifi reconnect check interval in ms. */
179 private static final int WIFI_RECONNECT_CHECK_INTERVAL = 1 * 1000;
180
181 /** Wifi reconnect timeout in ms. */
182 private static final int WIFI_RECONNECT_TIMEOUT = 60 * 1000;
183
Julien Desprez41e09ab2018-07-24 11:07:17 -0700184 /** Pattern to find an executable file. */
185 private static final Pattern EXE_FILE = Pattern.compile("^[-l]r.x.+");
186
Julien Desprezb48b9e22019-05-16 18:52:22 -0700187 /** Path of the device containing the tombstones */
188 private static final String TOMBSTONE_PATH = "/data/tombstones/";
189
Julien Desprez6961b272016-02-01 09:58:23 +0000190 /** The time in ms to wait for a command to complete. */
jdesprez3d7b9142018-03-02 12:11:23 -0800191 private long mCmdTimeout = 2 * 60 * 1000L;
Julien Desprez6961b272016-02-01 09:58:23 +0000192 /** The time in ms to wait for a 'long' command to complete. */
jdesprez3d7b9142018-03-02 12:11:23 -0800193 private long mLongCmdTimeout = 25 * 60 * 1000L;
Julien Desprez6961b272016-02-01 09:58:23 +0000194
Julien Desprez6961b272016-02-01 09:58:23 +0000195 private IDevice mIDevice;
196 private IDeviceRecovery mRecovery = new WaitDeviceRecovery();
Julien Desprezc8474552016-02-17 10:59:27 +0000197 protected final IDeviceStateMonitor mStateMonitor;
Julien Desprez6961b272016-02-01 09:58:23 +0000198 private TestDeviceState mState = TestDeviceState.ONLINE;
199 private final ReentrantLock mFastbootLock = new ReentrantLock();
200 private LogcatReceiver mLogcatReceiver;
201 private boolean mFastbootEnabled = true;
Julien Desprez0a7d67d2016-07-21 16:05:57 +0100202 private String mFastbootPath = "fastboot";
Julien Desprez6961b272016-02-01 09:58:23 +0000203
Julien Desprezc8474552016-02-17 10:59:27 +0000204 protected TestDeviceOptions mOptions = new TestDeviceOptions();
Julien Desprez6961b272016-02-01 09:58:23 +0000205 private Process mEmulatorProcess;
206 private SizeLimitedOutputStream mEmulatorOutput;
Jeffrey Lu279122f2018-01-29 17:25:08 -0800207 private Clock mClock = Clock.systemUTC();
Julien Desprez6961b272016-02-01 09:58:23 +0000208
209 private RecoveryMode mRecoveryMode = RecoveryMode.AVAILABLE;
210
211 private Boolean mIsEncryptionSupported = null;
212 private ReentrantLock mAllocationStateLock = new ReentrantLock();
213 @GuardedBy("mAllocationStateLock")
214 private DeviceAllocationState mAllocationState = DeviceAllocationState.Unknown;
215 private IDeviceMonitor mAllocationMonitor = null;
216
217 private String mLastConnectedWifiSsid = null;
218 private String mLastConnectedWifiPsk = null;
219 private boolean mNetworkMonitorEnabled = false;
220
Julien Desprez1dc6b392019-03-18 11:49:13 -0700221 private ContentProviderHandler mContentProvider = null;
222 private boolean mShouldSkipContentProviderSetup = false;
Julien Despreza5397f12019-03-28 09:46:16 -0700223 /** Keep track of the last time Tradefed itself triggered a reboot. */
224 private long mLastTradefedRebootTime = 0L;
Julien Desprez1dc6b392019-03-18 11:49:13 -0700225
Julien Desprez78de5532019-05-17 10:46:27 -0700226 private File mExecuteShellCommandLogs = null;
227
Julien Desprez6961b272016-02-01 09:58:23 +0000228 /**
229 * Interface for a generic device communication attempt.
230 */
Julien Desprezc8474552016-02-17 10:59:27 +0000231 abstract interface DeviceAction {
Julien Desprez6961b272016-02-01 09:58:23 +0000232
233 /**
234 * Execute the device operation.
235 *
236 * @return <code>true</code> if operation is performed successfully, <code>false</code>
237 * otherwise
Julien Desprezc8474552016-02-17 10:59:27 +0000238 * @throws IOException, TimeoutException, AdbCommandRejectedException,
239 * ShellCommandUnresponsiveException, InstallException,
240 * SyncException if operation terminated abnormally
Julien Desprez6961b272016-02-01 09:58:23 +0000241 */
242 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
243 ShellCommandUnresponsiveException, InstallException, SyncException;
244 }
245
246 /**
247 * A {@link DeviceAction} for running a OS 'adb ....' command.
248 */
Julien Desprezc8474552016-02-17 10:59:27 +0000249 protected class AdbAction implements DeviceAction {
Julien Desprez6961b272016-02-01 09:58:23 +0000250 /** the output from the command */
251 String mOutput = null;
252 private String[] mCmd;
253
254 AdbAction(String[] cmd) {
255 mCmd = cmd;
256 }
257
258 @Override
259 public boolean run() throws TimeoutException, IOException {
260 CommandResult result = getRunUtil().runTimedCmd(getCommandTimeout(), mCmd);
261 // TODO: how to determine device not present with command failing for other reasons
262 if (result.getStatus() == CommandStatus.EXCEPTION) {
263 throw new IOException();
264 } else if (result.getStatus() == CommandStatus.TIMED_OUT) {
265 throw new TimeoutException();
266 } else if (result.getStatus() == CommandStatus.FAILED) {
267 // interpret as communication failure
268 throw new IOException();
269 }
270 mOutput = result.getStdout();
271 return true;
272 }
273 }
274
jdesprez3d7b9142018-03-02 12:11:23 -0800275 protected class AdbShellAction implements DeviceAction {
276 /** the output from the command */
277 CommandResult mResult = null;
278
279 private String[] mCmd;
280 private long mTimeout;
jovanakf39a78c2019-04-02 17:08:43 -0700281 private File mPipeAsInput; // Used in pushFile, uses local file as input to "content write"
282 private OutputStream mPipeToOutput; // Used in pullFile, to pipe content from "content read"
jdesprez3d7b9142018-03-02 12:11:23 -0800283
jovanakf39a78c2019-04-02 17:08:43 -0700284 AdbShellAction(String[] cmd, File pipeAsInput, OutputStream pipeToOutput, long timeout) {
jdesprez3d7b9142018-03-02 12:11:23 -0800285 mCmd = cmd;
Julien Desprez41bd40b2019-02-27 12:32:48 -0800286 mPipeAsInput = pipeAsInput;
jovanakf39a78c2019-04-02 17:08:43 -0700287 mPipeToOutput = pipeToOutput;
jdesprez3d7b9142018-03-02 12:11:23 -0800288 mTimeout = timeout;
289 }
290
291 @Override
292 public boolean run() throws TimeoutException, IOException {
Julien Desprez41bd40b2019-02-27 12:32:48 -0800293 if (mPipeAsInput != null) {
294 mResult = getRunUtil().runTimedCmdWithInputRedirect(mTimeout, mPipeAsInput, mCmd);
295 } else {
jovanakf39a78c2019-04-02 17:08:43 -0700296 mResult =
297 getRunUtil().runTimedCmd(mTimeout, mPipeToOutput, /* stderr= */ null, mCmd);
Julien Desprez41bd40b2019-02-27 12:32:48 -0800298 }
jdesprez3d7b9142018-03-02 12:11:23 -0800299 if (mResult.getStatus() == CommandStatus.EXCEPTION) {
300 throw new IOException(mResult.getStderr());
301 } else if (mResult.getStatus() == CommandStatus.TIMED_OUT) {
302 throw new TimeoutException(mResult.getStderr());
303 }
304 // If it's not some issue with running the adb command, then we return the CommandResult
305 // which will contain all the infos.
306 return true;
307 }
308 }
309
Julien Desprez78344aa2018-09-04 16:06:05 -0700310 /** {@link DeviceAction} for rebooting a device. */
311 protected class RebootDeviceAction implements DeviceAction {
312
313 private final String mInto;
314
315 RebootDeviceAction(String into) {
316 mInto = into;
317 }
318
319 @Override
320 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException {
321 getIDevice().reboot(mInto);
322 return true;
323 }
324 }
325
Julien Desprez6961b272016-02-01 09:58:23 +0000326 /**
327 * Creates a {@link TestDevice}.
328 *
329 * @param device the associated {@link IDevice}
330 * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use
331 * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes.
332 * Can be null
333 */
Julien Desprez2f34e382016-06-21 12:30:39 +0100334 public NativeDevice(IDevice device, IDeviceStateMonitor stateMonitor,
Julien Desprezc8474552016-02-17 10:59:27 +0000335 IDeviceMonitor allocationMonitor) {
Julien Desprez6961b272016-02-01 09:58:23 +0000336 throwIfNull(device);
337 throwIfNull(stateMonitor);
338 mIDevice = device;
339 mStateMonitor = stateMonitor;
340 mAllocationMonitor = allocationMonitor;
341 }
342
Jeffrey Lu279122f2018-01-29 17:25:08 -0800343 /** Get the {@link RunUtil} instance to use. */
344 @VisibleForTesting
Julien Desprezc8474552016-02-17 10:59:27 +0000345 protected IRunUtil getRunUtil() {
Julien Desprez6961b272016-02-01 09:58:23 +0000346 return RunUtil.getDefault();
347 }
348
Jeffrey Lu279122f2018-01-29 17:25:08 -0800349 /** Set the Clock instance to use. */
350 @VisibleForTesting
351 protected void setClock(Clock clock) {
352 mClock = clock;
353 }
354
Julien Desprez6961b272016-02-01 09:58:23 +0000355 /**
356 * {@inheritDoc}
357 */
358 @Override
359 public void setOptions(TestDeviceOptions options) {
360 throwIfNull(options);
361 mOptions = options;
362 mStateMonitor.setDefaultOnlineTimeout(options.getOnlineTimeout());
363 mStateMonitor.setDefaultAvailableTimeout(options.getAvailableTimeout());
364 }
365
366 /**
367 * Sets the max size of a tmp logcat file.
368 *
369 * @param size max byte size of tmp file
370 */
371 void setTmpLogcatSize(long size) {
372 mOptions.setMaxLogcatDataSize(size);
373 }
374
375 /**
376 * Sets the time in ms to wait before starting logcat capture for a online device.
377 *
378 * @param delay the delay in ms
379 */
Julien Desprezbca52e02017-01-23 09:50:07 +0000380 protected void setLogStartDelay(int delay) {
Julien Desprez6961b272016-02-01 09:58:23 +0000381 mLogStartDelay = delay;
382 }
383
384 /**
385 * {@inheritDoc}
386 */
387 @Override
388 public IDevice getIDevice() {
389 synchronized (mIDevice) {
390 return mIDevice;
391 }
392 }
393
394 /**
395 * {@inheritDoc}
396 */
397 @Override
398 public void setIDevice(IDevice newDevice) {
399 throwIfNull(newDevice);
400 IDevice currentDevice = mIDevice;
401 if (!getIDevice().equals(newDevice)) {
402 synchronized (currentDevice) {
403 mIDevice = newDevice;
404 }
405 mStateMonitor.setIDevice(mIDevice);
406 }
407 }
408
409 /**
410 * {@inheritDoc}
411 */
412 @Override
413 public String getSerialNumber() {
414 return getIDevice().getSerialNumber();
415 }
416
Julien Desprez6961b272016-02-01 09:58:23 +0000417 /**
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800418 * Fetch a device property, from the ddmlib cache by default, and falling back to either `adb
419 * shell getprop` or `fastboot getvar` depending on whether the device is in Fastboot or not.
Julien Desprez6961b272016-02-01 09:58:23 +0000420 *
421 * @param propName The name of the device property as returned by `adb shell getprop`
422 * @param fastbootVar The name of the equivalent fastboot variable to query. if {@code null},
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800423 * fastboot query will not be attempted
424 * @param description A simple description of the variable. First letter should be capitalized.
Julien Desprez6961b272016-02-01 09:58:23 +0000425 * @return A string, possibly {@code null} or empty, containing the value of the given property
426 */
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800427 protected String internalGetProperty(String propName, String fastbootVar, String description)
Julien Desprez6961b272016-02-01 09:58:23 +0000428 throws DeviceNotAvailableException, UnsupportedOperationException {
429 String propValue = getIDevice().getProperty(propName);
430 if (propValue != null) {
431 return propValue;
432 } else if (TestDeviceState.FASTBOOT.equals(getDeviceState()) &&
433 fastbootVar != null) {
434 CLog.i("%s for device %s is null, re-querying in fastboot", description,
435 getSerialNumber());
436 return getFastbootVariable(fastbootVar);
437 } else {
Julien Desprez0abf2f12018-12-19 14:49:21 -0800438 CLog.d(
439 "property collection '%s' for device %s is null.",
440 description, getSerialNumber());
441 return null;
Julien Desprez6961b272016-02-01 09:58:23 +0000442 }
443 }
444
445 /**
446 * {@inheritDoc}
447 */
448 @Override
449 public String getProperty(final String name) throws DeviceNotAvailableException {
jdesprez5fb89732017-08-22 14:50:23 -0700450 if (getIDevice() instanceof StubDevice) {
451 return null;
452 }
Julien Desprez30715d62018-10-12 12:01:53 -0700453 if (!TestDeviceState.ONLINE.equals(getDeviceState())) {
454 // Only query property for online device
jdesprezbc580f92017-06-02 11:41:40 -0700455 CLog.d("Device %s is not online cannot get property %s.", getSerialNumber(), name);
456 return null;
457 }
Julien Desprez0abf2f12018-12-19 14:49:21 -0800458 return getIDevice().getProperty(name);
Julien Desprez6961b272016-02-01 09:58:23 +0000459 }
460
Julien Desprez499c5df2018-07-16 14:05:29 -0700461 /** {@inheritDoc} */
462 @Override
463 public boolean setProperty(String propKey, String propValue)
464 throws DeviceNotAvailableException {
465 if (propKey == null || propValue == null) {
466 throw new IllegalArgumentException("set property key or value cannot be null.");
467 }
468 if (!isAdbRoot()) {
469 CLog.e("setProperty requires adb root = true.");
470 return false;
471 }
472 CommandResult result =
473 executeShellV2Command(String.format("setprop \"%s\" \"%s\"", propKey, propValue));
474 if (CommandStatus.SUCCESS.equals(result.getStatus())) {
475 return true;
476 }
477 CLog.e("Something went wrong went setting property %s: %s", propKey, result.getStderr());
478 return false;
479 }
480
Julien Desprez6961b272016-02-01 09:58:23 +0000481 /**
482 * {@inheritDoc}
483 */
Julien Desprez6961b272016-02-01 09:58:23 +0000484 @Override
485 public String getBootloaderVersion() throws UnsupportedOperationException,
486 DeviceNotAvailableException {
487 return internalGetProperty("ro.bootloader", "version-bootloader", "Bootloader");
488 }
489
490 @Override
491 public String getBasebandVersion() throws DeviceNotAvailableException {
492 return internalGetProperty("gsm.version.baseband", "version-baseband", "Baseband");
493 }
494
495 /**
496 * {@inheritDoc}
497 */
498 @Override
499 public String getProductType() throws DeviceNotAvailableException {
500 return internalGetProductType(MAX_RETRY_ATTEMPTS);
501 }
502
503 /**
Julien Desprezc8474552016-02-17 10:59:27 +0000504 * {@link #getProductType()}
Julien Desprez6961b272016-02-01 09:58:23 +0000505 *
Julien Desprezc8474552016-02-17 10:59:27 +0000506 * @param retryAttempts The number of times to try calling {@link #recoverDevice()} if the
Julien Desprez6961b272016-02-01 09:58:23 +0000507 * device's product type cannot be found.
508 */
509 private String internalGetProductType(int retryAttempts) throws DeviceNotAvailableException {
Guang Zhu3ad4f832017-08-28 15:13:25 -0700510 String productType = internalGetProperty(DeviceProperties.BOARD, "product", "Product type");
Guang Zhuc0a99082019-02-27 15:21:27 -0800511 // fallback to ro.hardware for legacy devices
512 if (Strings.isNullOrEmpty(productType)) {
513 productType = internalGetProperty(DeviceProperties.HARDWARE, "product", "Product type");
514 }
Julien Desprez6961b272016-02-01 09:58:23 +0000515
516 // Things will likely break if we don't have a valid product type. Try recovery (in case
517 // the device is only partially booted for some reason), and if that doesn't help, bail.
Guang Zhuc0a99082019-02-27 15:21:27 -0800518 if (Strings.isNullOrEmpty(productType)) {
Julien Desprez6961b272016-02-01 09:58:23 +0000519 if (retryAttempts > 0) {
520 recoverDevice();
521 productType = internalGetProductType(retryAttempts - 1);
522 }
523
Guang Zhuc0a99082019-02-27 15:21:27 -0800524 if (Strings.isNullOrEmpty(productType)) {
Julien Desprez6961b272016-02-01 09:58:23 +0000525 throw new DeviceNotAvailableException(String.format(
Julien Desprez0c6c77c2016-05-31 16:35:57 +0100526 "Could not determine product type for device %s.", getSerialNumber()),
527 getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +0000528 }
529 }
530
Guang Zhud95f1722017-08-31 17:16:47 -0700531 return productType.toLowerCase();
Julien Desprez6961b272016-02-01 09:58:23 +0000532 }
533
534 /**
535 * {@inheritDoc}
536 */
537 @Override
538 public String getFastbootProductType()
539 throws DeviceNotAvailableException, UnsupportedOperationException {
Guang Zhud95f1722017-08-31 17:16:47 -0700540 String prop = getFastbootVariable("product");
541 if (prop != null) {
542 prop = prop.toLowerCase();
543 }
544 return prop;
Julien Desprez6961b272016-02-01 09:58:23 +0000545 }
546
547 /**
548 * {@inheritDoc}
549 */
550 @Override
551 public String getProductVariant() throws DeviceNotAvailableException {
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800552 String prop = internalGetProperty(DeviceProperties.VARIANT, "variant", "Product variant");
553 if (prop == null) {
554 prop =
555 internalGetProperty(
Bowgo Tsai2ae19b52018-10-18 11:58:53 +0800556 DeviceProperties.VARIANT_LEGACY_O_MR1, "variant", "Product variant");
557 }
558 if (prop == null) {
559 prop =
560 internalGetProperty(
561 DeviceProperties.VARIANT_LEGACY_LESS_EQUAL_O,
562 "variant",
563 "Product variant");
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800564 }
Guang Zhud95f1722017-08-31 17:16:47 -0700565 if (prop != null) {
566 prop = prop.toLowerCase();
567 }
568 return prop;
Julien Desprez6961b272016-02-01 09:58:23 +0000569 }
570
571 /**
572 * {@inheritDoc}
573 */
574 @Override
575 public String getFastbootProductVariant()
576 throws DeviceNotAvailableException, UnsupportedOperationException {
Guang Zhud95f1722017-08-31 17:16:47 -0700577 String prop = getFastbootVariable("variant");
578 if (prop != null) {
579 prop = prop.toLowerCase();
580 }
581 return prop;
Julien Desprez6961b272016-02-01 09:58:23 +0000582 }
583
584 private String getFastbootVariable(String variableName)
585 throws DeviceNotAvailableException, UnsupportedOperationException {
586 CommandResult result = executeFastbootCommand("getvar", variableName);
587 if (result.getStatus() == CommandStatus.SUCCESS) {
588 Pattern fastbootProductPattern = Pattern.compile(variableName + ":\\s(.*)\\s");
589 // fastboot is weird, and may dump the output on stderr instead of stdout
590 String resultText = result.getStdout();
591 if (resultText == null || resultText.length() < 1) {
592 resultText = result.getStderr();
593 }
594 Matcher matcher = fastbootProductPattern.matcher(resultText);
595 if (matcher.find()) {
596 return matcher.group(1);
597 }
598 }
599 return null;
600 }
601
602 /**
603 * {@inheritDoc}
604 */
605 @Override
606 public String getBuildAlias() throws DeviceNotAvailableException {
607 String alias = getProperty(BUILD_ALIAS_PROP);
608 if (alias == null || alias.isEmpty()) {
609 return getBuildId();
610 }
611 return alias;
612 }
613
614 /**
615 * {@inheritDoc}
616 */
617 @Override
618 public String getBuildId() throws DeviceNotAvailableException {
619 String bid = getProperty(BUILD_ID_PROP);
620 if (bid == null) {
621 CLog.w("Could not get device %s build id.", getSerialNumber());
622 return IBuildInfo.UNKNOWN_BUILD_ID;
623 }
624 return bid;
625 }
626
627 /**
628 * {@inheritDoc}
629 */
630 @Override
631 public String getBuildFlavor() throws DeviceNotAvailableException {
632 String buildFlavor = getProperty(BUILD_FLAVOR);
633 if (buildFlavor != null && !buildFlavor.isEmpty()) {
634 return buildFlavor;
635 }
636 String productName = getProperty(PRODUCT_NAME_PROP);
637 String buildType = getProperty(BUILD_TYPE_PROP);
638 if (productName == null || buildType == null) {
639 CLog.w("Could not get device %s build flavor.", getSerialNumber());
640 return null;
641 }
642 return String.format("%s-%s", productName, buildType);
643 }
644
645 /**
646 * {@inheritDoc}
647 */
648 @Override
649 public void executeShellCommand(final String command, final IShellOutputReceiver receiver)
650 throws DeviceNotAvailableException {
651 DeviceAction action = new DeviceAction() {
652 @Override
653 public boolean run() throws TimeoutException, IOException,
654 AdbCommandRejectedException, ShellCommandUnresponsiveException {
655 getIDevice().executeShellCommand(command, receiver,
656 mCmdTimeout, TimeUnit.MILLISECONDS);
657 return true;
658 }
659 };
660 performDeviceAction(String.format("shell %s", command), action, MAX_RETRY_ATTEMPTS);
661 }
662
663 /**
664 * {@inheritDoc}
665 */
Julien Desprez6961b272016-02-01 09:58:23 +0000666 @Override
667 public void executeShellCommand(final String command, final IShellOutputReceiver receiver,
668 final long maxTimeToOutputShellResponse, final TimeUnit timeUnit,
669 final int retryAttempts) throws DeviceNotAvailableException {
670 DeviceAction action = new DeviceAction() {
671 @Override
672 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
673 ShellCommandUnresponsiveException {
674 getIDevice().executeShellCommand(command, receiver,
675 maxTimeToOutputShellResponse, timeUnit);
676 return true;
677 }
678 };
679 performDeviceAction(String.format("shell %s", command), action, retryAttempts);
680 }
681
jdesprez2c37cec2017-08-25 12:03:55 -0700682 /** {@inheritDoc} */
683 @Override
684 public void executeShellCommand(
685 final String command,
686 final IShellOutputReceiver receiver,
687 final long maxTimeoutForCommand,
688 final long maxTimeToOutputShellResponse,
689 final TimeUnit timeUnit,
690 final int retryAttempts)
691 throws DeviceNotAvailableException {
692 DeviceAction action =
693 new DeviceAction() {
694 @Override
695 public boolean run()
696 throws TimeoutException, IOException, AdbCommandRejectedException,
697 ShellCommandUnresponsiveException {
698 getIDevice()
699 .executeShellCommand(
700 command,
701 receiver,
702 maxTimeoutForCommand,
703 maxTimeToOutputShellResponse,
704 timeUnit);
705 return true;
706 }
707 };
708 performDeviceAction(String.format("shell %s", command), action, retryAttempts);
709 }
710
Julien Desprez6961b272016-02-01 09:58:23 +0000711 /**
712 * {@inheritDoc}
713 */
714 @Override
715 public String executeShellCommand(String command) throws DeviceNotAvailableException {
716 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
717 executeShellCommand(command, receiver);
718 String output = receiver.getOutput();
Julien Desprez78de5532019-05-17 10:46:27 -0700719 if (mExecuteShellCommandLogs != null) {
720 // Log all output to a dedicated file as it can be very verbose.
721 String formatted =
722 LogUtil.getLogFormatString(
723 LogLevel.VERBOSE,
724 "NativeDevice",
725 String.format(
726 "%s on %s returned %s\n==== END OF OUTPUT ====\n",
727 command, getSerialNumber(), output));
728 try {
729 FileUtil.writeToFile(formatted, mExecuteShellCommandLogs, true);
730 } catch (IOException e) {
731 // Ignore the full error
732 CLog.e("Failed to log to executeShellCommand log: %s", e.getMessage());
733 }
734 }
735 if (output.length() > 80) {
736 CLog.v(
737 "%s on %s returned %s <truncated - See executeShellCommand log for full trace>",
738 command, getSerialNumber(), output.substring(0, 80));
739 } else {
740 CLog.v("%s on %s returned %s", command, getSerialNumber(), output);
741 }
Julien Desprez6961b272016-02-01 09:58:23 +0000742 return output;
743 }
744
jdesprez4de2f552018-02-02 14:38:13 -0800745 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000746 @Override
jdesprez3d7b9142018-03-02 12:11:23 -0800747 public CommandResult executeShellV2Command(String cmd) throws DeviceNotAvailableException {
748 return executeShellV2Command(cmd, getCommandTimeout(), TimeUnit.MILLISECONDS);
749 }
750
751 /** {@inheritDoc} */
752 @Override
Julien Desprez41bd40b2019-02-27 12:32:48 -0800753 public CommandResult executeShellV2Command(String cmd, File pipeAsInput)
754 throws DeviceNotAvailableException {
755 return executeShellV2Command(
jovanakf39a78c2019-04-02 17:08:43 -0700756 cmd,
757 pipeAsInput,
758 null,
759 getCommandTimeout(),
760 TimeUnit.MILLISECONDS,
761 MAX_RETRY_ATTEMPTS);
762 }
763
764 /** {@inheritDoc} */
765 @Override
766 public CommandResult executeShellV2Command(String cmd, OutputStream pipeToOutput)
767 throws DeviceNotAvailableException {
768 return executeShellV2Command(
769 cmd,
770 null,
771 pipeToOutput,
772 getCommandTimeout(),
773 TimeUnit.MILLISECONDS,
774 MAX_RETRY_ATTEMPTS);
Julien Desprez41bd40b2019-02-27 12:32:48 -0800775 }
776
777 /** {@inheritDoc} */
778 @Override
jdesprez3d7b9142018-03-02 12:11:23 -0800779 public CommandResult executeShellV2Command(
780 String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit)
781 throws DeviceNotAvailableException {
jovanakf39a78c2019-04-02 17:08:43 -0700782 return executeShellV2Command(
783 cmd, null, null, maxTimeoutForCommand, timeUnit, MAX_RETRY_ATTEMPTS);
jdesprez3d7b9142018-03-02 12:11:23 -0800784 }
785
786 /** {@inheritDoc} */
787 @Override
788 public CommandResult executeShellV2Command(
789 String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)
790 throws DeviceNotAvailableException {
jovanakf39a78c2019-04-02 17:08:43 -0700791 return executeShellV2Command(
792 cmd, null, null, maxTimeoutForCommand, timeUnit, retryAttempts);
Julien Desprez41bd40b2019-02-27 12:32:48 -0800793 }
794
Paul Trautrim57d82322019-04-12 11:51:03 +0900795 /** {@inheritDoc} */
796 @Override
797 public CommandResult executeShellV2Command(
Julien Desprez41bd40b2019-02-27 12:32:48 -0800798 String cmd,
799 File pipeAsInput,
jovanakf39a78c2019-04-02 17:08:43 -0700800 OutputStream pipeToOutput,
Julien Desprez41bd40b2019-02-27 12:32:48 -0800801 final long maxTimeoutForCommand,
802 final TimeUnit timeUnit,
803 int retryAttempts)
804 throws DeviceNotAvailableException {
jdesprez3d7b9142018-03-02 12:11:23 -0800805 final String[] fullCmd = buildAdbShellCommand(cmd);
806 AdbShellAction adbActionV2 =
jovanakf39a78c2019-04-02 17:08:43 -0700807 new AdbShellAction(
808 fullCmd,
809 pipeAsInput,
810 pipeToOutput,
811 timeUnit.toMillis(maxTimeoutForCommand));
jdesprez3d7b9142018-03-02 12:11:23 -0800812 performDeviceAction(String.format("adb %s", fullCmd[4]), adbActionV2, retryAttempts);
813 return adbActionV2.mResult;
814 }
815
816 /** {@inheritDoc} */
817 @Override
jdesprez4de2f552018-02-02 14:38:13 -0800818 public boolean runInstrumentationTests(
819 final IRemoteAndroidTestRunner runner,
820 final Collection<ITestLifeCycleReceiver> listeners)
821 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +0000822 RunFailureListener failureListener = new RunFailureListener();
jdesprez4de2f552018-02-02 14:38:13 -0800823 List<ITestRunListener> runListeners = new ArrayList<>();
824 runListeners.add(failureListener);
825 runListeners.add(new TestRunToTestInvocationForwarder(listeners));
Julien Desprez6961b272016-02-01 09:58:23 +0000826
jdesprez4de2f552018-02-02 14:38:13 -0800827 DeviceAction runTestsAction =
828 new DeviceAction() {
829 @Override
830 public boolean run()
831 throws IOException, TimeoutException, AdbCommandRejectedException,
832 ShellCommandUnresponsiveException, InstallException,
833 SyncException {
834 runner.run(runListeners);
835 return true;
836 }
837 };
Julien Desprez6961b272016-02-01 09:58:23 +0000838 boolean result = performDeviceAction(String.format("run %s instrumentation tests",
839 runner.getPackageName()), runTestsAction, 0);
840 if (failureListener.isRunFailure()) {
841 // run failed, might be system crash. Ensure device is up
842 if (mStateMonitor.waitForDeviceAvailable(5 * 1000) == null) {
843 // device isn't up, recover
844 recoverDevice();
845 }
846 }
847 return result;
848 }
849
jdesprez4de2f552018-02-02 14:38:13 -0800850 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000851 @Override
jdesprez4de2f552018-02-02 14:38:13 -0800852 public boolean runInstrumentationTestsAsUser(
853 final IRemoteAndroidTestRunner runner,
854 int userId,
855 final Collection<ITestLifeCycleReceiver> listeners)
856 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +0000857 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
858 boolean result = runInstrumentationTests(runner, listeners);
859 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
860 return result;
861 }
862
863 /**
864 * Helper method to add user run time option to {@link RemoteAndroidTestRunner}
865 *
866 * @param runner {@link IRemoteAndroidTestRunner}
867 * @param userId the integer of the user id to run as.
868 * @return original run time options.
869 */
870 private String appendUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, int userId) {
871 if (runner instanceof RemoteAndroidTestRunner) {
872 String original = ((RemoteAndroidTestRunner) runner).getRunOptions();
873 String userRunTimeOption = String.format("--user %s", Integer.toString(userId));
Jeff Sharkeydb4518e2018-10-27 16:10:51 -0600874 String updated = (original != null) ? (original + " " + userRunTimeOption)
875 : userRunTimeOption;
876 ((RemoteAndroidTestRunner) runner).setRunOptions(updated);
Julien Desprez6961b272016-02-01 09:58:23 +0000877 return original;
878 } else {
879 throw new IllegalStateException(String.format("%s runner does not support multi-user",
880 runner.getClass().getName()));
881 }
882 }
883
884 /**
885 * Helper method to reset the run time options to {@link RemoteAndroidTestRunner}
886 *
887 * @param runner {@link IRemoteAndroidTestRunner}
888 * @param oldRunTimeOptions
889 */
890 private void resetUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner,
891 String oldRunTimeOptions) {
892 if (runner instanceof RemoteAndroidTestRunner) {
893 if (oldRunTimeOptions != null) {
894 ((RemoteAndroidTestRunner) runner).setRunOptions(oldRunTimeOptions);
895 }
896 } else {
897 throw new IllegalStateException(String.format("%s runner does not support multi-user",
898 runner.getClass().getName()));
899 }
900 }
901
902 private static class RunFailureListener extends StubTestRunListener {
903 private boolean mIsRunFailure = false;
904
905 @Override
906 public void testRunFailed(String message) {
907 mIsRunFailure = true;
908 }
909
910 public boolean isRunFailure() {
911 return mIsRunFailure;
912 }
913 }
914
jdesprez4de2f552018-02-02 14:38:13 -0800915 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000916 @Override
jdesprez4de2f552018-02-02 14:38:13 -0800917 public boolean runInstrumentationTests(
918 IRemoteAndroidTestRunner runner, ITestLifeCycleReceiver... listeners)
919 throws DeviceNotAvailableException {
920 List<ITestLifeCycleReceiver> listenerList = new ArrayList<>();
Julien Desprez6961b272016-02-01 09:58:23 +0000921 listenerList.addAll(Arrays.asList(listeners));
922 return runInstrumentationTests(runner, listenerList);
923 }
924
jdesprez4de2f552018-02-02 14:38:13 -0800925 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000926 @Override
jdesprez4de2f552018-02-02 14:38:13 -0800927 public boolean runInstrumentationTestsAsUser(
928 IRemoteAndroidTestRunner runner, int userId, ITestLifeCycleReceiver... listeners)
929 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +0000930 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
931 boolean result = runInstrumentationTests(runner, listeners);
932 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
933 return result;
934 }
935
936 /**
937 * {@inheritDoc}
938 */
939 @Override
940 public boolean isRuntimePermissionSupported() throws DeviceNotAvailableException {
Guang Zhu1fb39eb2016-03-27 15:57:28 -0700941 return getApiLevel() > 22;
Julien Desprez6961b272016-02-01 09:58:23 +0000942 }
943
944 /**
945 * helper method to throw exception if runtime permission isn't supported
946 * @throws DeviceNotAvailableException
947 */
Julien Desprezc8474552016-02-17 10:59:27 +0000948 protected void ensureRuntimePermissionSupported() throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +0000949 boolean runtimePermissionSupported = isRuntimePermissionSupported();
950 if (!runtimePermissionSupported) {
951 throw new UnsupportedOperationException(
952 "platform on device does not support runtime permission granting!");
953 }
954 }
955
956 /**
Julien Desprez6961b272016-02-01 09:58:23 +0000957 * {@inheritDoc}
958 */
959 @Override
960 public String installPackage(final File packageFile, final boolean reinstall,
961 final String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000962 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000963 }
964
965 /**
966 * {@inheritDoc}
967 */
968 @Override
969 public String installPackage(File packageFile, boolean reinstall, boolean grantPermissions,
970 String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000971 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000972 }
973
974 /**
975 * {@inheritDoc}
976 */
977 @Override
978 public String installPackageForUser(File packageFile, boolean reinstall, int userId,
979 String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000980 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000981 }
982
983 /**
984 * {@inheritDoc}
985 */
986 @Override
987 public String installPackageForUser(File packageFile, boolean reinstall,
988 boolean grantPermissions, int userId, String... extraArgs)
989 throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000990 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000991 }
992
993 /**
994 * {@inheritDoc}
995 */
996 @Override
997 public String uninstallPackage(final String packageName) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000998 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000999 }
1000
1001 /**
1002 * {@inheritDoc}
1003 */
1004 @Override
1005 public boolean pullFile(final String remoteFilePath, final File localFile)
1006 throws DeviceNotAvailableException {
1007
jovanakf39a78c2019-04-02 17:08:43 -07001008 if (remoteFilePath.startsWith(SD_CARD)) {
1009 ContentProviderHandler handler = getContentProvider();
1010 if (handler != null) {
1011 return handler.pullFile(remoteFilePath, localFile);
1012 }
1013 }
1014
Julien Desprez6961b272016-02-01 09:58:23 +00001015 DeviceAction pullAction = new DeviceAction() {
1016 @Override
1017 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1018 SyncException {
1019 SyncService syncService = null;
1020 boolean status = false;
1021 try {
1022 syncService = getIDevice().getSyncService();
1023 syncService.pullFile(interpolatePathVariables(remoteFilePath),
1024 localFile.getAbsolutePath(), SyncService.getNullProgressMonitor());
1025 status = true;
1026 } catch (SyncException e) {
1027 CLog.w("Failed to pull %s from %s to %s. Message %s", remoteFilePath,
1028 getSerialNumber(), localFile.getAbsolutePath(), e.getMessage());
1029 throw e;
1030 } finally {
1031 if (syncService != null) {
1032 syncService.close();
1033 }
1034 }
1035 return status;
1036 }
1037 };
1038 return performDeviceAction(String.format("pull %s to %s", remoteFilePath,
1039 localFile.getAbsolutePath()), pullAction, MAX_RETRY_ATTEMPTS);
1040 }
1041
1042 /**
1043 * {@inheritDoc}
1044 */
1045 @Override
1046 public File pullFile(String remoteFilePath) throws DeviceNotAvailableException {
1047 File localFile = null;
1048 boolean success = false;
1049 try {
1050 localFile = FileUtil.createTempFileForRemote(remoteFilePath, null);
1051 if (pullFile(remoteFilePath, localFile)) {
1052 success = true;
1053 return localFile;
1054 }
1055 } catch (IOException e) {
Julien Desprez9cd6ca72016-12-19 12:35:15 +00001056 CLog.w("Encountered IOException while trying to pull '%s':", remoteFilePath);
1057 CLog.e(e);
Julien Desprez6961b272016-02-01 09:58:23 +00001058 } finally {
1059 if (!success) {
1060 FileUtil.deleteFile(localFile);
1061 }
1062 }
1063 return null;
1064 }
1065
1066 /**
1067 * {@inheritDoc}
1068 */
1069 @Override
Zach Riggle8c6fce62018-03-03 16:19:28 -06001070 public String pullFileContents(String remoteFilePath) throws DeviceNotAvailableException {
1071 File temp = pullFile(remoteFilePath);
1072
1073 if (temp != null) {
1074 try {
1075 return FileUtil.readStringFromFile(temp);
1076 } catch (IOException e) {
1077 CLog.e(String.format("Could not pull file: %s", remoteFilePath));
1078 } finally {
1079 FileUtil.deleteFile(temp);
1080 }
1081 }
1082
1083 return null;
1084 }
1085
1086 /**
1087 * {@inheritDoc}
1088 */
1089 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001090 public File pullFileFromExternal(String remoteFilePath) throws DeviceNotAvailableException {
1091 String externalPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
1092 String fullPath = (new File(externalPath, remoteFilePath)).getPath();
1093 return pullFile(fullPath);
1094 }
1095
1096 /**
1097 * Helper function that watches for the string "${EXTERNAL_STORAGE}" and replaces it with the
1098 * pathname of the EXTERNAL_STORAGE mountpoint. Specifically intended to be used for pathnames
1099 * that are being passed to SyncService, which does not support variables inside of filenames.
1100 */
1101 String interpolatePathVariables(String path) {
1102 final String esString = "${EXTERNAL_STORAGE}";
1103 if (path.contains(esString)) {
1104 final String esPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
1105 path = path.replace(esString, esPath);
1106 }
1107 return path;
1108 }
1109
1110 /**
1111 * {@inheritDoc}
1112 */
1113 @Override
1114 public boolean pushFile(final File localFile, final String remoteFilePath)
1115 throws DeviceNotAvailableException {
Julien Desprez1dc6b392019-03-18 11:49:13 -07001116 if (remoteFilePath.startsWith(SD_CARD)) {
1117 ContentProviderHandler handler = getContentProvider();
1118 if (handler != null) {
Julien Desprez1dc6b392019-03-18 11:49:13 -07001119 return handler.pushFile(localFile, remoteFilePath);
1120 }
1121 }
1122
jdesprez3dff70b2017-04-18 10:39:13 -07001123 DeviceAction pushAction =
1124 new DeviceAction() {
1125 @Override
1126 public boolean run()
1127 throws TimeoutException, IOException, AdbCommandRejectedException,
1128 SyncException {
1129 SyncService syncService = null;
1130 boolean status = false;
1131 try {
1132 syncService = getIDevice().getSyncService();
1133 if (syncService == null) {
1134 throw new IOException("SyncService returned null.");
1135 }
1136 syncService.pushFile(
1137 localFile.getAbsolutePath(),
1138 interpolatePathVariables(remoteFilePath),
1139 SyncService.getNullProgressMonitor());
1140 status = true;
1141 } catch (SyncException e) {
1142 CLog.w(
jdesprez791fc5d2017-08-02 11:25:48 -07001143 "Failed to push %s to %s on device %s. Message: '%s'. "
1144 + "Error code: %s",
jdesprez3dff70b2017-04-18 10:39:13 -07001145 localFile.getAbsolutePath(),
1146 remoteFilePath,
1147 getSerialNumber(),
jdesprez791fc5d2017-08-02 11:25:48 -07001148 e.getMessage(),
1149 e.getErrorCode());
1150 // TODO: check if ddmlib can report a better error
1151 if (SyncError.TRANSFER_PROTOCOL_ERROR.equals(e.getErrorCode())) {
1152 if (e.getMessage().contains("Permission denied")) {
1153 return false;
1154 }
1155 }
jdesprez3dff70b2017-04-18 10:39:13 -07001156 throw e;
1157 } finally {
1158 if (syncService != null) {
1159 syncService.close();
1160 }
1161 }
1162 return status;
Julien Desprez6961b272016-02-01 09:58:23 +00001163 }
jdesprez3dff70b2017-04-18 10:39:13 -07001164 };
Julien Desprez6961b272016-02-01 09:58:23 +00001165 return performDeviceAction(String.format("push %s to %s", localFile.getAbsolutePath(),
1166 remoteFilePath), pushAction, MAX_RETRY_ATTEMPTS);
1167 }
1168
1169 /**
1170 * {@inheritDoc}
1171 */
1172 @Override
1173 public boolean pushString(final String contents, final String remoteFilePath)
1174 throws DeviceNotAvailableException {
1175 File tmpFile = null;
1176 try {
1177 tmpFile = FileUtil.createTempFile("temp", ".txt");
1178 FileUtil.writeToFile(contents, tmpFile);
1179 return pushFile(tmpFile, remoteFilePath);
1180 } catch (IOException e) {
1181 CLog.e(e);
1182 return false;
1183 } finally {
Julien Desprez1320e592016-12-06 09:51:53 +00001184 FileUtil.deleteFile(tmpFile);
Julien Desprez6961b272016-02-01 09:58:23 +00001185 }
1186 }
1187
Julien Desprez901d4962019-03-27 10:10:23 -07001188 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00001189 @Override
Julien Desprez901d4962019-03-27 10:10:23 -07001190 public boolean doesFileExist(String deviceFilePath) throws DeviceNotAvailableException {
1191 String lsGrep = executeShellCommand(String.format("ls \"%s\"", deviceFilePath));
Julien Desprez6961b272016-02-01 09:58:23 +00001192 return !lsGrep.contains("No such file or directory");
1193 }
1194
Julien Desprez901d4962019-03-27 10:10:23 -07001195 /** {@inheritDoc} */
1196 @Override
1197 public void deleteFile(String deviceFilePath) throws DeviceNotAvailableException {
Julien Desprez6e5d83c2019-04-02 09:27:22 -07001198 if (deviceFilePath.startsWith(SD_CARD)) {
1199 ContentProviderHandler handler = getContentProvider();
1200 if (handler != null) {
1201 if (handler.deleteFile(deviceFilePath)) {
1202 return;
1203 }
1204 }
1205 }
1206 // Fallback to the direct command if content provider is unsuccessful
Julien Desprezed378b92019-04-19 12:31:57 -07001207 String path = StringEscapeUtils.escapeShell(deviceFilePath);
1208 // Escape spaces to handle filename with spaces
1209 path = path.replaceAll(" ", "\\ ");
1210 executeShellCommand(String.format("rm -rf %s", StringEscapeUtils.escapeShell(path)));
Julien Desprez901d4962019-03-27 10:10:23 -07001211 }
1212
Julien Desprez6961b272016-02-01 09:58:23 +00001213 /**
1214 * {@inheritDoc}
1215 */
1216 @Override
1217 public long getExternalStoreFreeSpace() throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00001218 String externalStorePath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
Arthur Eubankse4531612017-10-23 11:30:52 -07001219 return getPartitionFreeSpace(externalStorePath);
1220 }
1221
1222 /** {@inheritDoc} */
1223 @Override
1224 public long getPartitionFreeSpace(String partition) throws DeviceNotAvailableException {
1225 CLog.i("Checking free space for %s on partition %s", getSerialNumber(), partition);
1226 String output = getDfOutput(partition);
Julien Desprez6961b272016-02-01 09:58:23 +00001227 // Try coreutils/toybox style output first.
1228 Long available = parseFreeSpaceFromModernOutput(output);
1229 if (available != null) {
1230 return available;
1231 }
1232 // Then the two legacy toolbox formats.
1233 available = parseFreeSpaceFromAvailable(output);
1234 if (available != null) {
1235 return available;
1236 }
Arthur Eubankse4531612017-10-23 11:30:52 -07001237 available = parseFreeSpaceFromFree(partition, output);
Julien Desprez6961b272016-02-01 09:58:23 +00001238 if (available != null) {
1239 return available;
1240 }
1241
1242 CLog.e("free space command output \"%s\" did not match expected patterns", output);
1243 return 0;
1244 }
1245
1246 /**
1247 * Run the 'df' shell command and return output, making multiple attempts if necessary.
1248 *
1249 * @param externalStorePath the path to check
1250 * @return the output from 'shell df path'
1251 * @throws DeviceNotAvailableException
1252 */
1253 private String getDfOutput(String externalStorePath) throws DeviceNotAvailableException {
1254 for (int i=0; i < MAX_RETRY_ATTEMPTS; i++) {
1255 String output = executeShellCommand(String.format("df %s", externalStorePath));
1256 if (output.trim().length() > 0) {
1257 return output;
1258 }
1259 }
1260 throw new DeviceUnresponsiveException(String.format(
1261 "Device %s not returning output from df command after %d attempts",
Julien Desprez0c6c77c2016-05-31 16:35:57 +01001262 getSerialNumber(), MAX_RETRY_ATTEMPTS), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00001263 }
1264
1265 /**
1266 * Parses a partition's available space from the legacy output of a 'df' command, used
1267 * pre-gingerbread.
1268 * <p/>
1269 * Assumes output format of:
1270 * <br>/
1271 * <code>
1272 * [partition]: 15659168K total, 51584K used, 15607584K available (block size 32768)
1273 * </code>
1274 * @param dfOutput the output of df command to parse
1275 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1276 */
1277 private Long parseFreeSpaceFromAvailable(String dfOutput) {
1278 final Pattern freeSpacePattern = Pattern.compile("(\\d+)K available");
1279 Matcher patternMatcher = freeSpacePattern.matcher(dfOutput);
1280 if (patternMatcher.find()) {
1281 String freeSpaceString = patternMatcher.group(1);
1282 try {
1283 return Long.parseLong(freeSpaceString);
1284 } catch (NumberFormatException e) {
1285 // fall through
1286 }
1287 }
1288 return null;
1289 }
1290
1291 /**
1292 * Parses a partition's available space from the 'table-formatted' output of a toolbox 'df'
1293 * command, used from gingerbread to lollipop.
1294 * <p/>
1295 * Assumes output format of:
1296 * <br/>
1297 * <code>
1298 * Filesystem Size Used Free Blksize
1299 * <br/>
1300 * [partition]: 3G 790M 2G 4096
1301 * </code>
1302 * @param dfOutput the output of df command to parse
1303 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1304 */
1305 Long parseFreeSpaceFromFree(String externalStorePath, String dfOutput) {
1306 Long freeSpace = null;
1307 final Pattern freeSpaceTablePattern = Pattern.compile(String.format(
1308 //fs Size Used Free
1309 "%s\\s+[\\w\\d\\.]+\\s+[\\w\\d\\.]+\\s+([\\d\\.]+)(\\w)", externalStorePath));
1310 Matcher tablePatternMatcher = freeSpaceTablePattern.matcher(dfOutput);
1311 if (tablePatternMatcher.find()) {
1312 String numericValueString = tablePatternMatcher.group(1);
1313 String unitType = tablePatternMatcher.group(2);
1314 try {
1315 Float freeSpaceFloat = Float.parseFloat(numericValueString);
1316 if (unitType.equals("M")) {
1317 freeSpaceFloat = freeSpaceFloat * 1024;
1318 } else if (unitType.equals("G")) {
1319 freeSpaceFloat = freeSpaceFloat * 1024 * 1024;
1320 }
1321 freeSpace = freeSpaceFloat.longValue();
1322 } catch (NumberFormatException e) {
1323 // fall through
1324 }
1325 }
1326 return freeSpace;
1327 }
1328
1329 /**
1330 * Parses a partition's available space from the modern coreutils/toybox 'df' output, used
1331 * after lollipop.
1332 * <p/>
1333 * Assumes output format of:
1334 * <br/>
1335 * <code>
1336 * Filesystem 1K-blocks Used Available Use% Mounted on
1337 * <br/>
1338 * /dev/fuse 11585536 1316348 10269188 12% /mnt/shell/emulated
1339 * </code>
1340 * @param dfOutput the output of df command to parse
1341 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1342 */
1343 Long parseFreeSpaceFromModernOutput(String dfOutput) {
1344 Matcher matcher = DF_PATTERN.matcher(dfOutput);
1345 if (matcher.find()) {
1346 try {
1347 return Long.parseLong(matcher.group(1));
1348 } catch (NumberFormatException e) {
1349 // fall through
1350 }
1351 }
1352 return null;
1353 }
1354
1355 /**
1356 * {@inheritDoc}
1357 */
1358 @Override
1359 public String getMountPoint(String mountName) {
1360 return mStateMonitor.getMountPoint(mountName);
1361 }
1362
1363 /**
1364 * {@inheritDoc}
1365 */
1366 @Override
1367 public List<MountPointInfo> getMountPointInfo() throws DeviceNotAvailableException {
1368 final String mountInfo = executeShellCommand("cat /proc/mounts");
1369 final String[] mountInfoLines = mountInfo.split("\r?\n");
Eric Rowe1abf2c02017-03-20 17:12:33 -07001370 List<MountPointInfo> list = new ArrayList<>(mountInfoLines.length);
Julien Desprez6961b272016-02-01 09:58:23 +00001371
1372 for (String line : mountInfoLines) {
1373 // We ignore the last two fields
1374 // /dev/block/mtdblock4 /cache yaffs2 rw,nosuid,nodev,relatime 0 0
1375 final String[] parts = line.split("\\s+", 5);
1376 list.add(new MountPointInfo(parts[0], parts[1], parts[2], parts[3]));
1377 }
1378
1379 return list;
1380 }
1381
1382 /**
1383 * {@inheritDoc}
1384 */
1385 @Override
1386 public MountPointInfo getMountPointInfo(String mountpoint) throws DeviceNotAvailableException {
1387 // The overhead of parsing all of the lines should be minimal
1388 List<MountPointInfo> mountpoints = getMountPointInfo();
1389 for (MountPointInfo info : mountpoints) {
1390 if (mountpoint.equals(info.mountpoint)) return info;
1391 }
1392 return null;
1393 }
1394
1395 /**
1396 * {@inheritDoc}
1397 */
1398 @Override
1399 public IFileEntry getFileEntry(String path) throws DeviceNotAvailableException {
1400 path = interpolatePathVariables(path);
1401 String[] pathComponents = path.split(FileListingService.FILE_SEPARATOR);
1402 FileListingService service = getFileListingService();
1403 IFileEntry rootFile = new FileEntryWrapper(this, service.getRoot());
1404 return FileEntryWrapper.getDescendant(rootFile, Arrays.asList(pathComponents));
1405 }
1406
1407 /**
jdesprez934653e2018-02-13 12:48:48 -08001408 * Unofficial helper to get a {@link FileEntry} from a non-root path. FIXME: Refactor the
1409 * FileEntry system to have it available from any path. (even non root).
1410 *
1411 * @param entry a {@link FileEntry} not necessarily root as Ddmlib requires.
1412 * @return a {@link FileEntryWrapper} representing the FileEntry.
1413 * @throws DeviceNotAvailableException
1414 */
1415 public IFileEntry getFileEntry(FileEntry entry) throws DeviceNotAvailableException {
Julien Desprez41e09ab2018-07-24 11:07:17 -07001416 // FileEntryWrapper is going to construct the list of child file internally.
jdesprez934653e2018-02-13 12:48:48 -08001417 return new FileEntryWrapper(this, entry);
1418 }
1419
Julien Desprez41e09ab2018-07-24 11:07:17 -07001420 /** {@inheritDoc} */
1421 @Override
1422 public boolean isExecutable(String fullPath) throws DeviceNotAvailableException {
1423 String fileMode = executeShellCommand(String.format("ls -l %s", fullPath));
1424 if (fileMode != null) {
1425 return EXE_FILE.matcher(fileMode).find();
1426 }
1427 return false;
1428 }
1429
jdesprez934653e2018-02-13 12:48:48 -08001430 /**
Julien Desprez56f18e02016-03-11 14:40:18 +00001431 * {@inheritDoc}
1432 */
1433 @Override
1434 public boolean isDirectory(String path) throws DeviceNotAvailableException {
1435 return executeShellCommand(String.format("ls -ld %s", path)).charAt(0) == 'd';
1436 }
1437
1438 /**
1439 * {@inheritDoc}
1440 */
1441 @Override
1442 public String[] getChildren(String path) throws DeviceNotAvailableException {
1443 String lsOutput = executeShellCommand(String.format("ls -A1 %s", path));
1444 if (lsOutput.trim().isEmpty()) {
1445 return new String[0];
1446 }
1447 return lsOutput.split("\r?\n");
1448 }
1449
1450 /**
Julien Desprez6961b272016-02-01 09:58:23 +00001451 * Retrieve the {@link FileListingService} for the {@link IDevice}, making multiple attempts
1452 * and recovery operations if necessary.
1453 * <p/>
1454 * This is necessary because {@link IDevice#getFileListingService()} can return
1455 * <code>null</code> if device is in fastboot. The symptom of this condition is that the
1456 * current {@link #getIDevice()} is a {@link StubDevice}.
1457 *
1458 * @return the {@link FileListingService}
1459 * @throws DeviceNotAvailableException if device communication is lost.
1460 */
1461 private FileListingService getFileListingService() throws DeviceNotAvailableException {
1462 final FileListingService[] service = new FileListingService[1];
1463 DeviceAction serviceAction = new DeviceAction() {
1464 @Override
1465 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
1466 ShellCommandUnresponsiveException, InstallException, SyncException {
1467 service[0] = getIDevice().getFileListingService();
1468 if (service[0] == null) {
1469 // could not get file listing service - must be a stub device - enter recovery
1470 throw new IOException("Could not get file listing service");
1471 }
1472 return true;
1473 }
1474 };
1475 performDeviceAction("getFileListingService", serviceAction, MAX_RETRY_ATTEMPTS);
1476 return service[0];
1477 }
1478
1479 /**
1480 * {@inheritDoc}
1481 */
1482 @Override
1483 public boolean pushDir(File localFileDir, String deviceFilePath)
1484 throws DeviceNotAvailableException {
Julien Desprez69324072018-08-14 14:08:47 -07001485 return pushDir(localFileDir, deviceFilePath, new HashSet<>());
1486 }
1487
1488 /** {@inheritDoc} */
1489 @Override
1490 public boolean pushDir(
1491 File localFileDir, String deviceFilePath, Set<String> excludedDirectories)
1492 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00001493 if (!localFileDir.isDirectory()) {
1494 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
1495 return false;
1496 }
1497 File[] childFiles = localFileDir.listFiles();
1498 if (childFiles == null) {
1499 CLog.e("Could not read files in %s", localFileDir.getAbsolutePath());
1500 return false;
1501 }
1502 for (File childFile : childFiles) {
1503 String remotePath = String.format("%s/%s", deviceFilePath, childFile.getName());
1504 if (childFile.isDirectory()) {
Julien Desprez69324072018-08-14 14:08:47 -07001505 // If we encounter a filtered directory do not push it.
1506 if (excludedDirectories.contains(childFile.getName())) {
1507 CLog.d(
1508 "%s directory was not pushed because it was filtered.",
1509 childFile.getAbsolutePath());
1510 continue;
1511 }
Hyungtae Tim Kim4cff14e2016-08-01 18:29:48 +09001512 executeShellCommand(String.format("mkdir -p \"%s\"", remotePath));
Julien Desprez69324072018-08-14 14:08:47 -07001513 if (!pushDir(childFile, remotePath, excludedDirectories)) {
Julien Desprez6961b272016-02-01 09:58:23 +00001514 return false;
1515 }
1516 } else if (childFile.isFile()) {
1517 if (!pushFile(childFile, remotePath)) {
1518 return false;
1519 }
1520 }
1521 }
1522 return true;
1523 }
1524
1525 /**
1526 * {@inheritDoc}
1527 */
1528 @Override
Guang Zhud7088362016-06-28 18:41:10 -07001529 public boolean pullDir(String deviceFilePath, File localDir)
1530 throws DeviceNotAvailableException {
jovanak0eaec7e2019-04-12 17:54:46 -07001531 if (deviceFilePath.startsWith(SD_CARD)) {
1532 ContentProviderHandler handler = getContentProvider();
1533 if (handler != null) {
1534 return handler.pullDir(deviceFilePath, localDir);
1535 }
1536 }
1537
Guang Zhud7088362016-06-28 18:41:10 -07001538 if (!localDir.isDirectory()) {
1539 CLog.e("Local path %s is not a directory", localDir.getAbsolutePath());
1540 return false;
1541 }
1542 if (!isDirectory(deviceFilePath)) {
1543 CLog.e("Device path %s is not a directory", deviceFilePath);
1544 return false;
1545 }
jdesprezb1469112018-02-15 09:57:25 -08001546 FileEntry entryRoot =
1547 new FileEntry(null, deviceFilePath, FileListingService.TYPE_DIRECTORY, false);
jdesprez934653e2018-02-13 12:48:48 -08001548 IFileEntry entry = getFileEntry(entryRoot);
jdesprez104458a2018-02-06 11:32:59 -08001549 Collection<IFileEntry> children = entry.getChildren(false);
1550 if (children.isEmpty()) {
Guang Zhud7088362016-06-28 18:41:10 -07001551 CLog.i("Device path is empty, nothing to do.");
1552 return true;
1553 }
jdesprez104458a2018-02-06 11:32:59 -08001554 for (IFileEntry item : children) {
1555 if (item.isDirectory()) {
Guang Zhud7088362016-06-28 18:41:10 -07001556 // handle sub dir
jdesprez104458a2018-02-06 11:32:59 -08001557 File subDir = new File(localDir, item.getName());
Guang Zhud7088362016-06-28 18:41:10 -07001558 if (!subDir.mkdir()) {
1559 CLog.w("Failed to create sub directory %s, aborting.",
1560 subDir.getAbsolutePath());
1561 return false;
1562 }
jdesprez104458a2018-02-06 11:32:59 -08001563 String deviceSubDir = item.getFullPath();
Guang Zhud7088362016-06-28 18:41:10 -07001564 if (!pullDir(deviceSubDir, subDir)) {
1565 CLog.w("Failed to pull sub directory %s from device, aborting", deviceSubDir);
1566 return false;
1567 }
1568 } else {
1569 // handle regular file
jdesprez104458a2018-02-06 11:32:59 -08001570 File localFile = new File(localDir, item.getName());
1571 String fullPath = item.getFullPath();
1572 if (!pullFile(fullPath, localFile)) {
1573 CLog.w("Failed to pull file %s from device, aborting", fullPath);
Guang Zhud7088362016-06-28 18:41:10 -07001574 return false;
1575 }
1576 }
1577 }
1578 return true;
1579 }
1580
1581 /**
1582 * {@inheritDoc}
1583 */
1584 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001585 public boolean syncFiles(File localFileDir, String deviceFilePath)
1586 throws DeviceNotAvailableException {
1587 if (localFileDir == null || deviceFilePath == null) {
1588 throw new IllegalArgumentException("syncFiles does not take null arguments");
1589 }
1590 CLog.i("Syncing %s to %s on device %s",
1591 localFileDir.getAbsolutePath(), deviceFilePath, getSerialNumber());
1592 if (!localFileDir.isDirectory()) {
1593 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
1594 return false;
1595 }
1596 // get the real destination path. This is done because underlying syncService.push
1597 // implementation will add localFileDir.getName() to destination path
1598 deviceFilePath = String.format("%s/%s", interpolatePathVariables(deviceFilePath),
1599 localFileDir.getName());
1600 if (!doesFileExist(deviceFilePath)) {
Hyungtae Tim Kim4cff14e2016-08-01 18:29:48 +09001601 executeShellCommand(String.format("mkdir -p \"%s\"", deviceFilePath));
Julien Desprez6961b272016-02-01 09:58:23 +00001602 }
1603 IFileEntry remoteFileEntry = getFileEntry(deviceFilePath);
1604 if (remoteFileEntry == null) {
1605 CLog.e("Could not find remote file entry %s ", deviceFilePath);
1606 return false;
1607 }
1608
1609 return syncFiles(localFileDir, remoteFileEntry);
1610 }
1611
1612 /**
1613 * Recursively sync newer files.
1614 *
1615 * @param localFileDir the local {@link File} directory to sync
1616 * @param remoteFileEntry the remote destination {@link IFileEntry}
1617 * @return <code>true</code> if files were synced successfully
1618 * @throws DeviceNotAvailableException
1619 */
1620 private boolean syncFiles(File localFileDir, final IFileEntry remoteFileEntry)
1621 throws DeviceNotAvailableException {
1622 CLog.d("Syncing %s to %s on %s", localFileDir.getAbsolutePath(),
1623 remoteFileEntry.getFullPath(), getSerialNumber());
1624 // find newer files to sync
1625 File[] localFiles = localFileDir.listFiles(new NoHiddenFilesFilter());
Eric Rowe1abf2c02017-03-20 17:12:33 -07001626 ArrayList<String> filePathsToSync = new ArrayList<>();
Julien Desprez6961b272016-02-01 09:58:23 +00001627 for (File localFile : localFiles) {
1628 IFileEntry entry = remoteFileEntry.findChild(localFile.getName());
1629 if (entry == null) {
1630 CLog.d("Detected missing file path %s", localFile.getAbsolutePath());
1631 filePathsToSync.add(localFile.getAbsolutePath());
1632 } else if (localFile.isDirectory()) {
1633 // This directory exists remotely. recursively sync it to sync only its newer files
1634 // contents
1635 if (!syncFiles(localFile, entry)) {
1636 return false;
1637 }
1638 } else if (isNewer(localFile, entry)) {
1639 CLog.d("Detected newer file %s", localFile.getAbsolutePath());
1640 filePathsToSync.add(localFile.getAbsolutePath());
1641 }
1642 }
1643
1644 if (filePathsToSync.size() == 0) {
1645 CLog.d("No files to sync");
1646 return true;
1647 }
1648 final String files[] = filePathsToSync.toArray(new String[filePathsToSync.size()]);
1649 DeviceAction syncAction = new DeviceAction() {
1650 @Override
1651 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1652 SyncException {
1653 SyncService syncService = null;
1654 boolean status = false;
1655 try {
1656 syncService = getIDevice().getSyncService();
1657 syncService.push(files, remoteFileEntry.getFileEntry(),
1658 SyncService.getNullProgressMonitor());
1659 status = true;
1660 } catch (SyncException e) {
1661 CLog.w("Failed to sync files to %s on device %s. Message %s",
1662 remoteFileEntry.getFullPath(), getSerialNumber(), e.getMessage());
1663 throw e;
1664 } finally {
1665 if (syncService != null) {
1666 syncService.close();
1667 }
1668 }
1669 return status;
1670 }
1671 };
1672 return performDeviceAction(String.format("sync files %s", remoteFileEntry.getFullPath()),
1673 syncAction, MAX_RETRY_ATTEMPTS);
1674 }
1675
1676 /**
1677 * Queries the file listing service for a given directory
1678 *
1679 * @param remoteFileEntry
1680 * @throws DeviceNotAvailableException
1681 */
1682 FileEntry[] getFileChildren(final FileEntry remoteFileEntry)
1683 throws DeviceNotAvailableException {
1684 // time this operation because its known to hang
1685 FileQueryAction action = new FileQueryAction(remoteFileEntry,
1686 getIDevice().getFileListingService());
1687 performDeviceAction("buildFileCache", action, MAX_RETRY_ATTEMPTS);
1688 return action.mFileContents;
1689 }
1690
1691 private class FileQueryAction implements DeviceAction {
1692
1693 FileEntry[] mFileContents = null;
1694 private final FileEntry mRemoteFileEntry;
1695 private final FileListingService mService;
1696
1697 FileQueryAction(FileEntry remoteFileEntry, FileListingService service) {
1698 throwIfNull(remoteFileEntry);
1699 throwIfNull(service);
1700 mRemoteFileEntry = remoteFileEntry;
1701 mService = service;
1702 }
1703
1704 @Override
1705 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1706 ShellCommandUnresponsiveException {
1707 mFileContents = mService.getChildrenSync(mRemoteFileEntry);
1708 return true;
1709 }
1710 }
1711
1712 /**
1713 * A {@link FilenameFilter} that rejects hidden (ie starts with ".") files.
1714 */
1715 private static class NoHiddenFilesFilter implements FilenameFilter {
1716 /**
1717 * {@inheritDoc}
1718 */
1719 @Override
1720 public boolean accept(File dir, String name) {
1721 return !name.startsWith(".");
1722 }
1723 }
1724
1725 /**
Julien Desprez7c15ac42016-08-10 16:45:44 +01001726 * helper to get the timezone from the device. Example: "Europe/London"
Julien Desprez6961b272016-02-01 09:58:23 +00001727 */
Julien Desprez7c15ac42016-08-10 16:45:44 +01001728 private String getDeviceTimezone() {
Julien Desprez6961b272016-02-01 09:58:23 +00001729 try {
Julien Desprez7c15ac42016-08-10 16:45:44 +01001730 // This may not be set at first, default to GMT in this case.
1731 String timezone = getProperty("persist.sys.timezone");
1732 if (timezone != null) {
1733 return timezone.trim();
1734 }
1735 } catch (DeviceNotAvailableException e) {
1736 // Fall through on purpose
1737 }
1738 return "GMT";
1739 }
1740
1741 /**
Julien Desprez75518d32016-11-18 09:54:34 +00001742 * Return <code>true</code> if local file is newer than remote file. {@link IFileEntry} being
1743 * accurate to the minute, in case of equal times, the file will be considered newer.
Julien Desprez7c15ac42016-08-10 16:45:44 +01001744 */
Jeffrey Lu279122f2018-01-29 17:25:08 -08001745 @VisibleForTesting
Julien Desprez7c15ac42016-08-10 16:45:44 +01001746 protected boolean isNewer(File localFile, IFileEntry entry) {
1747 final String entryTimeString = String.format("%s %s", entry.getDate(), entry.getTime());
1748 try {
1749 String timezone = getDeviceTimezone();
Julien Desprez6961b272016-02-01 09:58:23 +00001750 // expected format of a FileEntry's date and time
Julien Desprez7c15ac42016-08-10 16:45:44 +01001751 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
1752 format.setTimeZone(TimeZone.getTimeZone(timezone));
Julien Desprez6961b272016-02-01 09:58:23 +00001753 Date remoteDate = format.parse(entryTimeString);
Julien Desprez7c15ac42016-08-10 16:45:44 +01001754
1755 long offset = 0;
1756 try {
1757 offset = getDeviceTimeOffset(null);
1758 } catch (DeviceNotAvailableException e) {
1759 offset = 0;
1760 }
1761 CLog.i("Device offset time: %s", offset);
1762
Julien Desprez6961b272016-02-01 09:58:23 +00001763 // localFile.lastModified has granularity of ms, but remoteDate.getTime only has
1764 // granularity of minutes. Shift remoteDate.getTime() backward by one minute so newly
1765 // modified files get synced
Julien Desprez5aa8c6e2016-10-27 10:12:13 +01001766 return localFile.lastModified() > (remoteDate.getTime() - 60 * 1000 + offset);
Julien Desprez6961b272016-02-01 09:58:23 +00001767 } catch (ParseException e) {
1768 CLog.e("Error converting remote time stamp %s for %s on device %s", entryTimeString,
1769 entry.getFullPath(), getSerialNumber());
1770 }
1771 // sync file by default
1772 return true;
1773 }
1774
1775 /**
1776 * {@inheritDoc}
1777 */
1778 @Override
1779 public String executeAdbCommand(String... cmdArgs) throws DeviceNotAvailableException {
1780 final String[] fullCmd = buildAdbCommand(cmdArgs);
1781 AdbAction adbAction = new AdbAction(fullCmd);
1782 performDeviceAction(String.format("adb %s", cmdArgs[0]), adbAction, MAX_RETRY_ATTEMPTS);
1783 return adbAction.mOutput;
1784 }
1785
jdesprez3d7b9142018-03-02 12:11:23 -08001786
Julien Desprez6961b272016-02-01 09:58:23 +00001787 /**
1788 * {@inheritDoc}
1789 */
1790 @Override
1791 public CommandResult executeFastbootCommand(String... cmdArgs)
1792 throws DeviceNotAvailableException, UnsupportedOperationException {
1793 return doFastbootCommand(getCommandTimeout(), cmdArgs);
1794 }
1795
1796 /**
1797 * {@inheritDoc}
1798 */
1799 @Override
Julien Desprezf7d1e0d2016-06-01 09:32:38 +01001800 public CommandResult executeFastbootCommand(long timeout, String... cmdArgs)
1801 throws DeviceNotAvailableException, UnsupportedOperationException {
1802 return doFastbootCommand(timeout, cmdArgs);
1803 }
1804
1805 /**
1806 * {@inheritDoc}
1807 */
1808 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001809 public CommandResult executeLongFastbootCommand(String... cmdArgs)
1810 throws DeviceNotAvailableException, UnsupportedOperationException {
1811 return doFastbootCommand(getLongCommandTimeout(), cmdArgs);
1812 }
1813
1814 /**
1815 * @param cmdArgs
1816 * @throws DeviceNotAvailableException
1817 */
1818 private CommandResult doFastbootCommand(final long timeout, String... cmdArgs)
1819 throws DeviceNotAvailableException, UnsupportedOperationException {
1820 if (!mFastbootEnabled) {
1821 throw new UnsupportedOperationException(String.format(
1822 "Attempted to fastboot on device %s , but fastboot is not available. Aborting.",
1823 getSerialNumber()));
1824 }
1825 final String[] fullCmd = buildFastbootCommand(cmdArgs);
1826 for (int i = 0; i < MAX_RETRY_ATTEMPTS; i++) {
Guang Zhu98e1e5c2017-11-03 09:47:37 -07001827 File fastbootTmpDir = getHostOptions().getFastbootTmpDir();
Guang Zhu26cca482017-11-01 18:18:08 -07001828 IRunUtil runUtil = null;
1829 if (fastbootTmpDir != null) {
1830 runUtil = new RunUtil();
Guang Zhu98e1e5c2017-11-03 09:47:37 -07001831 runUtil.setEnvVariable("TMPDIR", fastbootTmpDir.getAbsolutePath());
Guang Zhu26cca482017-11-01 18:18:08 -07001832 } else {
1833 runUtil = getRunUtil();
1834 }
Julien Desprez6961b272016-02-01 09:58:23 +00001835 CommandResult result = new CommandResult(CommandStatus.EXCEPTION);
1836 // block state changes while executing a fastboot command, since
1837 // device will disappear from fastboot devices while command is being executed
1838 mFastbootLock.lock();
1839 try {
Guang Zhu26cca482017-11-01 18:18:08 -07001840 result = runUtil.runTimedCmd(timeout, fullCmd);
Julien Desprez6961b272016-02-01 09:58:23 +00001841 } finally {
1842 mFastbootLock.unlock();
1843 }
1844 if (!isRecoveryNeeded(result)) {
1845 return result;
1846 }
1847 CLog.w("Recovery needed after executing fastboot command");
1848 if (result != null) {
1849 CLog.v("fastboot command output:\nstdout: %s\nstderr:%s",
1850 result.getStdout(), result.getStderr());
1851 }
1852 recoverDeviceFromBootloader();
1853 }
1854 throw new DeviceUnresponsiveException(String.format("Attempted fastboot %s multiple "
1855 + "times on device %s without communication success. Aborting.", cmdArgs[0],
Julien Desprez0c6c77c2016-05-31 16:35:57 +01001856 getSerialNumber()), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00001857 }
1858
1859 /**
1860 * {@inheritDoc}
1861 */
1862 @Override
1863 public boolean getUseFastbootErase() {
1864 return mOptions.getUseFastbootErase();
1865 }
1866
1867 /**
1868 * {@inheritDoc}
1869 */
1870 @Override
1871 public void setUseFastbootErase(boolean useFastbootErase) {
1872 mOptions.setUseFastbootErase(useFastbootErase);
1873 }
1874
1875 /**
1876 * {@inheritDoc}
1877 */
1878 @Override
1879 public CommandResult fastbootWipePartition(String partition)
1880 throws DeviceNotAvailableException {
1881 if (mOptions.getUseFastbootErase()) {
1882 return executeLongFastbootCommand("erase", partition);
1883 } else {
1884 return executeLongFastbootCommand("format", partition);
1885 }
1886 }
1887
1888 /**
1889 * Evaluate the given fastboot result to determine if recovery mode needs to be entered
1890 *
1891 * @param fastbootResult the {@link CommandResult} from a fastboot command
1892 * @return <code>true</code> if recovery mode should be entered, <code>false</code> otherwise.
1893 */
1894 private boolean isRecoveryNeeded(CommandResult fastbootResult) {
1895 if (fastbootResult.getStatus().equals(CommandStatus.TIMED_OUT)) {
1896 // fastboot commands always time out if devices is not present
1897 return true;
1898 } else {
1899 // check for specific error messages in result that indicate bad device communication
1900 // and recovery mode is needed
1901 if (fastbootResult.getStderr() == null ||
1902 fastbootResult.getStderr().contains("data transfer failure (Protocol error)") ||
1903 fastbootResult.getStderr().contains("status read failed (No such device)")) {
1904 CLog.w("Bad fastboot response from device %s. stderr: %s. Entering recovery",
1905 getSerialNumber(), fastbootResult.getStderr());
1906 return true;
1907 }
1908 }
1909 return false;
1910 }
1911
jdesprez3d7b9142018-03-02 12:11:23 -08001912 /** Get the max time allowed in ms for commands. */
1913 long getCommandTimeout() {
Julien Desprez6961b272016-02-01 09:58:23 +00001914 return mCmdTimeout;
1915 }
1916
1917 /**
1918 * Set the max time allowed in ms for commands.
1919 */
1920 void setLongCommandTimeout(long timeout) {
1921 mLongCmdTimeout = timeout;
1922 }
1923
1924 /**
1925 * Get the max time allowed in ms for commands.
1926 */
1927 long getLongCommandTimeout() {
1928 return mLongCmdTimeout;
1929 }
1930
jdesprez3d7b9142018-03-02 12:11:23 -08001931 /** Set the max time allowed in ms for commands. */
1932 void setCommandTimeout(long timeout) {
Julien Desprez6961b272016-02-01 09:58:23 +00001933 mCmdTimeout = timeout;
1934 }
1935
1936 /**
1937 * Builds the OS command for the given adb command and args
1938 */
1939 private String[] buildAdbCommand(String... commandArgs) {
1940 return ArrayUtil.buildArray(new String[] {"adb", "-s", getSerialNumber()},
1941 commandArgs);
1942 }
1943
jdesprez3d7b9142018-03-02 12:11:23 -08001944 /** Builds the OS command for the given adb shell command session and args */
1945 private String[] buildAdbShellCommand(String command) {
1946 // TODO: implement the shell v2 support in ddmlib itself.
Julien Desprez1dc6b392019-03-18 11:49:13 -07001947 String[] commandArgs =
1948 QuotationAwareTokenizer.tokenizeLine(
1949 command,
1950 /** No logging */
1951 false);
jdesprez3d7b9142018-03-02 12:11:23 -08001952 return ArrayUtil.buildArray(
1953 new String[] {"adb", "-s", getSerialNumber(), "shell"}, commandArgs);
1954 }
1955
Julien Desprez6961b272016-02-01 09:58:23 +00001956 /**
1957 * Builds the OS command for the given fastboot command and args
1958 */
1959 private String[] buildFastbootCommand(String... commandArgs) {
Julien Desprez0a7d67d2016-07-21 16:05:57 +01001960 return ArrayUtil.buildArray(new String[] {getFastbootPath(), "-s", getSerialNumber()},
Julien Desprez6961b272016-02-01 09:58:23 +00001961 commandArgs);
1962 }
1963
1964 /**
1965 * Performs an action on this device. Attempts to recover device and optionally retry command
1966 * if action fails.
1967 *
1968 * @param actionDescription a short description of action to be performed. Used for logging
1969 * purposes only.
1970 * @param action the action to be performed
1971 * @param retryAttempts the retry attempts to make for action if it fails but
1972 * recovery succeeds
Julien Desprezd0c379a2016-11-04 11:00:54 +00001973 * @return <code>true</code> if action was performed successfully
Julien Desprez6961b272016-02-01 09:58:23 +00001974 * @throws DeviceNotAvailableException if recovery attempt fails or max attempts done without
1975 * success
1976 */
Julien Desprezc8474552016-02-17 10:59:27 +00001977 protected boolean performDeviceAction(String actionDescription, final DeviceAction action,
Julien Desprez6961b272016-02-01 09:58:23 +00001978 int retryAttempts) throws DeviceNotAvailableException {
1979
1980 for (int i = 0; i < retryAttempts + 1; i++) {
1981 try {
1982 return action.run();
1983 } catch (TimeoutException e) {
1984 logDeviceActionException(actionDescription, e);
1985 } catch (IOException e) {
1986 logDeviceActionException(actionDescription, e);
1987 } catch (InstallException e) {
1988 logDeviceActionException(actionDescription, e);
1989 } catch (SyncException e) {
1990 logDeviceActionException(actionDescription, e);
1991 // a SyncException is not necessarily a device communication problem
1992 // do additional diagnosis
1993 if (!e.getErrorCode().equals(SyncError.BUFFER_OVERRUN) &&
1994 !e.getErrorCode().equals(SyncError.TRANSFER_PROTOCOL_ERROR)) {
1995 // this is a logic problem, doesn't need recovery or to be retried
1996 return false;
1997 }
1998 } catch (AdbCommandRejectedException e) {
1999 logDeviceActionException(actionDescription, e);
2000 } catch (ShellCommandUnresponsiveException e) {
2001 CLog.w("Device %s stopped responding when attempting %s", getSerialNumber(),
2002 actionDescription);
2003 }
2004 // TODO: currently treat all exceptions the same. In future consider different recovery
2005 // mechanisms for time out's vs IOExceptions
2006 recoverDevice();
2007 }
2008 if (retryAttempts > 0) {
2009 throw new DeviceUnresponsiveException(String.format("Attempted %s multiple times "
2010 + "on device %s without communication success. Aborting.", actionDescription,
Julien Desprez0c6c77c2016-05-31 16:35:57 +01002011 getSerialNumber()), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00002012 }
2013 return false;
2014 }
2015
2016 /**
2017 * Log an entry for given exception
2018 *
2019 * @param actionDescription the action's description
2020 * @param e the exception
2021 */
2022 private void logDeviceActionException(String actionDescription, Exception e) {
2023 CLog.w("%s (%s) when attempting %s on device %s", e.getClass().getSimpleName(),
2024 getExceptionMessage(e), actionDescription, getSerialNumber());
2025 }
2026
2027 /**
2028 * Make a best effort attempt to retrieve a meaningful short descriptive message for given
2029 * {@link Exception}
2030 *
2031 * @param e the {@link Exception}
2032 * @return a short message
2033 */
2034 private String getExceptionMessage(Exception e) {
2035 StringBuilder msgBuilder = new StringBuilder();
2036 if (e.getMessage() != null) {
2037 msgBuilder.append(e.getMessage());
2038 }
2039 if (e.getCause() != null) {
2040 msgBuilder.append(" cause: ");
2041 msgBuilder.append(e.getCause().getClass().getSimpleName());
2042 if (e.getCause().getMessage() != null) {
2043 msgBuilder.append(" (");
2044 msgBuilder.append(e.getCause().getMessage());
2045 msgBuilder.append(")");
2046 }
2047 }
2048 return msgBuilder.toString();
2049 }
2050
2051 /**
2052 * Attempts to recover device communication.
2053 *
2054 * @throws DeviceNotAvailableException if device is not longer available
2055 */
2056 @Override
2057 public void recoverDevice() throws DeviceNotAvailableException {
2058 if (mRecoveryMode.equals(RecoveryMode.NONE)) {
2059 CLog.i("Skipping recovery on %s", getSerialNumber());
2060 getRunUtil().sleep(NONE_RECOVERY_MODE_DELAY);
2061 return;
2062 }
2063 CLog.i("Attempting recovery on %s", getSerialNumber());
Julien Desprez7a7d97e2016-02-05 12:27:49 +00002064 try {
2065 mRecovery.recoverDevice(mStateMonitor, mRecoveryMode.equals(RecoveryMode.ONLINE));
2066 } catch (DeviceUnresponsiveException due) {
2067 RecoveryMode previousRecoveryMode = mRecoveryMode;
2068 mRecoveryMode = RecoveryMode.NONE;
Julien Desprez83f93f02019-04-22 08:59:35 -07002069 try {
2070 boolean enabled = enableAdbRoot();
2071 CLog.d("Device Unresponsive during recovery, is root still enabled: %s", enabled);
2072 } catch (DeviceUnresponsiveException e) {
2073 // Ignore exception thrown here to rethrow original exception.
2074 CLog.e("Exception occurred during recovery adb root:");
2075 CLog.e(e);
2076 }
Julien Desprez7a7d97e2016-02-05 12:27:49 +00002077 mRecoveryMode = previousRecoveryMode;
2078 throw due;
2079 }
Julien Desprez6961b272016-02-01 09:58:23 +00002080 if (mRecoveryMode.equals(RecoveryMode.AVAILABLE)) {
2081 // turn off recovery mode to prevent reentrant recovery
2082 // TODO: look for a better way to handle this, such as doing postBootUp steps in
2083 // recovery itself
2084 mRecoveryMode = RecoveryMode.NONE;
2085 // this might be a runtime reset - still need to run post boot setup steps
2086 if (isEncryptionSupported() && isDeviceEncrypted()) {
2087 unlockDevice();
2088 }
2089 postBootSetup();
2090 mRecoveryMode = RecoveryMode.AVAILABLE;
2091 } else if (mRecoveryMode.equals(RecoveryMode.ONLINE)) {
2092 // turn off recovery mode to prevent reentrant recovery
2093 // TODO: look for a better way to handle this, such as doing postBootUp steps in
2094 // recovery itself
2095 mRecoveryMode = RecoveryMode.NONE;
2096 enableAdbRoot();
2097 mRecoveryMode = RecoveryMode.ONLINE;
2098 }
2099 CLog.i("Recovery successful for %s", getSerialNumber());
2100 }
2101
2102 /**
2103 * Attempts to recover device fastboot communication.
2104 *
2105 * @throws DeviceNotAvailableException if device is not longer available
2106 */
2107 private void recoverDeviceFromBootloader() throws DeviceNotAvailableException {
2108 CLog.i("Attempting recovery on %s in bootloader", getSerialNumber());
2109 mRecovery.recoverDeviceBootloader(mStateMonitor);
2110 CLog.i("Bootloader recovery successful for %s", getSerialNumber());
2111 }
2112
2113 private void recoverDeviceInRecovery() throws DeviceNotAvailableException {
2114 CLog.i("Attempting recovery on %s in recovery", getSerialNumber());
2115 mRecovery.recoverDeviceRecovery(mStateMonitor);
2116 CLog.i("Recovery mode recovery successful for %s", getSerialNumber());
2117 }
2118
2119 /**
2120 * {@inheritDoc}
2121 */
2122 @Override
2123 public void startLogcat() {
2124 if (mLogcatReceiver != null) {
2125 CLog.d("Already capturing logcat for %s, ignoring", getSerialNumber());
2126 return;
2127 }
2128 mLogcatReceiver = createLogcatReceiver();
2129 mLogcatReceiver.start();
2130 }
2131
2132 /**
2133 * {@inheritDoc}
2134 */
2135 @Override
2136 public void clearLogcat() {
2137 if (mLogcatReceiver != null) {
2138 mLogcatReceiver.clear();
2139 }
2140 }
2141
jdesprezd0e00da2017-09-07 18:54:49 -07002142 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00002143 @Override
jdesprezd0e00da2017-09-07 18:54:49 -07002144 @SuppressWarnings("MustBeClosedChecker")
Julien Desprez6961b272016-02-01 09:58:23 +00002145 public InputStreamSource getLogcat() {
2146 if (mLogcatReceiver == null) {
2147 CLog.w("Not capturing logcat for %s in background, returning a logcat dump",
2148 getSerialNumber());
2149 return getLogcatDump();
2150 } else {
2151 return mLogcatReceiver.getLogcatData();
2152 }
2153 }
2154
jdesprezd0e00da2017-09-07 18:54:49 -07002155 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00002156 @Override
jdesprezcdd03352017-10-06 18:04:49 -07002157 @SuppressWarnings("MustBeClosedChecker")
Julien Desprez6961b272016-02-01 09:58:23 +00002158 public InputStreamSource getLogcat(int maxBytes) {
2159 if (mLogcatReceiver == null) {
2160 CLog.w("Not capturing logcat for %s in background, returning a logcat dump "
2161 + "ignoring size", getSerialNumber());
2162 return getLogcatDump();
2163 } else {
2164 return mLogcatReceiver.getLogcatData(maxBytes);
2165 }
2166 }
2167
2168 /**
2169 * {@inheritDoc}
2170 */
2171 @Override
Julien Desprezdcb19d52016-06-20 12:23:12 +01002172 public InputStreamSource getLogcatSince(long date) {
2173 try {
2174 if (getApiLevel() <= 22) {
2175 CLog.i("Api level too low to use logcat -t 'time' reverting to dump");
2176 return getLogcatDump();
2177 }
2178 } catch (DeviceNotAvailableException e) {
2179 // For convenience of interface, we catch the DNAE here.
2180 CLog.e(e);
2181 return getLogcatDump();
2182 }
2183
jdesprez0b9d69e2017-12-11 03:40:39 -08002184 // Convert date to format needed by the command:
jdesprez6a2f1d22018-03-20 11:31:54 -07002185 // 'MM-DD HH:mm:ss.mmm' or 'YYYY-MM-DD HH:mm:ss.mmm'
2186 SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss.mmm");
jdesprez0b9d69e2017-12-11 03:40:39 -08002187 String dateFormatted = format.format(new Date(date));
2188
Julien Desprezdcb19d52016-06-20 12:23:12 +01002189 byte[] output = new byte[0];
2190 try {
2191 // use IDevice directly because we don't want callers to handle
2192 // DeviceNotAvailableException for this method
2193 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
jdesprez0b9d69e2017-12-11 03:40:39 -08002194 String command = String.format("%s -t '%s'", LogcatReceiver.LOGCAT_CMD, dateFormatted);
Julien Desprezdcb19d52016-06-20 12:23:12 +01002195 getIDevice().executeShellCommand(command, receiver);
2196 output = receiver.getOutput();
2197 } catch (IOException|AdbCommandRejectedException|
2198 ShellCommandUnresponsiveException|TimeoutException e) {
2199 CLog.w("Failed to get logcat dump from %s: %s", getSerialNumber(), e.getMessage());
2200 CLog.e(e);
2201 }
2202 return new ByteArrayInputStreamSource(output);
2203 }
2204
2205 /**
2206 * {@inheritDoc}
2207 */
2208 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00002209 public InputStreamSource getLogcatDump() {
2210 byte[] output = new byte[0];
2211 try {
2212 // use IDevice directly because we don't want callers to handle
2213 // DeviceNotAvailableException for this method
2214 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
2215 // add -d parameter to make this a non blocking call
Julien Desprez73c55bf2016-09-01 09:27:37 +01002216 getIDevice().executeShellCommand(LogcatReceiver.LOGCAT_CMD + " -d", receiver,
2217 LOGCAT_DUMP_TIMEOUT, TimeUnit.MILLISECONDS);
Julien Desprez6961b272016-02-01 09:58:23 +00002218 output = receiver.getOutput();
2219 } catch (IOException e) {
2220 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2221 } catch (TimeoutException e) {
2222 CLog.w("Failed to get logcat dump from %s: timeout", getSerialNumber());
2223 } catch (AdbCommandRejectedException e) {
2224 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2225 } catch (ShellCommandUnresponsiveException e) {
2226 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2227 }
2228 return new ByteArrayInputStreamSource(output);
2229 }
2230
2231 /**
2232 * {@inheritDoc}
2233 */
2234 @Override
2235 public void stopLogcat() {
2236 if (mLogcatReceiver != null) {
2237 mLogcatReceiver.stop();
2238 mLogcatReceiver = null;
2239 } else {
2240 CLog.w("Attempting to stop logcat when not capturing for %s", getSerialNumber());
2241 }
2242 }
2243
Jeffrey Lu279122f2018-01-29 17:25:08 -08002244 /** Factory method to create a {@link LogcatReceiver}. */
2245 @VisibleForTesting
Julien Desprez6961b272016-02-01 09:58:23 +00002246 LogcatReceiver createLogcatReceiver() {
2247 String logcatOptions = mOptions.getLogcatOptions();
2248 if (logcatOptions == null) {
2249 return new LogcatReceiver(this, mOptions.getMaxLogcatDataSize(), mLogStartDelay);
2250 } else {
2251 return new LogcatReceiver(this,
2252 String.format("%s %s", LogcatReceiver.LOGCAT_CMD, logcatOptions),
2253 mOptions.getMaxLogcatDataSize(), mLogStartDelay);
2254 }
2255 }
2256
2257 /**
2258 * {@inheritDoc}
2259 */
2260 @Override
2261 public InputStreamSource getBugreport() {
jdesprezc3537f72017-09-05 17:27:14 -07002262 if (getApiLevelSafe() < 24) {
2263 InputStreamSource bugreport = getBugreportInternal();
2264 if (bugreport == null) {
2265 // Safe call so we don't return null but an empty resource.
2266 return new ByteArrayInputStreamSource("".getBytes());
2267 }
2268 return bugreport;
Julien Desprez16184162016-06-10 08:56:17 +01002269 }
jdesprezc3537f72017-09-05 17:27:14 -07002270 CLog.d("Api level above 24, using bugreportz instead.");
2271 File mainEntry = null;
2272 File bugreportzFile = null;
2273 try {
2274 bugreportzFile = getBugreportzInternal();
2275 if (bugreportzFile == null) {
2276 bugreportzFile = bugreportzFallback();
Julien Desprez15de6812016-08-08 15:08:06 +01002277 }
jdesprezc3537f72017-09-05 17:27:14 -07002278 if (bugreportzFile == null) {
2279 // return empty buffer
2280 return new ByteArrayInputStreamSource("".getBytes());
Julien Desprez15de6812016-08-08 15:08:06 +01002281 }
jdesprezc3537f72017-09-05 17:27:14 -07002282 try (ZipFile zip = new ZipFile(bugreportzFile)) {
2283 // We get the main_entry.txt that contains the bugreport name.
2284 mainEntry = ZipUtil2.extractFileFromZip(zip, "main_entry.txt");
2285 String bugreportName = FileUtil.readStringFromFile(mainEntry).trim();
2286 CLog.d("bugreport name: '%s'", bugreportName);
2287 File bugreport = ZipUtil2.extractFileFromZip(zip, bugreportName);
2288 return new FileInputStreamSource(bugreport, true);
2289 }
2290 } catch (IOException e) {
2291 CLog.e("Error while unzipping bugreportz");
2292 CLog.e(e);
2293 return new ByteArrayInputStreamSource("corrupted bugreport.".getBytes());
2294 } finally {
2295 FileUtil.deleteFile(bugreportzFile);
2296 FileUtil.deleteFile(mainEntry);
Julien Desprez15de6812016-08-08 15:08:06 +01002297 }
Julien Desprez16184162016-06-10 08:56:17 +01002298 }
2299
2300 /**
jdesprezc3537f72017-09-05 17:27:14 -07002301 * If first bugreportz collection was interrupted for any reasons, the temporary file where the
2302 * dumpstate is redirected could exists if it started. We attempt to get it to have some partial
2303 * data.
Julien Desprez56d044d2016-08-17 12:28:10 +01002304 */
jdesprezc3537f72017-09-05 17:27:14 -07002305 private File bugreportzFallback() {
Julien Desprez56d044d2016-08-17 12:28:10 +01002306 try {
2307 IFileEntry entries = getFileEntry(BUGREPORTZ_TMP_PATH);
2308 if (entries != null) {
2309 for (IFileEntry f : entries.getChildren(false)) {
2310 String name = f.getName();
Julien Desprezf20644b2016-08-31 09:31:27 +01002311 CLog.d("bugreport entry: %s", name);
jdesprezc3537f72017-09-05 17:27:14 -07002312 // Only get left-over zipped data to avoid confusing data types.
2313 if (name.endsWith(".zip")) {
Julien Desprez96c00252018-06-06 03:25:40 -07002314 File pulledZip = pullFile(BUGREPORTZ_TMP_PATH + name);
2315 try {
2316 // Validate the zip before returning it.
2317 if (ZipUtil.isZipFileValid(pulledZip, false)) {
2318 return pulledZip;
2319 }
2320 } catch (IOException e) {
2321 CLog.e(e);
2322 }
2323 CLog.w("Failed to get a valid bugreportz.");
2324 // if zip validation failed, delete it and return null.
2325 FileUtil.deleteFile(pulledZip);
2326 return null;
2327
Julien Desprez56d044d2016-08-17 12:28:10 +01002328 }
2329 }
Julien Desprezf20644b2016-08-31 09:31:27 +01002330 CLog.w("Could not find a tmp bugreport file in the directory.");
2331 } else {
2332 CLog.w("Could not find the file entry: '%s' on the device.", BUGREPORTZ_TMP_PATH);
Julien Desprez56d044d2016-08-17 12:28:10 +01002333 }
2334 } catch (DeviceNotAvailableException e) {
2335 CLog.e(e);
2336 }
jdesprezc3537f72017-09-05 17:27:14 -07002337 return null;
Julien Desprez56d044d2016-08-17 12:28:10 +01002338 }
2339
2340 /**
Julien Desprez16184162016-06-10 08:56:17 +01002341 * {@inheritDoc}
2342 */
2343 @Override
Julien Desprez0c836c92016-08-10 14:40:41 +01002344 public boolean logBugreport(String dataName, ITestLogger listener) {
jdesprezc3537f72017-09-05 17:27:14 -07002345 InputStreamSource bugreport = null;
2346 LogDataType type = null;
Julien Desprez0c836c92016-08-10 14:40:41 +01002347 try {
jdesprezc3537f72017-09-05 17:27:14 -07002348 bugreport = getBugreportz();
2349 type = LogDataType.BUGREPORTZ;
2350
Julien Desprez0c836c92016-08-10 14:40:41 +01002351 if (bugreport == null) {
jdesprezc3537f72017-09-05 17:27:14 -07002352 CLog.d("Bugreportz failed, attempting bugreport collection instead.");
2353 bugreport = getBugreportInternal();
Julien Desprez0c836c92016-08-10 14:40:41 +01002354 type = LogDataType.BUGREPORT;
2355 }
jdesprezc3537f72017-09-05 17:27:14 -07002356 // log what we managed to capture.
Julien Desprez0c836c92016-08-10 14:40:41 +01002357 if (bugreport != null) {
2358 listener.testLog(dataName, type, bugreport);
2359 return true;
2360 }
2361 } finally {
2362 StreamUtil.cancel(bugreport);
2363 }
jdesprezc3537f72017-09-05 17:27:14 -07002364 CLog.d(
2365 "logBugreport() was not successful in collecting and logging the bugreport "
2366 + "for device %s",
2367 getSerialNumber());
Julien Desprez0c836c92016-08-10 14:40:41 +01002368 return false;
2369 }
2370
2371 /**
2372 * {@inheritDoc}
2373 */
2374 @Override
2375 public Bugreport takeBugreport() {
jdesprezc3537f72017-09-05 17:27:14 -07002376 File bugreportFile = null;
2377 int apiLevel = getApiLevelSafe();
2378 if (apiLevel == UNKNOWN_API_LEVEL) {
Julien Desprez0c836c92016-08-10 14:40:41 +01002379 return null;
2380 }
jdesprezc3537f72017-09-05 17:27:14 -07002381 if (apiLevel >= 24) {
2382 CLog.d("Api level above 24, using bugreportz.");
Julien Desprez0c836c92016-08-10 14:40:41 +01002383 bugreportFile = getBugreportzInternal();
2384 if (bugreportFile != null) {
2385 return new Bugreport(bugreportFile, true);
Julien Desprez0c836c92016-08-10 14:40:41 +01002386 }
jdesprezc3537f72017-09-05 17:27:14 -07002387 return null;
Julien Desprez0c836c92016-08-10 14:40:41 +01002388 }
jdesprezc3537f72017-09-05 17:27:14 -07002389 // fall back to regular bugreport
2390 InputStreamSource bugreport = getBugreportInternal();
2391 if (bugreport == null) {
2392 CLog.e("Error when collecting the bugreport.");
2393 return null;
2394 }
2395 try {
2396 bugreportFile = FileUtil.createTempFile("bugreport", ".txt");
2397 FileUtil.writeToFile(bugreport.createInputStream(), bugreportFile);
2398 return new Bugreport(bugreportFile, false);
2399 } catch (IOException e) {
2400 CLog.e("Error when writing the bugreport file");
2401 CLog.e(e);
2402 }
2403 return null;
Julien Desprez0c836c92016-08-10 14:40:41 +01002404 }
2405
2406 /**
2407 * {@inheritDoc}
2408 */
2409 @Override
Julien Desprez16184162016-06-10 08:56:17 +01002410 public InputStreamSource getBugreportz() {
jdesprezc3537f72017-09-05 17:27:14 -07002411 if (getApiLevelSafe() < 24) {
2412 return null;
2413 }
2414 File bugreportZip = getBugreportzInternal();
2415 if (bugreportZip == null) {
2416 bugreportZip = bugreportzFallback();
2417 }
2418 if (bugreportZip != null) {
2419 return new FileInputStreamSource(bugreportZip, true);
Julien Desprez15de6812016-08-08 15:08:06 +01002420 }
2421 return null;
2422 }
2423
Jeffrey Lu279122f2018-01-29 17:25:08 -08002424 /** Internal Helper method to get the bugreportz zip file as a {@link File}. */
2425 @VisibleForTesting
Julien Desprez0c836c92016-08-10 14:40:41 +01002426 protected File getBugreportzInternal() {
Julien Desprez15de6812016-08-08 15:08:06 +01002427 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
Julien Desprez16184162016-06-10 08:56:17 +01002428 // Does not rely on {@link ITestDevice#executeAdbCommand(String...)} because it does not
2429 // provide a timeout.
Julien Desprez16184162016-06-10 08:56:17 +01002430 try {
2431 executeShellCommand(BUGREPORTZ_CMD, receiver,
Julien Desprezf54735c2016-08-16 09:17:07 +01002432 BUGREPORTZ_TIMEOUT, TimeUnit.MILLISECONDS, 0 /* don't retry */);
Julien Desprez16184162016-06-10 08:56:17 +01002433 String output = receiver.getOutput().trim();
2434 Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
2435 if (!match.find()) {
2436 CLog.e("Something went went wrong during bugreportz collection: '%s'", output);
2437 return null;
2438 } else {
2439 String remoteFilePath = match.group(2);
2440 File zipFile = null;
2441 try {
2442 if (!doesFileExist(remoteFilePath)) {
2443 CLog.e("Did not find bugreportz at: %s", remoteFilePath);
2444 return null;
2445 }
2446 // Create a placeholder to replace the file
2447 zipFile = FileUtil.createTempFile("bugreportz", ".zip");
2448 pullFile(remoteFilePath, zipFile);
2449 String bugreportDir =
2450 remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/'));
2451 if (!bugreportDir.isEmpty()) {
2452 // clean bugreport files directory on device
Julien Desprez901d4962019-03-27 10:10:23 -07002453 deleteFile(String.format("%s/*", bugreportDir));
Julien Desprez16184162016-06-10 08:56:17 +01002454 }
2455
Julien Desprez15de6812016-08-08 15:08:06 +01002456 return zipFile;
Julien Desprez16184162016-06-10 08:56:17 +01002457 } catch (IOException e) {
2458 CLog.e("Failed to create the temporary file.");
2459 return null;
2460 }
2461 }
2462 } catch (DeviceNotAvailableException e) {
2463 CLog.e("Device %s became unresponsive while retrieving bugreportz", getSerialNumber());
2464 CLog.e(e);
2465 }
2466 return null;
Julien Desprez6961b272016-02-01 09:58:23 +00002467 }
2468
jdesprezc3537f72017-09-05 17:27:14 -07002469 protected InputStreamSource getBugreportInternal() {
2470 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
2471 try {
2472 executeShellCommand(
2473 BUGREPORT_CMD,
2474 receiver,
2475 BUGREPORT_TIMEOUT,
2476 TimeUnit.MILLISECONDS,
2477 0 /* don't retry */);
2478 } catch (DeviceNotAvailableException e) {
2479 // Log, but don't throw, so the caller can get the bugreport contents even
2480 // if the device goes away
2481 CLog.e("Device %s became unresponsive while retrieving bugreport", getSerialNumber());
2482 return null;
2483 }
2484 return new ByteArrayInputStreamSource(receiver.getOutput());
2485 }
2486
Julien Desprez6961b272016-02-01 09:58:23 +00002487 /**
2488 * {@inheritDoc}
2489 */
2490 @Override
2491 public InputStreamSource getScreenshot() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002492 throw new UnsupportedOperationException("No support for Screenshot");
Julien Desprez6961b272016-02-01 09:58:23 +00002493 }
2494
2495 /**
2496 * {@inheritDoc}
2497 */
2498 @Override
2499 public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002500 throw new UnsupportedOperationException("No support for Screenshot");
Julien Desprez6961b272016-02-01 09:58:23 +00002501 }
2502
Insaf Latypov73f7aeb2017-03-03 11:05:08 +00002503 /** {@inheritDoc} */
2504 @Override
2505 public InputStreamSource getScreenshot(String format, boolean rescale)
2506 throws DeviceNotAvailableException {
2507 throw new UnsupportedOperationException("No support for Screenshot");
2508 }
2509
2510 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00002511 @Override
Julien Despreze722b232019-03-28 18:47:14 -07002512 public InputStreamSource getScreenshot(int displayId) throws DeviceNotAvailableException {
2513 throw new UnsupportedOperationException("No support for Screenshot");
2514 }
2515
2516 /** {@inheritDoc} */
2517 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00002518 public void clearLastConnectedWifiNetwork() {
2519 mLastConnectedWifiSsid = null;
2520 mLastConnectedWifiPsk = null;
2521 }
2522
2523 /**
2524 * {@inheritDoc}
2525 */
2526 @Override
2527 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk)
2528 throws DeviceNotAvailableException {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002529 return connectToWifiNetwork(wifiSsid, wifiPsk, false);
2530 }
2531
2532 /**
2533 * {@inheritDoc}
2534 */
2535 @Override
2536 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)
2537 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00002538 // Clears the last connected wifi network.
2539 mLastConnectedWifiSsid = null;
2540 mLastConnectedWifiPsk = null;
2541
2542 // Connects to wifi network. It retries up to {@link TestDeviceOptions@getWifiAttempts()}
Julien Desprez8539c082016-03-04 13:39:19 +00002543 // times
Julien Desprez6961b272016-02-01 09:58:23 +00002544 Random rnd = new Random();
2545 int backoffSlotCount = 2;
Jeffrey Lu279122f2018-01-29 17:25:08 -08002546 int slotTime = mOptions.getWifiRetryWaitTime();
2547 int waitTime = 0;
Julien Desprez6961b272016-02-01 09:58:23 +00002548 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002549 long startTime = mClock.millis();
2550 for (int i = 1; i <= mOptions.getWifiAttempts(); i++) {
2551 CLog.i("Connecting to wifi network %s on %s", wifiSsid, getSerialNumber());
2552 boolean success =
2553 wifi.connectToNetwork(wifiSsid, wifiPsk, mOptions.getConnCheckUrl(), scanSsid);
2554 final Map<String, String> wifiInfo = wifi.getWifiInfo();
2555 if (success) {
2556 CLog.i(
2557 "Successfully connected to wifi network %s(%s) on %s",
2558 wifiSsid, wifiInfo.get("bssid"), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00002559
Julien Desprezb1301842018-04-16 10:12:52 -07002560 mLastConnectedWifiSsid = wifiSsid;
2561 mLastConnectedWifiPsk = wifiPsk;
Julien Desprez6961b272016-02-01 09:58:23 +00002562
Julien Desprezb1301842018-04-16 10:12:52 -07002563 return true;
2564 } else {
2565 CLog.w(
2566 "Failed to connect to wifi network %s(%s) on %s on attempt %d of %d",
2567 wifiSsid,
2568 wifiInfo.get("bssid"),
2569 getSerialNumber(),
2570 i,
2571 mOptions.getWifiAttempts());
Julien Desprez6961b272016-02-01 09:58:23 +00002572 }
Julien Desprezb1301842018-04-16 10:12:52 -07002573 if (mClock.millis() - startTime >= mOptions.getMaxWifiConnectTime()) {
2574 CLog.e(
2575 "Failed to connect to wifi after %d ms. Aborting.",
2576 mOptions.getMaxWifiConnectTime());
2577 break;
2578 }
2579 if (i < mOptions.getWifiAttempts()) {
2580 if (mOptions.isWifiExpoRetryEnabled()) {
2581 // use binary exponential back-offs when retrying.
2582 waitTime = rnd.nextInt(backoffSlotCount) * slotTime;
2583 backoffSlotCount *= 2;
2584 }
2585 CLog.e("Waiting for %d ms before reconnecting to %s...", waitTime, wifiSsid);
2586 getRunUtil().sleep(waitTime);
2587 }
Julien Desprez6961b272016-02-01 09:58:23 +00002588 }
2589 return false;
2590 }
2591
2592 /**
2593 * {@inheritDoc}
2594 */
2595 @Override
2596 public boolean checkConnectivity() throws DeviceNotAvailableException {
2597 IWifiHelper wifi = createWifiHelper();
2598 return wifi.checkConnectivity(mOptions.getConnCheckUrl());
2599 }
2600
2601 /**
2602 * {@inheritDoc}
2603 */
2604 @Override
2605 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)
2606 throws DeviceNotAvailableException {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002607 return connectToWifiNetworkIfNeeded(wifiSsid, wifiPsk, false);
2608 }
2609
2610 /**
2611 * {@inheritDoc}
2612 */
2613 @Override
2614 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)
2615 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00002616 if (!checkConnectivity()) {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002617 return connectToWifiNetwork(wifiSsid, wifiPsk, scanSsid);
Julien Desprez6961b272016-02-01 09:58:23 +00002618 }
2619 return true;
2620 }
2621
2622 /**
2623 * {@inheritDoc}
2624 */
2625 @Override
2626 public boolean isWifiEnabled() throws DeviceNotAvailableException {
Julien Desprezac5f37f2017-02-13 10:41:22 +00002627 final IWifiHelper wifi = createWifiHelper();
Julien Desprez6961b272016-02-01 09:58:23 +00002628 try {
Julien Desprez6961b272016-02-01 09:58:23 +00002629 return wifi.isWifiEnabled();
2630 } catch (RuntimeException e) {
2631 CLog.w("Failed to create WifiHelper: %s", e.getMessage());
2632 return false;
2633 }
2634 }
2635
2636 /**
2637 * Checks that the device is currently successfully connected to given wifi SSID.
2638 *
2639 * @param wifiSSID the wifi ssid
2640 * @return <code>true</code> if device is currently connected to wifiSSID and has network
2641 * connectivity. <code>false</code> otherwise
2642 * @throws DeviceNotAvailableException if connection with device was lost
2643 */
2644 boolean checkWifiConnection(String wifiSSID) throws DeviceNotAvailableException {
2645 CLog.i("Checking connection with wifi network %s on %s", wifiSSID, getSerialNumber());
2646 final IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002647 // getSSID returns SSID as "SSID"
2648 final String quotedSSID = String.format("\"%s\"", wifiSSID);
Julien Desprez6961b272016-02-01 09:58:23 +00002649
Julien Desprezb1301842018-04-16 10:12:52 -07002650 boolean test = wifi.isWifiEnabled();
2651 CLog.v("%s: wifi enabled? %b", getSerialNumber(), test);
Julien Desprez6961b272016-02-01 09:58:23 +00002652
Julien Desprezb1301842018-04-16 10:12:52 -07002653 if (test) {
2654 final String actualSSID = wifi.getSSID();
2655 test = quotedSSID.equals(actualSSID);
2656 CLog.v("%s: SSID match (%s, %s, %b)", getSerialNumber(), quotedSSID, actualSSID, test);
Julien Desprez6961b272016-02-01 09:58:23 +00002657 }
Julien Desprezb1301842018-04-16 10:12:52 -07002658 if (test) {
2659 test = wifi.hasValidIp();
2660 CLog.v("%s: validIP? %b", getSerialNumber(), test);
2661 }
2662 if (test) {
2663 test = checkConnectivity();
2664 CLog.v("%s: checkConnectivity returned %b", getSerialNumber(), test);
2665 }
2666 return test;
Julien Desprez6961b272016-02-01 09:58:23 +00002667 }
2668
2669 /**
2670 * {@inheritDoc}
2671 */
2672 @Override
2673 public boolean disconnectFromWifi() throws DeviceNotAvailableException {
2674 CLog.i("Disconnecting from wifi on %s", getSerialNumber());
2675 // Clears the last connected wifi network.
2676 mLastConnectedWifiSsid = null;
2677 mLastConnectedWifiPsk = null;
2678
2679 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002680 return wifi.disconnectFromNetwork();
Julien Desprez6961b272016-02-01 09:58:23 +00002681 }
2682
2683 /**
2684 * {@inheritDoc}
2685 */
2686 @Override
2687 public String getIpAddress() throws DeviceNotAvailableException {
2688 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002689 return wifi.getIpAddress();
Julien Desprez6961b272016-02-01 09:58:23 +00002690 }
2691
2692 /**
2693 * {@inheritDoc}
2694 */
2695 @Override
2696 public boolean enableNetworkMonitor() throws DeviceNotAvailableException {
2697 mNetworkMonitorEnabled = false;
2698
2699 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002700 wifi.stopMonitor();
2701 if (wifi.startMonitor(NETWORK_MONITOR_INTERVAL, mOptions.getConnCheckUrl())) {
2702 mNetworkMonitorEnabled = true;
2703 return true;
Julien Desprez6961b272016-02-01 09:58:23 +00002704 }
2705 return false;
2706 }
2707
2708 /**
2709 * {@inheritDoc}
2710 */
2711 @Override
2712 public boolean disableNetworkMonitor() throws DeviceNotAvailableException {
2713 mNetworkMonitorEnabled = false;
2714
2715 IWifiHelper wifi = createWifiHelper();
2716 List<Long> samples = wifi.stopMonitor();
2717 if (!samples.isEmpty()) {
2718 int failures = 0;
2719 long totalLatency = 0;
2720 for (Long sample : samples) {
2721 if (sample < 0) {
2722 failures += 1;
2723 } else {
2724 totalLatency += sample;
2725 }
2726 }
2727 double failureRate = failures * 100.0 / samples.size();
2728 double avgLatency = 0.0;
2729 if (failures < samples.size()) {
2730 avgLatency = totalLatency / (samples.size() - failures);
2731 }
2732 CLog.d("[metric] url=%s, window=%ss, failure_rate=%.2f%%, latency_avg=%.2f",
2733 mOptions.getConnCheckUrl(), samples.size() * NETWORK_MONITOR_INTERVAL / 1000,
2734 failureRate, avgLatency);
2735 }
2736 return true;
2737 }
2738
2739 /**
2740 * Create a {@link WifiHelper} to use
Jeffrey Lu279122f2018-01-29 17:25:08 -08002741 *
2742 * <p>
2743 *
Julien Desprez021af1d2016-11-29 14:54:07 +00002744 * @throws DeviceNotAvailableException
Julien Desprez6961b272016-02-01 09:58:23 +00002745 */
Jeffrey Lu279122f2018-01-29 17:25:08 -08002746 @VisibleForTesting
Julien Desprez6961b272016-02-01 09:58:23 +00002747 IWifiHelper createWifiHelper() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002748 // current wifi helper won't work on AndroidNativeDevice
2749 // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when
2750 // we learn what is available.
2751 throw new UnsupportedOperationException("Wifi helper is not supported.");
Julien Desprez6961b272016-02-01 09:58:23 +00002752 }
2753
2754 /**
2755 * {@inheritDoc}
2756 */
2757 @Override
2758 public boolean clearErrorDialogs() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002759 throw new UnsupportedOperationException("No support for Screen's features");
Julien Desprez6961b272016-02-01 09:58:23 +00002760 }
2761
Julien Desprez14e96692017-01-12 12:31:29 +00002762 /** {@inheritDoc} */
2763 @Override
2764 public KeyguardControllerState getKeyguardState() throws DeviceNotAvailableException {
2765 throw new UnsupportedOperationException("No support for keyguard querying.");
2766 }
2767
Julien Desprez6961b272016-02-01 09:58:23 +00002768 IDeviceStateMonitor getDeviceStateMonitor() {
2769 return mStateMonitor;
2770 }
2771
2772 /**
2773 * {@inheritDoc}
2774 */
2775 @Override
2776 public void postBootSetup() throws DeviceNotAvailableException {
2777 enableAdbRoot();
Julien Desprezc8474552016-02-17 10:59:27 +00002778 prePostBootSetup();
Julien Desprez6961b272016-02-01 09:58:23 +00002779 for (String command : mOptions.getPostBootCommands()) {
2780 executeShellCommand(command);
2781 }
2782 }
2783
2784 /**
Julien Desprezc8474552016-02-17 10:59:27 +00002785 * Allows each device type (AndroidNativeDevice, TestDevice) to override this method for
2786 * specific post boot setup.
2787 * @throws DeviceNotAvailableException
2788 */
2789 protected void prePostBootSetup() throws DeviceNotAvailableException {
2790 // Empty on purpose.
2791 }
2792
2793 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002794 * Ensure wifi connection is re-established after boot. This is intended to be called after TF
2795 * initiated reboots(ones triggered by {@link #reboot()}) only.
2796 *
2797 * @throws DeviceNotAvailableException
2798 */
2799 void postBootWifiSetup() throws DeviceNotAvailableException {
2800 if (mLastConnectedWifiSsid != null) {
2801 reconnectToWifiNetwork();
2802 }
2803 if (mNetworkMonitorEnabled) {
2804 if (!enableNetworkMonitor()) {
2805 CLog.w("Failed to enable network monitor on %s after reboot", getSerialNumber());
2806 }
2807 }
2808 }
2809
2810 void reconnectToWifiNetwork() throws DeviceNotAvailableException {
2811 // First, wait for wifi to re-connect automatically.
2812 long startTime = System.currentTimeMillis();
2813 boolean isConnected = checkConnectivity();
2814 while (!isConnected && (System.currentTimeMillis() - startTime) < WIFI_RECONNECT_TIMEOUT) {
2815 getRunUtil().sleep(WIFI_RECONNECT_CHECK_INTERVAL);
2816 isConnected = checkConnectivity();
2817 }
2818
2819 if (isConnected) {
2820 return;
2821 }
2822
2823 // If wifi is still not connected, try to re-connect on our own.
2824 final String wifiSsid = mLastConnectedWifiSsid;
2825 if (!connectToWifiNetworkIfNeeded(mLastConnectedWifiSsid, mLastConnectedWifiPsk)) {
2826 throw new NetworkNotAvailableException(
2827 String.format("Failed to connect to wifi network %s on %s after reboot",
2828 wifiSsid, getSerialNumber()));
2829 }
2830 }
2831
2832 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002833 * {@inheritDoc}
2834 */
2835 @Override
2836 public void rebootIntoBootloader()
2837 throws DeviceNotAvailableException, UnsupportedOperationException {
2838 if (!mFastbootEnabled) {
2839 throw new UnsupportedOperationException(
2840 "Fastboot is not available and cannot reboot into bootloader");
2841 }
Julien Desprez13399b92019-04-05 12:31:18 -07002842 // If we go to bootloader, it's probably for flashing so ensure we re-check the provider
2843 mShouldSkipContentProviderSetup = false;
Julien Desprez6961b272016-02-01 09:58:23 +00002844 CLog.i("Rebooting device %s in state %s into bootloader", getSerialNumber(),
2845 getDeviceState());
2846 if (TestDeviceState.FASTBOOT.equals(getDeviceState())) {
2847 CLog.i("device %s already in fastboot. Rebooting anyway", getSerialNumber());
2848 executeFastbootCommand("reboot-bootloader");
2849 } else {
2850 CLog.i("Booting device %s into bootloader", getSerialNumber());
2851 doAdbRebootBootloader();
2852 }
2853 if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) {
2854 recoverDeviceFromBootloader();
2855 }
2856 }
2857
2858 private void doAdbRebootBootloader() throws DeviceNotAvailableException {
2859 doAdbReboot("bootloader");
2860 }
2861
2862 /**
2863 * {@inheritDoc}
2864 */
2865 @Override
2866 public void reboot() throws DeviceNotAvailableException {
2867 rebootUntilOnline();
2868
2869 RecoveryMode cachedRecoveryMode = getRecoveryMode();
2870 setRecoveryMode(RecoveryMode.ONLINE);
2871
2872 if (isEncryptionSupported() && isDeviceEncrypted()) {
2873 unlockDevice();
2874 }
2875
2876 setRecoveryMode(cachedRecoveryMode);
2877
2878 if (mStateMonitor.waitForDeviceAvailable(mOptions.getRebootTimeout()) != null) {
2879 postBootSetup();
2880 postBootWifiSetup();
2881 return;
2882 } else {
2883 recoverDevice();
2884 }
2885 }
2886
2887 /**
2888 * {@inheritDoc}
2889 */
2890 @Override
2891 public void rebootUntilOnline() throws DeviceNotAvailableException {
2892 doReboot();
2893 RecoveryMode cachedRecoveryMode = getRecoveryMode();
2894 setRecoveryMode(RecoveryMode.ONLINE);
Julien Desprez088e5c92019-03-20 09:10:25 -07002895 waitForDeviceOnline();
2896 enableAdbRoot();
Julien Desprez6961b272016-02-01 09:58:23 +00002897 setRecoveryMode(cachedRecoveryMode);
2898 }
2899
2900 /**
2901 * {@inheritDoc}
2902 */
2903 @Override
2904 public void rebootIntoRecovery() throws DeviceNotAvailableException {
2905 if (TestDeviceState.FASTBOOT == getDeviceState()) {
2906 CLog.w("device %s in fastboot when requesting boot to recovery. " +
2907 "Rebooting to userspace first.", getSerialNumber());
2908 rebootUntilOnline();
2909 }
2910 doAdbReboot("recovery");
2911 if (!waitForDeviceInRecovery(mOptions.getAdbRecoveryTimeout())) {
2912 recoverDeviceInRecovery();
2913 }
2914 }
2915
Guang Zhuac76f7a2019-05-22 16:46:23 -07002916
2917 /** {@inheritDoc} */
2918 @Override
2919 public void rebootIntoSideload() throws DeviceNotAvailableException {
2920 if (TestDeviceState.FASTBOOT == getDeviceState()) {
2921 CLog.w(
2922 "device %s in fastboot when requesting boot to sideload. "
2923 + "Rebooting to userspace first.",
2924 getSerialNumber());
2925 rebootUntilOnline();
2926 }
2927 doAdbReboot("sideload");
2928 if (!waitForDeviceInSideload(mOptions.getAdbRecoveryTimeout())) {
2929 // using recovery mode because sideload is a sub-mode under recovery
2930 recoverDeviceInRecovery();
2931 }
2932 }
2933
Julien Desprez6961b272016-02-01 09:58:23 +00002934 /**
2935 * {@inheritDoc}
2936 */
2937 @Override
2938 public void nonBlockingReboot() throws DeviceNotAvailableException {
2939 doReboot();
2940 }
2941
Julien Desprez78344aa2018-09-04 16:06:05 -07002942 /**
2943 * Trigger a reboot of the device, offers no guarantee of the device state after the call.
2944 *
2945 * @throws DeviceNotAvailableException
2946 * @throws UnsupportedOperationException
2947 */
Jeffrey Lu279122f2018-01-29 17:25:08 -08002948 @VisibleForTesting
Julien Desprez6961b272016-02-01 09:58:23 +00002949 void doReboot() throws DeviceNotAvailableException, UnsupportedOperationException {
Julien Despreza5397f12019-03-28 09:46:16 -07002950 // Track Tradefed reboot time
2951 mLastTradefedRebootTime = System.currentTimeMillis();
2952
Julien Desprez6961b272016-02-01 09:58:23 +00002953 if (TestDeviceState.FASTBOOT == getDeviceState()) {
2954 CLog.i("device %s in fastboot. Rebooting to userspace.", getSerialNumber());
2955 executeFastbootCommand("reboot");
2956 } else {
Guang Zhu120ed1c2016-02-24 23:31:49 -08002957 if (mOptions.shouldDisableReboot()) {
2958 CLog.i("Device reboot disabled by options, skipped.");
2959 return;
2960 }
Julien Desprez6961b272016-02-01 09:58:23 +00002961 CLog.i("Rebooting device %s", getSerialNumber());
2962 doAdbReboot(null);
Julien Desprez78344aa2018-09-04 16:06:05 -07002963 // Check if device shows as unavailable (as expected after reboot).
2964 boolean notAvailable = waitForDeviceNotAvailable(DEFAULT_UNAVAILABLE_TIMEOUT);
2965 if (notAvailable) {
2966 postAdbReboot();
2967 } else {
2968 CLog.w(
2969 "Did not detect device %s becoming unavailable after reboot",
2970 getSerialNumber());
2971 }
Julien Desprez6961b272016-02-01 09:58:23 +00002972 }
2973 }
2974
2975 /**
Julien Desprez78344aa2018-09-04 16:06:05 -07002976 * Possible extra actions that can be taken after a reboot.
2977 *
2978 * @throws DeviceNotAvailableException
2979 */
2980 protected void postAdbReboot() throws DeviceNotAvailableException {
2981 // Default implementation empty on purpose.
2982 }
2983
2984 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002985 * Perform a adb reboot.
2986 *
2987 * @param into the bootloader name to reboot into, or <code>null</code> to just reboot the
2988 * device.
2989 * @throws DeviceNotAvailableException
2990 */
Julien Desprezc8474552016-02-17 10:59:27 +00002991 protected void doAdbReboot(final String into) throws DeviceNotAvailableException {
Julien Desprez78344aa2018-09-04 16:06:05 -07002992 DeviceAction rebootAction = createRebootDeviceAction(into);
Julien Desprezc8474552016-02-17 10:59:27 +00002993 performDeviceAction("reboot", rebootAction, MAX_RETRY_ATTEMPTS);
Julien Desprez78344aa2018-09-04 16:06:05 -07002994 }
Julien Desprezc8474552016-02-17 10:59:27 +00002995
Julien Desprez78344aa2018-09-04 16:06:05 -07002996 /**
2997 * Create a {@link RebootDeviceAction} to be used when performing a reboot action.
2998 *
2999 * @param into the bootloader name to reboot into, or <code>null</code> to just reboot the
3000 * device.
3001 * @return the created {@link RebootDeviceAction}.
3002 */
3003 protected RebootDeviceAction createRebootDeviceAction(final String into) {
3004 return new RebootDeviceAction(into);
Julien Desprez6961b272016-02-01 09:58:23 +00003005 }
3006
Julien Desprezc8474552016-02-17 10:59:27 +00003007 protected void waitForDeviceNotAvailable(String operationDesc, long time) {
Julien Desprez6961b272016-02-01 09:58:23 +00003008 // TODO: a bit of a race condition here. Would be better to start a
3009 // before the operation
3010 if (!mStateMonitor.waitForDeviceNotAvailable(time)) {
3011 // above check is flaky, ignore till better solution is found
3012 CLog.w("Did not detect device %s becoming unavailable after %s", getSerialNumber(),
3013 operationDesc);
3014 }
3015 }
3016
3017 /**
3018 * {@inheritDoc}
3019 */
3020 @Override
3021 public boolean enableAdbRoot() throws DeviceNotAvailableException {
3022 // adb root is a relatively intensive command, so do a brief check first to see
3023 // if its necessary or not
3024 if (isAdbRoot()) {
3025 CLog.i("adb is already running as root on %s", getSerialNumber());
jdesprezdcc60fc2017-08-07 14:51:00 -07003026 // Still check for online, in some case we could see the root, but device could be
3027 // very early in its cycle.
3028 waitForDeviceOnline();
Julien Desprez6961b272016-02-01 09:58:23 +00003029 return true;
3030 }
3031 // Don't enable root if user requested no root
3032 if (!isEnableAdbRoot()) {
3033 CLog.i("\"enable-root\" set to false; ignoring 'adb root' request");
3034 return false;
3035 }
3036 CLog.i("adb root on device %s", getSerialNumber());
3037 int attempts = MAX_RETRY_ATTEMPTS + 1;
3038 for (int i=1; i <= attempts; i++) {
3039 String output = executeAdbCommand("root");
3040 // wait for device to disappear from adb
3041 waitForDeviceNotAvailable("root", 20 * 1000);
Julien Desprez8539c082016-03-04 13:39:19 +00003042
3043 postAdbRootAction();
3044
Julien Desprez6961b272016-02-01 09:58:23 +00003045 // wait for device to be back online
3046 waitForDeviceOnline();
3047
3048 if (isAdbRoot()) {
3049 return true;
3050 }
3051 CLog.w("'adb root' on %s unsuccessful on attempt %d of %d. Output: '%s'",
3052 getSerialNumber(), i, attempts, output);
3053 }
3054 return false;
3055 }
3056
3057 /**
Julien Desprez22d0c3a2016-04-15 10:38:08 +01003058 * {@inheritDoc}
3059 */
3060 @Override
3061 public boolean disableAdbRoot() throws DeviceNotAvailableException {
3062 if (!isAdbRoot()) {
3063 CLog.i("adb is already unroot on %s", getSerialNumber());
3064 return true;
3065 }
3066
3067 CLog.i("adb unroot on device %s", getSerialNumber());
3068 int attempts = MAX_RETRY_ATTEMPTS + 1;
3069 for (int i=1; i <= attempts; i++) {
3070 String output = executeAdbCommand("unroot");
3071 // wait for device to disappear from adb
3072 waitForDeviceNotAvailable("unroot", 5 * 1000);
3073
3074 postAdbUnrootAction();
3075
3076 // wait for device to be back online
3077 waitForDeviceOnline();
3078
3079 if (!isAdbRoot()) {
3080 return true;
3081 }
3082 CLog.w("'adb unroot' on %s unsuccessful on attempt %d of %d. Output: '%s'",
3083 getSerialNumber(), i, attempts, output);
3084 }
3085 return false;
3086 }
3087
3088 /**
Julien Desprez8539c082016-03-04 13:39:19 +00003089 * Override if the device needs some specific actions to be taken after adb root and before the
3090 * device is back online.
3091 * Default implementation doesn't include any addition actions.
3092 * adb root is not guaranteed to be enabled at this stage.
Julien Desprez021af1d2016-11-29 14:54:07 +00003093 * @throws DeviceNotAvailableException
Julien Desprez8539c082016-03-04 13:39:19 +00003094 */
Julien Desprez9dca62e2016-04-08 14:47:57 +01003095 public void postAdbRootAction() throws DeviceNotAvailableException {
Julien Desprez8539c082016-03-04 13:39:19 +00003096 // Empty on purpose.
3097 }
3098
3099 /**
Julien Desprez22d0c3a2016-04-15 10:38:08 +01003100 * Override if the device needs some specific actions to be taken after adb unroot and before
3101 * the device is back online.
3102 * Default implementation doesn't include any additional actions.
3103 * adb root is not guaranteed to be disabled at this stage.
Julien Desprez021af1d2016-11-29 14:54:07 +00003104 * @throws DeviceNotAvailableException
Julien Desprez22d0c3a2016-04-15 10:38:08 +01003105 */
Julien Desprez5e4fc6b2016-06-06 10:08:46 +01003106 public void postAdbUnrootAction() throws DeviceNotAvailableException {
Julien Desprez22d0c3a2016-04-15 10:38:08 +01003107 // Empty on purpose.
3108 }
3109
3110 /**
Julien Desprez6961b272016-02-01 09:58:23 +00003111 * {@inheritDoc}
3112 */
3113 @Override
3114 public boolean isAdbRoot() throws DeviceNotAvailableException {
3115 String output = executeShellCommand("id");
3116 return output.contains("uid=0(root)");
3117 }
3118
3119 /**
3120 * {@inheritDoc}
3121 */
3122 @Override
3123 public boolean encryptDevice(boolean inplace) throws DeviceNotAvailableException,
3124 UnsupportedOperationException {
3125 if (!isEncryptionSupported()) {
3126 throw new UnsupportedOperationException(String.format("Can't encrypt device %s: "
3127 + "encryption not supported", getSerialNumber()));
3128 }
3129
3130 if (isDeviceEncrypted()) {
3131 CLog.d("Device %s is already encrypted, skipping", getSerialNumber());
3132 return true;
3133 }
3134
3135 enableAdbRoot();
3136
3137 String encryptMethod;
3138 long timeout;
3139 if (inplace) {
3140 encryptMethod = "inplace";
3141 timeout = ENCRYPTION_INPLACE_TIMEOUT_MIN;
3142 } else {
3143 encryptMethod = "wipe";
3144 timeout = ENCRYPTION_WIPE_TIMEOUT_MIN;
3145 }
3146
3147 CLog.i("Encrypting device %s via %s", getSerialNumber(), encryptMethod);
3148
3149 // enable crypto takes one of the following formats:
3150 // cryptfs enablecrypto <wipe|inplace> <passwd>
3151 // cryptfs enablecrypto <wipe|inplace> default|password|pin|pattern [passwd]
3152 // Try the first one first, if it outputs "500 0 Usage: ...", try the second.
3153 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
3154 String command = String.format("vdc cryptfs enablecrypto %s \"%s\"", encryptMethod,
3155 ENCRYPTION_PASSWORD);
3156 executeShellCommand(command, receiver, timeout, TimeUnit.MINUTES, 1);
Julien Desprezac96c812016-08-10 14:57:45 +01003157 if (receiver.getOutput().split(":")[0].matches("500 \\d+ Usage")) {
Julien Desprez6961b272016-02-01 09:58:23 +00003158 command = String.format("vdc cryptfs enablecrypto %s default", encryptMethod);
3159 executeShellCommand(command, new NullOutputReceiver(), timeout, TimeUnit.MINUTES, 1);
3160 }
3161
3162 waitForDeviceNotAvailable("reboot", getCommandTimeout());
3163 waitForDeviceOnline(); // Device will not become available until the user data is unlocked.
3164
3165 return isDeviceEncrypted();
3166 }
3167
3168 /**
3169 * {@inheritDoc}
3170 */
3171 @Override
3172 public boolean unencryptDevice() throws DeviceNotAvailableException,
3173 UnsupportedOperationException {
3174 if (!isEncryptionSupported()) {
3175 throw new UnsupportedOperationException(String.format("Can't unencrypt device %s: "
3176 + "encryption not supported", getSerialNumber()));
3177 }
3178
3179 if (!isDeviceEncrypted()) {
3180 CLog.d("Device %s is already unencrypted, skipping", getSerialNumber());
3181 return true;
3182 }
3183
3184 CLog.i("Unencrypting device %s", getSerialNumber());
3185
3186 // If the device supports fastboot format, then we're done.
3187 if (!mOptions.getUseFastbootErase()) {
3188 rebootIntoBootloader();
3189 fastbootWipePartition("userdata");
3190 rebootUntilOnline();
3191 waitForDeviceAvailable(ENCRYPTION_WIPE_TIMEOUT_MIN * 60 * 1000);
3192 return true;
3193 }
3194
3195 // Determine if we need to format partition instead of wipe.
3196 boolean format = false;
3197 String output = executeShellCommand("vdc volume list");
3198 String[] splitOutput;
3199 if (output != null) {
3200 splitOutput = output.split("\r?\n");
3201 for (String line : splitOutput) {
3202 if (line.startsWith("110 ") && line.contains("sdcard /mnt/sdcard") &&
3203 !line.endsWith("0")) {
3204 format = true;
3205 }
3206 }
3207 }
3208
3209 rebootIntoBootloader();
3210 fastbootWipePartition("userdata");
3211
3212 // If the device requires time to format the filesystem after fastboot erase userdata, wait
3213 // for the device to reboot a second time.
3214 if (mOptions.getUnencryptRebootTimeout() > 0) {
3215 rebootUntilOnline();
3216 if (waitForDeviceNotAvailable(mOptions.getUnencryptRebootTimeout())) {
3217 waitForDeviceOnline();
3218 }
3219 }
3220
3221 if (format) {
3222 CLog.d("Need to format sdcard for device %s", getSerialNumber());
3223
3224 RecoveryMode cachedRecoveryMode = getRecoveryMode();
3225 setRecoveryMode(RecoveryMode.ONLINE);
3226
3227 output = executeShellCommand("vdc volume format sdcard");
3228 if (output == null) {
3229 CLog.e("Command vdc volume format sdcard failed will no output for device %s:\n%s",
3230 getSerialNumber());
3231 setRecoveryMode(cachedRecoveryMode);
3232 return false;
3233 }
3234 splitOutput = output.split("\r?\n");
3235 if (!splitOutput[splitOutput.length - 1].startsWith("200 ")) {
3236 CLog.e("Command vdc volume format sdcard failed for device %s:\n%s",
3237 getSerialNumber(), output);
3238 setRecoveryMode(cachedRecoveryMode);
3239 return false;
3240 }
3241
3242 setRecoveryMode(cachedRecoveryMode);
3243 }
3244
3245 reboot();
3246
3247 return true;
3248 }
3249
3250 /**
3251 * {@inheritDoc}
3252 */
3253 @Override
3254 public boolean unlockDevice() throws DeviceNotAvailableException,
3255 UnsupportedOperationException {
3256 if (!isEncryptionSupported()) {
3257 throw new UnsupportedOperationException(String.format("Can't unlock device %s: "
3258 + "encryption not supported", getSerialNumber()));
3259 }
3260
3261 if (!isDeviceEncrypted()) {
3262 CLog.d("Device %s is not encrypted, skipping", getSerialNumber());
3263 return true;
3264 }
3265
3266 CLog.i("Unlocking device %s", getSerialNumber());
3267
3268 enableAdbRoot();
3269
3270 // FIXME: currently, vcd checkpw can return an empty string when it never should. Try 3
3271 // times.
3272 String output;
3273 int i = 0;
3274 do {
3275 // Enter the password. Output will be:
3276 // "200 [X] -1" if the password has already been entered correctly,
3277 // "200 [X] 0" if the password is entered correctly,
3278 // "200 [X] N" where N is any positive number if the password is incorrect,
3279 // any other string if there is an error.
3280 output = executeShellCommand(String.format("vdc cryptfs checkpw \"%s\"",
3281 ENCRYPTION_PASSWORD)).trim();
3282
3283 if (output.startsWith("200 ") && output.endsWith(" -1")) {
3284 return true;
3285 }
3286
3287 if (!output.isEmpty() && !(output.startsWith("200 ") && output.endsWith(" 0"))) {
3288 CLog.e("checkpw gave output '%s' while trying to unlock device %s",
3289 output, getSerialNumber());
3290 return false;
3291 }
3292
3293 getRunUtil().sleep(500);
3294 } while (output.isEmpty() && ++i < 3);
3295
3296 if (output.isEmpty()) {
3297 CLog.e("checkpw gave no output while trying to unlock device %s");
3298 }
3299
3300 // Restart the framework. Output will be:
3301 // "200 [X] 0" if the user data partition can be mounted,
3302 // "200 [X] -1" if the user data partition can not be mounted (no correct password given),
3303 // any other string if there is an error.
3304 output = executeShellCommand("vdc cryptfs restart").trim();
3305
3306 if (!(output.startsWith("200 ") && output.endsWith(" 0"))) {
3307 CLog.e("restart gave output '%s' while trying to unlock device %s", output,
3308 getSerialNumber());
3309 return false;
3310 }
3311
3312 waitForDeviceAvailable();
3313
3314 return true;
3315 }
3316
3317 /**
3318 * {@inheritDoc}
3319 */
3320 @Override
3321 public boolean isDeviceEncrypted() throws DeviceNotAvailableException {
Julien Desprezaf166b32016-08-08 11:58:47 +01003322 String output = getProperty("ro.crypto.state");
Julien Desprez6961b272016-02-01 09:58:23 +00003323
3324 if (output == null && isEncryptionSupported()) {
3325 CLog.w("Property ro.crypto.state is null on device %s", getSerialNumber());
3326 }
3327
3328 return "encrypted".equals(output);
3329 }
3330
3331 /**
3332 * {@inheritDoc}
3333 */
3334 @Override
3335 public boolean isEncryptionSupported() throws DeviceNotAvailableException {
3336 if (!isEnableAdbRoot()) {
3337 CLog.i("root is required for encryption");
3338 mIsEncryptionSupported = false;
3339 return mIsEncryptionSupported;
3340 }
3341 if (mIsEncryptionSupported != null) {
3342 return mIsEncryptionSupported.booleanValue();
3343 }
3344 enableAdbRoot();
3345 String output = executeShellCommand("vdc cryptfs enablecrypto").trim();
jdesprezdd9d9e92017-07-13 03:34:19 -07003346
3347 mIsEncryptionSupported =
3348 (output != null
3349 && Pattern.matches("(500)(\\s+)(\\d+)(\\s+)(Usage)(.*)(:)(.*)", output));
Julien Desprez6961b272016-02-01 09:58:23 +00003350 return mIsEncryptionSupported;
3351 }
3352
3353 /**
3354 * {@inheritDoc}
3355 */
3356 @Override
3357 public void waitForDeviceOnline(long waitTime) throws DeviceNotAvailableException {
3358 if (mStateMonitor.waitForDeviceOnline(waitTime) == null) {
3359 recoverDevice();
3360 }
3361 }
3362
3363 /**
3364 * {@inheritDoc}
3365 */
3366 @Override
3367 public void waitForDeviceOnline() throws DeviceNotAvailableException {
3368 if (mStateMonitor.waitForDeviceOnline() == null) {
3369 recoverDevice();
3370 }
3371 }
3372
3373 /**
3374 * {@inheritDoc}
3375 */
3376 @Override
3377 public void waitForDeviceAvailable(long waitTime) throws DeviceNotAvailableException {
3378 if (mStateMonitor.waitForDeviceAvailable(waitTime) == null) {
3379 recoverDevice();
3380 }
3381 }
3382
3383 /**
3384 * {@inheritDoc}
3385 */
3386 @Override
3387 public void waitForDeviceAvailable() throws DeviceNotAvailableException {
3388 if (mStateMonitor.waitForDeviceAvailable() == null) {
3389 recoverDevice();
3390 }
3391 }
3392
3393 /**
3394 * {@inheritDoc}
3395 */
3396 @Override
3397 public boolean waitForDeviceNotAvailable(long waitTime) {
3398 return mStateMonitor.waitForDeviceNotAvailable(waitTime);
3399 }
3400
3401 /**
3402 * {@inheritDoc}
3403 */
3404 @Override
3405 public boolean waitForDeviceInRecovery(long waitTime) {
3406 return mStateMonitor.waitForDeviceInRecovery(waitTime);
3407 }
3408
Guang Zhuac76f7a2019-05-22 16:46:23 -07003409 /** {@inheritDoc} */
3410 @Override
3411 public boolean waitForDeviceInSideload(long waitTime) {
3412 return mStateMonitor.waitForDeviceInSideload(waitTime);
3413 }
3414
Julien Desprez6961b272016-02-01 09:58:23 +00003415 /**
3416 * Small helper function to throw an NPE if the passed arg is null. This should be used when
3417 * some value will be stored and used later, in which case it'll avoid hard-to-trace
3418 * asynchronous NullPointerExceptions by throwing the exception synchronously. This is not
3419 * intended to be used where the NPE would be thrown synchronously -- just let the jvm take care
3420 * of it in that case.
3421 */
3422 private void throwIfNull(Object obj) {
3423 if (obj == null) throw new NullPointerException();
3424 }
3425
Jeffrey Lu279122f2018-01-29 17:25:08 -08003426 /** Retrieve this device's recovery mechanism. */
3427 @VisibleForTesting
Julien Desprez6961b272016-02-01 09:58:23 +00003428 IDeviceRecovery getRecovery() {
3429 return mRecovery;
3430 }
3431
3432 /**
3433 * {@inheritDoc}
3434 */
3435 @Override
3436 public void setRecovery(IDeviceRecovery recovery) {
3437 throwIfNull(recovery);
3438 mRecovery = recovery;
3439 }
3440
3441 /**
3442 * {@inheritDoc}
3443 */
3444 @Override
3445 public void setRecoveryMode(RecoveryMode mode) {
3446 throwIfNull(mRecoveryMode);
3447 mRecoveryMode = mode;
3448 }
3449
3450 /**
3451 * {@inheritDoc}
3452 */
3453 @Override
3454 public RecoveryMode getRecoveryMode() {
3455 return mRecoveryMode;
3456 }
3457
3458 /**
3459 * {@inheritDoc}
3460 */
3461 @Override
3462 public void setFastbootEnabled(boolean fastbootEnabled) {
3463 mFastbootEnabled = fastbootEnabled;
3464 }
3465
3466 /**
3467 * {@inheritDoc}
3468 */
3469 @Override
Julien Desprez3a503442016-04-05 15:00:41 +01003470 public boolean isFastbootEnabled() {
3471 return mFastbootEnabled;
3472 }
3473
3474 /**
3475 * {@inheritDoc}
3476 */
3477 @Override
Julien Desprez0a7d67d2016-07-21 16:05:57 +01003478 public void setFastbootPath(String fastbootPath) {
3479 mFastbootPath = fastbootPath;
3480 // ensure the device and its associated recovery use the same fastboot version.
3481 mRecovery.setFastbootPath(fastbootPath);
3482 }
3483
3484 /**
3485 * {@inheritDoc}
3486 */
3487 @Override
3488 public String getFastbootPath() {
3489 return mFastbootPath;
3490 }
3491
Julien Desprez072acdf2019-02-15 14:23:50 -08003492 /** {@inheritDoc} */
3493 @Override
3494 public String getFastbootVersion() {
3495 try {
3496 CommandResult res = executeFastbootCommand("--version");
3497 return res.getStdout().trim();
3498 } catch (DeviceNotAvailableException e) {
3499 // Ignored for host side request
3500 }
3501 return null;
3502 }
3503
Julien Desprez0a7d67d2016-07-21 16:05:57 +01003504 /**
3505 * {@inheritDoc}
3506 */
3507 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003508 public void setDeviceState(final TestDeviceState deviceState) {
3509 if (!deviceState.equals(getDeviceState())) {
3510 // disable state changes while fastboot lock is held, because issuing fastboot command
3511 // will disrupt state
3512 if (getDeviceState().equals(TestDeviceState.FASTBOOT) && mFastbootLock.isLocked()) {
3513 return;
3514 }
3515 mState = deviceState;
3516 CLog.d("Device %s state is now %s", getSerialNumber(), deviceState);
3517 mStateMonitor.setState(deviceState);
3518 }
3519 }
3520
3521 /**
3522 * {@inheritDoc}
3523 */
3524 @Override
3525 public TestDeviceState getDeviceState() {
3526 return mState;
3527 }
3528
3529 @Override
3530 public boolean isAdbTcp() {
3531 return mStateMonitor.isAdbTcp();
3532 }
3533
3534 /**
3535 * {@inheritDoc}
3536 */
3537 @Override
3538 public String switchToAdbTcp() throws DeviceNotAvailableException {
3539 String ipAddress = getIpAddress();
3540 if (ipAddress == null) {
3541 CLog.e("connectToTcp failed: Device %s doesn't have an IP", getSerialNumber());
3542 return null;
3543 }
3544 String port = "5555";
3545 executeAdbCommand("tcpip", port);
3546 // TODO: analyze result? wait for device offline?
3547 return String.format("%s:%s", ipAddress, port);
3548 }
3549
3550 /**
3551 * {@inheritDoc}
3552 */
3553 @Override
3554 public boolean switchToAdbUsb() throws DeviceNotAvailableException {
3555 executeAdbCommand("usb");
3556 // TODO: analyze result? wait for device offline?
3557 return true;
3558 }
3559
3560 /**
3561 * {@inheritDoc}
3562 */
3563 @Override
3564 public void setEmulatorProcess(Process p) {
3565 mEmulatorProcess = p;
3566
3567 }
3568
3569 /**
3570 * For emulator set {@link SizeLimitedOutputStream} to log output
3571 * @param output to log the output
3572 */
3573 public void setEmulatorOutputStream(SizeLimitedOutputStream output) {
3574 mEmulatorOutput = output;
3575 }
3576
3577 /**
3578 * {@inheritDoc}
3579 */
3580 @Override
3581 public void stopEmulatorOutput() {
3582 if (mEmulatorOutput != null) {
3583 mEmulatorOutput.delete();
3584 mEmulatorOutput = null;
3585 }
3586 }
3587
3588 /**
3589 * {@inheritDoc}
3590 */
3591 @Override
3592 public InputStreamSource getEmulatorOutput() {
3593 if (getIDevice().isEmulator()) {
3594 if (mEmulatorOutput == null) {
3595 CLog.w("Emulator output for %s was not captured in background",
3596 getSerialNumber());
3597 } else {
3598 try {
jdesprez14084fb2017-07-26 14:36:39 -07003599 return new SnapshotInputStreamSource(
3600 "getEmulatorOutput", mEmulatorOutput.getData());
Julien Desprez6961b272016-02-01 09:58:23 +00003601 } catch (IOException e) {
3602 CLog.e("Failed to get %s data.", getSerialNumber());
3603 CLog.e(e);
3604 }
3605 }
3606 }
3607 return new ByteArrayInputStreamSource(new byte[0]);
3608 }
3609
3610 /**
3611 * {@inheritDoc}
3612 */
3613 @Override
3614 public Process getEmulatorProcess() {
3615 return mEmulatorProcess;
3616 }
3617
3618 /**
3619 * @return <code>true</code> if adb root should be enabled on device
3620 */
3621 public boolean isEnableAdbRoot() {
3622 return mOptions.isEnableAdbRoot();
3623 }
3624
3625 /**
3626 * {@inheritDoc}
3627 */
3628 @Override
3629 public Set<String> getInstalledPackageNames() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003630 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003631 }
3632
Jiyong Park3396a842018-12-17 14:01:54 +09003633 /** {@inheritDoc} */
3634 @Override
Julien Desprez529381a2019-05-02 15:16:35 -07003635 public boolean isPackageInstalled(String packageName) throws DeviceNotAvailableException {
3636 throw new UnsupportedOperationException("No support for Package's feature");
3637 }
3638
3639 /** {@inheritDoc} */
3640 @Override
Julien Desprez0b44ad32019-05-15 19:02:45 -07003641 public boolean isPackageInstalled(String packageName, String userId)
3642 throws DeviceNotAvailableException {
3643 throw new UnsupportedOperationException("No support for Package's feature");
3644 }
3645
3646 /** {@inheritDoc} */
3647 @Override
Jiyong Park3396a842018-12-17 14:01:54 +09003648 public Set<ApexInfo> getActiveApexes() throws DeviceNotAvailableException {
3649 throw new UnsupportedOperationException("No support for Package's feature");
3650 }
3651
Julien Desprez6961b272016-02-01 09:58:23 +00003652 /**
3653 * {@inheritDoc}
3654 */
3655 @Override
3656 public Set<String> getUninstallablePackageNames() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003657 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003658 }
3659
3660 /**
3661 * {@inheritDoc}
3662 */
3663 @Override
3664 public PackageInfo getAppPackageInfo(String packageName) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003665 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003666 }
3667
3668 /**
3669 * {@inheritDoc}
3670 */
3671 @Override
3672 public TestDeviceOptions getOptions() {
3673 return mOptions;
3674 }
3675
3676 /**
3677 * {@inheritDoc}
3678 */
3679 @Override
3680 public int getApiLevel() throws DeviceNotAvailableException {
3681 int apiLevel = UNKNOWN_API_LEVEL;
3682 try {
3683 String prop = getProperty("ro.build.version.sdk");
3684 apiLevel = Integer.parseInt(prop);
3685 } catch (NumberFormatException nfe) {
3686 // ignore, return unknown instead
3687 }
3688 return apiLevel;
3689 }
3690
Julien Desprez3c4fb012019-04-17 09:12:30 -07003691 /** {@inheritDoc} */
3692 @Override
3693 public boolean checkApiLevelAgainstNextRelease(int strictMinLevel)
3694 throws DeviceNotAvailableException {
3695 String codeName = getProperty(BUILD_CODENAME_PROP).trim();
3696 int apiLevel = getApiLevel() + ("REL".equals(codeName) ? 0 : 1);
3697 if (strictMinLevel > apiLevel) {
3698 return false;
3699 }
3700 return true;
3701 }
3702
jdesprezc3537f72017-09-05 17:27:14 -07003703 private int getApiLevelSafe() {
3704 try {
3705 return getApiLevel();
3706 } catch (DeviceNotAvailableException e) {
3707 CLog.e(e);
3708 return UNKNOWN_API_LEVEL;
3709 }
3710 }
3711
Julien Desprez6961b272016-02-01 09:58:23 +00003712 @Override
3713 public IDeviceStateMonitor getMonitor() {
3714 return mStateMonitor;
3715 }
3716
3717 /**
3718 * {@inheritDoc}
3719 */
3720 @Override
3721 public boolean waitForDeviceShell(long waitTime) {
3722 return mStateMonitor.waitForDeviceShell(waitTime);
3723 }
3724
3725 @Override
3726 public DeviceAllocationState getAllocationState() {
3727 return mAllocationState;
3728 }
3729
3730 /**
3731 * {@inheritDoc}
3732 * <p>
3733 * Process the DeviceEvent, which may or may not transition this device to a new allocation
3734 * state.
3735 * </p>
3736 */
3737 @Override
3738 public DeviceEventResponse handleAllocationEvent(DeviceEvent event) {
3739
3740 // keep track of whether state has actually changed or not
3741 boolean stateChanged = false;
3742 DeviceAllocationState newState;
3743 DeviceAllocationState oldState = mAllocationState;
3744 mAllocationStateLock.lock();
3745 try {
3746 // update oldState here, just in case in changed before we got lock
3747 oldState = mAllocationState;
3748 newState = mAllocationState.handleDeviceEvent(event);
3749 if (oldState != newState) {
3750 // state has changed! record this fact, and store the new state
3751 stateChanged = true;
3752 mAllocationState = newState;
3753 }
3754 } finally {
3755 mAllocationStateLock.unlock();
3756 }
3757 if (stateChanged && mAllocationMonitor != null) {
3758 // state has changed! Lets inform the allocation monitor listener
3759 mAllocationMonitor.notifyDeviceStateChange(getSerialNumber(), oldState, newState);
3760 }
3761 return new DeviceEventResponse(newState, stateChanged);
3762 }
3763
jdespreze2d5ed72018-03-07 14:48:16 -08003764 /** {@inheritDoc} */
3765 @Override
3766 public long getDeviceTimeOffset(Date date) throws DeviceNotAvailableException {
Julien Desprezdcb19d52016-06-20 12:23:12 +01003767 Long deviceTime = getDeviceDate();
Julien Desprez6961b272016-02-01 09:58:23 +00003768 long offset = 0;
3769
Julien Desprez6961b272016-02-01 09:58:23 +00003770 if (date == null) {
3771 date = new Date();
3772 }
3773
jdesprez0b9d69e2017-12-11 03:40:39 -08003774 offset = date.getTime() - deviceTime;
Julien Desprez1fadf1a2016-10-20 15:50:46 +01003775 CLog.d("Time offset = %d ms", offset);
Julien Desprez6961b272016-02-01 09:58:23 +00003776 return offset;
3777 }
3778
3779 /**
3780 * {@inheritDoc}
3781 */
3782 @Override
3783 public void setDate(Date date) throws DeviceNotAvailableException {
3784 if (date == null) {
3785 date = new Date();
3786 }
3787 long timeOffset = getDeviceTimeOffset(date);
3788 // no need to set date
3789 if (Math.abs(timeOffset) <= MAX_HOST_DEVICE_TIME_OFFSET) {
3790 return;
3791 }
3792 String dateString = null;
3793 if (getApiLevel() < 23) {
3794 // set date in epoch format
3795 dateString = Long.toString(date.getTime() / 1000); //ms to s
3796 } else {
3797 // set date with POSIX like params
3798 SimpleDateFormat sdf = new java.text.SimpleDateFormat(
3799 "MMddHHmmyyyy.ss");
3800 sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
3801 dateString = sdf.format(date);
3802 }
3803 // best effort, no verification
Julien Desprez0fd9e562019-03-12 14:05:37 -07003804 // Use TZ= to default to UTC timezone (b/128353510 for background)
3805 executeShellCommand("TZ=UTC date -u " + dateString);
Julien Desprez6961b272016-02-01 09:58:23 +00003806 }
3807
3808 /**
3809 * {@inheritDoc}
3810 */
3811 @Override
Julien Desprezdcb19d52016-06-20 12:23:12 +01003812 public long getDeviceDate() throws DeviceNotAvailableException {
3813 String deviceTimeString = executeShellCommand("date +%s");
3814 Long deviceTime = null;
3815 try {
3816 deviceTime = Long.valueOf(deviceTimeString.trim());
3817 } catch (NumberFormatException nfe) {
3818 CLog.i("Invalid device time: \"%s\", ignored.", nfe);
3819 return 0;
3820 }
jdesprez0b9d69e2017-12-11 03:40:39 -08003821 // Convert from seconds to milliseconds
3822 return deviceTime * 1000L;
Julien Desprezdcb19d52016-06-20 12:23:12 +01003823 }
3824
3825 /**
3826 * {@inheritDoc}
3827 */
3828 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003829 public boolean waitForBootComplete(long timeOut) throws DeviceNotAvailableException {
3830 return mStateMonitor.waitForBootComplete(timeOut);
3831 }
3832
3833 /**
3834 * {@inheritDoc}
3835 */
3836 @Override
3837 public ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003838 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003839 }
3840
3841 /**
3842 * {@inheritDoc}
3843 */
3844 @Override
3845 public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003846 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003847 }
3848
Alex Chau93617352018-01-16 15:22:25 +00003849 @Override
3850 public int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException {
3851 throw new UnsupportedOperationException("No support for user's feature.");
3852 }
3853
Julien Desprez6961b272016-02-01 09:58:23 +00003854 /**
3855 * {@inheritDoc}
3856 */
3857 @Override
3858 public boolean isMultiUserSupported() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003859 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003860 }
3861
Rett Berg2b683362019-02-13 11:36:34 -08003862 /** {@inheritDoc} */
3863 @Override
3864 public int createUserNoThrow(String name) throws DeviceNotAvailableException {
3865 throw new UnsupportedOperationException("No support for user's feature.");
3866 }
3867
Julien Desprez6961b272016-02-01 09:58:23 +00003868 /**
3869 * {@inheritDoc}
3870 */
3871 @Override
3872 public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException {
Julien Desprezc8474552016-02-17 10:59:27 +00003873 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003874 }
3875
3876 /**
3877 * {@inheritDoc}
3878 */
3879 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003880 public int createUser(String name, boolean guest, boolean ephemeral)
3881 throws DeviceNotAvailableException, IllegalStateException {
3882 throw new UnsupportedOperationException("No support for user's feature.");
3883 }
3884
3885 /**
3886 * {@inheritDoc}
3887 */
3888 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003889 public boolean removeUser(int userId) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003890 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003891 }
3892
3893 /**
3894 * {@inheritDoc}
3895 */
3896 @Override
3897 public boolean startUser(int userId) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003898 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003899 }
3900
Bookatzfd6f6942019-03-05 14:29:52 -08003901 /** {@inheritDoc} */
3902 @Override
3903 public boolean startUser(int userId, boolean waitFlag) throws DeviceNotAvailableException {
3904 throw new UnsupportedOperationException("No support for user's feature.");
3905 }
3906
Julien Desprez6961b272016-02-01 09:58:23 +00003907 /**
3908 * {@inheritDoc}
3909 */
3910 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003911 public boolean stopUser(int userId) throws DeviceNotAvailableException {
3912 throw new UnsupportedOperationException("No support for user's feature.");
3913 }
3914
3915 /**
3916 * {@inheritDoc}
3917 */
3918 @Override
3919 public boolean stopUser(int userId, boolean waitFlag, boolean forceFlag)
3920 throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003921 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003922 }
3923
3924 /**
3925 * {@inheritDoc}
3926 */
3927 @Override
3928 public void remountSystemWritable() throws DeviceNotAvailableException {
3929 String verity = getProperty("partition.system.verified");
3930 // have the property set (regardless state) implies verity is enabled, so we send adb
3931 // command to disable verity
3932 if (verity != null && !verity.isEmpty()) {
3933 executeAdbCommand("disable-verity");
3934 reboot();
3935 }
3936 executeAdbCommand("remount");
3937 waitForDeviceAvailable();
3938 }
3939
3940 /**
3941 * {@inheritDoc}
3942 */
3943 @Override
3944 public Integer getPrimaryUserId() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003945 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003946 }
3947
3948 /**
3949 * {@inheritDoc}
3950 */
3951 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003952 public int getCurrentUser() throws DeviceNotAvailableException {
3953 throw new UnsupportedOperationException("No support for user's feature.");
3954 }
3955
Rett Berg2b683362019-02-13 11:36:34 -08003956 /** {@inheritDoc} */
3957 @Override
3958 public boolean isUserSecondary(int userId) throws DeviceNotAvailableException {
3959 throw new UnsupportedOperationException("No support for user's feature.");
3960 }
3961
3962
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003963 /**
3964 * {@inheritDoc}
3965 */
3966 @Override
3967 public int getUserFlags(int userId) throws DeviceNotAvailableException {
3968 throw new UnsupportedOperationException("No support for user's feature.");
3969 }
3970
3971 /**
3972 * {@inheritDoc}
3973 */
3974 @Override
3975 public int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
3976 throw new UnsupportedOperationException("No support for user's feature.");
3977 }
3978
3979 /**
3980 * {@inheritDoc}
3981 */
3982 @Override
3983 public boolean switchUser(int userId) throws DeviceNotAvailableException {
3984 throw new UnsupportedOperationException("No support for user's feature.");
3985 }
3986
3987 /**
3988 * {@inheritDoc}
3989 */
3990 @Override
3991 public boolean switchUser(int userId, long timeout) throws DeviceNotAvailableException {
3992 throw new UnsupportedOperationException("No support for user's feature.");
3993 }
3994
3995 /**
3996 * {@inheritDoc}
3997 */
3998 @Override
3999 public boolean isUserRunning(int userId) throws DeviceNotAvailableException {
4000 throw new UnsupportedOperationException("No support for user's feature.");
4001 }
4002
4003 /**
4004 * {@inheritDoc}
4005 */
4006 @Override
4007 public boolean hasFeature(String feature) throws DeviceNotAvailableException {
4008 throw new UnsupportedOperationException("No support pm's features.");
4009 }
4010
4011 /**
4012 * {@inheritDoc}
4013 */
4014 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00004015 public String getSetting(String namespace, String key)
4016 throws DeviceNotAvailableException {
4017 throw new UnsupportedOperationException("No support for setting's feature.");
4018 }
4019
4020 /**
4021 * {@inheritDoc}
4022 */
4023 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004024 public String getSetting(int userId, String namespace, String key)
4025 throws DeviceNotAvailableException {
4026 throw new UnsupportedOperationException("No support for setting's feature.");
4027 }
4028
Yichun Lib1900022018-05-11 09:54:48 -07004029 /** {@inheritDoc} */
4030 @Override
4031 public Map<String, String> getAllSettings(String namespace) throws DeviceNotAvailableException {
4032 throw new UnsupportedOperationException("No support for setting's feature.");
4033 }
4034
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004035 /**
4036 * {@inheritDoc}
4037 */
4038 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00004039 public void setSetting(String namespace, String key, String value)
4040 throws DeviceNotAvailableException {
4041 throw new UnsupportedOperationException("No support for setting's feature.");
4042 }
4043
4044 /**
4045 * {@inheritDoc}
4046 */
4047 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004048 public void setSetting(int userId, String namespace, String key, String value)
4049 throws DeviceNotAvailableException {
4050 throw new UnsupportedOperationException("No support for setting's feature.");
4051 }
4052
4053 /**
4054 * {@inheritDoc}
4055 */
4056 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00004057 public String getBuildSigningKeys() throws DeviceNotAvailableException {
4058 String buildTags = getProperty(BUILD_TAGS);
4059 if (buildTags != null) {
4060 String[] tags = buildTags.split(",");
4061 for (String tag : tags) {
4062 Matcher m = KEYS_PATTERN.matcher(tag);
4063 if (m.matches()) {
4064 return tag;
4065 }
4066 }
4067 }
4068 return null;
4069 }
4070
4071 /**
4072 * {@inheritDoc}
4073 */
4074 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004075 public String getAndroidId(int userId) throws DeviceNotAvailableException {
4076 throw new UnsupportedOperationException("No support for user's feature.");
4077 }
4078
4079 /**
4080 * {@inheritDoc}
4081 */
4082 @Override
4083 public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException {
4084 throw new UnsupportedOperationException("No support for user's feature.");
4085 }
4086
Tony Mak14e4dee2017-04-12 14:37:34 +01004087 /** {@inheritDoc} */
4088 @Override
4089 public boolean setDeviceOwner(String componentName, int userId)
4090 throws DeviceNotAvailableException {
4091 throw new UnsupportedOperationException("No support for user's feature.");
4092 }
4093
4094 /** {@inheritDoc} */
4095 @Override
4096 public boolean removeAdmin(String componentName, int userId)
4097 throws DeviceNotAvailableException {
4098 throw new UnsupportedOperationException("No support for user's feature.");
4099 }
4100
4101 /** {@inheritDoc} */
4102 @Override
4103 public void removeOwners() throws DeviceNotAvailableException {
4104 throw new UnsupportedOperationException("No support for user's feature.");
4105 }
4106
Guang Zhu03c985e2017-04-28 14:53:55 -07004107 /**
4108 * {@inheritDoc}
4109 */
4110 @Override
4111 public void disableKeyguard() throws DeviceNotAvailableException {
4112 throw new UnsupportedOperationException("No support for Window Manager's features");
4113 }
4114
Tony Mak14e4dee2017-04-12 14:37:34 +01004115 /** {@inheritDoc} */
Julien Desprez4c8aabc2016-03-14 15:53:48 +00004116 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00004117 public String getDeviceClass() {
4118 IDevice device = getIDevice();
4119 if (device == null) {
4120 CLog.w("No IDevice instance, cannot determine device class.");
4121 return "";
4122 }
4123 return device.getClass().getSimpleName();
4124 }
Julien Desprez26bee8d2016-03-29 12:09:48 +01004125
4126 /**
4127 * {@inheritDoc}
4128 */
4129 @Override
4130 public void preInvocationSetup(IBuildInfo info)
4131 throws TargetSetupError, DeviceNotAvailableException {
Julien Desprezfd2aac02019-04-12 17:49:29 -07004132 // Default implementation
4133 mContentProvider = null;
4134 mShouldSkipContentProviderSetup = false;
Julien Desprez78de5532019-05-17 10:46:27 -07004135 try {
4136 mExecuteShellCommandLogs =
4137 FileUtil.createTempFile("TestDevice_ExecuteShellCommands", ".txt");
4138 } catch (IOException e) {
4139 throw new TargetSetupError(
4140 "Failed to create the executeShellCommand log file.", e, getDeviceDescriptor());
4141 }
Julien Desprez26bee8d2016-03-29 12:09:48 +01004142 }
4143
4144 /**
4145 * {@inheritDoc}
4146 */
4147 @Override
4148 public void postInvocationTearDown() {
Julien Desprez78de5532019-05-17 10:46:27 -07004149 FileUtil.deleteFile(mExecuteShellCommandLogs);
4150 mExecuteShellCommandLogs = null;
Julien Desprez1dc6b392019-03-18 11:49:13 -07004151 // Default implementation
4152 if (getIDevice() instanceof StubDevice) {
4153 return;
4154 }
Julien Desprez3cca2bc2019-04-05 17:15:16 -07004155 // Reset the Content Provider bit.
4156 mShouldSkipContentProviderSetup = false;
Julien Desprez1dc6b392019-03-18 11:49:13 -07004157 try {
Julien Desprezd7ad0d52019-03-28 10:57:55 -07004158 // If we never installed it, don't even bother checking for it during tear down.
4159 if (mContentProvider == null) {
4160 return;
4161 }
Julien Desprez7aad51b2019-04-18 09:15:19 -07004162 if (TestDeviceState.ONLINE.equals(getDeviceState())) {
4163 mContentProvider.tearDown();
4164 }
Julien Desprez1dc6b392019-03-18 11:49:13 -07004165 } catch (DeviceNotAvailableException e) {
4166 CLog.e(e);
4167 }
Julien Desprez26bee8d2016-03-29 12:09:48 +01004168 }
Julien Despreze824e8b2016-06-08 17:21:58 +01004169
4170 /**
4171 * {@inheritDoc}
4172 */
4173 @Override
4174 public boolean isHeadless() throws DeviceNotAvailableException {
4175 if (getProperty(HEADLESS_PROP) != null) {
4176 return true;
4177 }
4178 return false;
4179 }
Julien Desprez16184162016-06-10 08:56:17 +01004180
4181 protected void checkApiLevelAgainst(String feature, int strictMinLevel) {
4182 try {
4183 if (getApiLevel() < strictMinLevel){
4184 throw new IllegalArgumentException(String.format("%s not supported on %s. "
4185 + "Must be API %d.", feature, getSerialNumber(), strictMinLevel));
4186 }
4187 } catch (DeviceNotAvailableException e) {
4188 throw new RuntimeException("Device became unavailable while checking API level", e);
4189 }
4190 }
Julien Desprez07fe1532016-08-12 11:48:32 +01004191
4192 /**
4193 * {@inheritDoc}
4194 */
4195 @Override
4196 public DeviceDescriptor getDeviceDescriptor() {
4197 IDeviceSelection selector = new DeviceSelectionOptions();
4198 IDevice idevice = getIDevice();
Julien Desprezc98669f2018-11-19 14:05:59 -08004199 try {
Julien Desprez057d0c42019-01-18 11:48:22 -08004200 boolean isTemporary = false;
4201 if (idevice instanceof NullDevice) {
4202 isTemporary = ((NullDevice) idevice).isTemporary();
4203 }
Julien Desprezc98669f2018-11-19 14:05:59 -08004204 return new DeviceDescriptor(
4205 idevice.getSerialNumber(),
4206 idevice instanceof StubDevice,
4207 idevice.getState(),
4208 getAllocationState(),
4209 getDisplayString(selector.getDeviceProductType(idevice)),
4210 getDisplayString(selector.getDeviceProductVariant(idevice)),
4211 getDisplayString(idevice.getProperty("ro.build.version.sdk")),
4212 getDisplayString(idevice.getProperty("ro.build.id")),
Julien Desprez6f028672018-11-29 14:41:01 -08004213 getDisplayString(getBattery()),
Julien Desprezc98669f2018-11-19 14:05:59 -08004214 getDeviceClass(),
4215 getDisplayString(getMacAddress()),
4216 getDisplayString(getSimState()),
4217 getDisplayString(getSimOperator()),
Julien Desprez057d0c42019-01-18 11:48:22 -08004218 isTemporary,
Julien Desprezc98669f2018-11-19 14:05:59 -08004219 idevice);
4220 } catch (RuntimeException e) {
4221 CLog.e("Exception while building device '%s' description:", getSerialNumber());
4222 CLog.e(e);
4223 }
4224 return null;
Julien Desprez07fe1532016-08-12 11:48:32 +01004225 }
4226
4227 /**
4228 * Return the displayable string for given object
4229 */
4230 private String getDisplayString(Object o) {
4231 return o == null ? "unknown" : o.toString();
4232 }
Gopinath65e837a2016-10-06 17:55:12 -07004233
4234 /**
4235 * {@inheritDoc}
4236 */
4237 @Override
4238 public List<ProcessInfo> getProcesses() throws DeviceNotAvailableException {
4239 return PsParser.getProcesses(executeShellCommand(PS_COMMAND));
4240 }
4241
4242 /**
4243 * {@inheritDoc}
4244 */
4245 @Override
4246 public ProcessInfo getProcessByName(String processName) throws DeviceNotAvailableException {
4247 List<ProcessInfo> processList = getProcesses();
4248 for (ProcessInfo processInfo : processList) {
4249 if (processName.equals(processInfo.getName())) {
4250 return processInfo;
4251 }
4252 }
4253 return null;
4254 }
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004255
4256 /**
4257 * Validates that the given input is a valid MAC address
4258 *
4259 * @param address input to validate
4260 * @return true if the input is a valid MAC address
4261 */
4262 boolean isMacAddress(String address) {
4263 Pattern macPattern = Pattern.compile(MAC_ADDRESS_PATTERN);
4264 Matcher macMatcher = macPattern.matcher(address);
4265 return macMatcher.find();
4266 }
4267
4268 /**
4269 * {@inheritDoc}
4270 */
4271 @Override
4272 public String getMacAddress() {
Julien Desprez30715d62018-10-12 12:01:53 -07004273 if (getIDevice() instanceof StubDevice) {
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004274 // Do not query MAC addresses from stub devices.
4275 return null;
4276 }
Kevin Lau Fangfeff6d62016-12-14 17:29:18 -08004277 if (!TestDeviceState.ONLINE.equals(mState)) {
4278 // Only query MAC addresses from online devices.
4279 return null;
4280 }
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004281 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
4282 try {
4283 mIDevice.executeShellCommand(MAC_ADDRESS_COMMAND, receiver);
4284 } catch (IOException | TimeoutException | AdbCommandRejectedException |
4285 ShellCommandUnresponsiveException e) {
4286 CLog.w("Failed to query MAC address for %s", mIDevice.getSerialNumber());
Kevin Lau Fange94d8972017-03-13 15:11:30 -07004287 CLog.w(e);
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004288 }
4289 String output = receiver.getOutput().trim();
4290 if (isMacAddress(output)) {
4291 return output;
4292 }
4293 CLog.d("No valid MAC address queried from device %s", mIDevice.getSerialNumber());
4294 return null;
4295 }
jdesprezbc580f92017-06-02 11:41:40 -07004296
4297 /** {@inheritDoc} */
4298 @Override
4299 public String getSimState() {
4300 try {
4301 return getProperty(SIM_STATE_PROP);
4302 } catch (DeviceNotAvailableException e) {
4303 CLog.w("Failed to query SIM state for %s", mIDevice.getSerialNumber());
4304 CLog.w(e);
4305 return null;
4306 }
4307 }
4308
4309 /** {@inheritDoc} */
4310 @Override
4311 public String getSimOperator() {
4312 try {
4313 return getProperty(SIM_OPERATOR_PROP);
4314 } catch (DeviceNotAvailableException e) {
4315 CLog.w("Failed to query SIM operator for %s", mIDevice.getSerialNumber());
4316 CLog.w(e);
4317 return null;
4318 }
4319 }
jdesprezd7670322017-08-30 14:12:28 -07004320
jdesprez1fbb2472018-03-07 11:15:38 -08004321 /** {@inheritDoc} */
jdesprezd7670322017-08-30 14:12:28 -07004322 @Override
4323 public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException {
4324 throw new UnsupportedOperationException("dumpHeap is not supported.");
4325 }
4326
jdesprez1fbb2472018-03-07 11:15:38 -08004327 /** {@inheritDoc} */
jdesprezd7670322017-08-30 14:12:28 -07004328 @Override
4329 public String getProcessPid(String process) throws DeviceNotAvailableException {
4330 String output = executeShellCommand(String.format("pidof %s", process)).trim();
4331 if (checkValidPid(output)) {
4332 return output;
4333 }
4334 CLog.e("Failed to find a valid pid for process.");
4335 return null;
4336 }
4337
jdesprez1fbb2472018-03-07 11:15:38 -08004338 /** {@inheritDoc} */
4339 @Override
4340 public void logOnDevice(String tag, LogLevel level, String format, Object... args) {
4341 String message = String.format(format, args);
4342 try {
4343 String levelLetter = logLevelToLogcatLevel(level);
4344 String command = String.format("log -t %s -p %s '%s'", tag, levelLetter, message);
4345 executeShellCommand(command);
4346 } catch (DeviceNotAvailableException e) {
4347 CLog.e("Device went not available when attempting to log '%s'", message);
4348 CLog.e(e);
4349 }
4350 }
4351
4352 /** Convert the {@link LogLevel} to the letter used in log (see 'adb shell log --help'). */
4353 private String logLevelToLogcatLevel(LogLevel level) {
4354 switch (level) {
4355 case DEBUG:
4356 return "d";
4357 case ERROR:
4358 return "e";
4359 case INFO:
4360 return "i";
4361 case VERBOSE:
4362 return "v";
4363 case WARN:
4364 return "w";
4365 default:
4366 return "i";
4367 }
4368 }
4369
Yuji Hachiya7bcf9ca2018-07-16 18:27:49 -07004370 /** {@inheritDoc} */
4371 @Override
4372 public long getTotalMemory() {
4373 // "/proc/meminfo" always returns value in kilobytes.
4374 long totalMemory = 0;
4375 String output = null;
4376 try {
4377 output = executeShellCommand("cat /proc/meminfo | grep MemTotal");
4378 } catch (DeviceNotAvailableException e) {
4379 CLog.e(e);
4380 return -1;
4381 }
4382 if (output.isEmpty()) {
4383 return -1;
4384 }
4385 String[] results = output.split("\\s+");
4386 try {
4387 totalMemory = Long.parseLong(results[1].replaceAll("\\D+", ""));
4388 } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
4389 CLog.e(e);
4390 return -1;
4391 }
4392 return totalMemory * 1024;
4393 }
4394
Julien Desprezb0e88012018-11-19 11:49:48 -08004395 /** {@inheritDoc} */
4396 @Override
4397 public Integer getBattery() {
4398 if (getIDevice() instanceof StubDevice) {
4399 return null;
4400 }
4401 try {
4402 // Use default 5 minutes freshness
4403 Future<Integer> batteryFuture = getIDevice().getBattery();
4404 // Get cached value or wait up to 500ms for battery level query
4405 return batteryFuture.get(500, TimeUnit.MILLISECONDS);
4406 } catch (InterruptedException
4407 | ExecutionException
4408 | java.util.concurrent.TimeoutException e) {
4409 CLog.w(
4410 "Failed to query battery level for %s: %s",
4411 getIDevice().getSerialNumber(), e.toString());
4412 }
4413 return null;
4414 }
4415
Julien Desprez467a41c2019-03-26 09:04:40 -07004416 /** {@inheritDoc} */
4417 @Override
4418 public Set<Integer> listDisplayIds() throws DeviceNotAvailableException {
4419 throw new UnsupportedOperationException("dumpsys SurfaceFlinger is not supported.");
4420 }
4421
Julien Despreza5397f12019-03-28 09:46:16 -07004422 /** {@inheritDoc} */
4423 @Override
4424 public long getLastExpectedRebootTimeMillis() {
4425 return mLastTradefedRebootTime;
4426 }
4427
Julien Desprezb48b9e22019-05-16 18:52:22 -07004428 /** {@inheritDoc} */
4429 @Override
4430 public List<File> getTombstones() throws DeviceNotAvailableException {
4431 List<File> tombstones = new ArrayList<>();
4432 if (!isAdbRoot()) {
4433 CLog.w("Device was not root, cannot collect tombstones.");
4434 return tombstones;
4435 }
4436 for (String tombName : getChildren(TOMBSTONE_PATH)) {
4437 File tombFile = pullFile(TOMBSTONE_PATH + tombName);
4438 if (tombFile != null) {
4439 tombstones.add(tombFile);
4440 }
4441 }
4442 return tombstones;
4443 }
4444
jdesprezd7670322017-08-30 14:12:28 -07004445 /** Validate that pid is an integer and not empty. */
4446 private boolean checkValidPid(String output) {
4447 if (output.isEmpty()) {
4448 return false;
4449 }
4450 try {
4451 Integer.parseInt(output);
4452 } catch (NumberFormatException e) {
4453 CLog.e(e);
4454 return false;
4455 }
4456 return true;
4457 }
Guang Zhu26cca482017-11-01 18:18:08 -07004458
Jeffrey Lu279122f2018-01-29 17:25:08 -08004459 /** Gets the {@link IHostOptions} instance to use. */
4460 @VisibleForTesting
Guang Zhu26cca482017-11-01 18:18:08 -07004461 IHostOptions getHostOptions() {
4462 return GlobalConfiguration.getInstance().getHostOptions();
4463 }
Julien Desprez1dc6b392019-03-18 11:49:13 -07004464
4465 /** Returns the {@link ContentProviderHandler} or null if not available. */
4466 @VisibleForTesting
4467 ContentProviderHandler getContentProvider() throws DeviceNotAvailableException {
Julien Desprez13399b92019-04-05 12:31:18 -07004468 // If disabled at the device level, don't attempt any checks.
4469 if (!getOptions().shouldUseContentProvider()) {
4470 return null;
4471 }
Julien Desprezddc36132019-04-11 08:30:22 -07004472 // Prevent usage of content provider before API 28 as it would not work well since content
4473 // tool is not working before P.
4474 if (getApiLevel() < 28) {
Julien Desprez2bfb8422019-03-28 08:28:58 -07004475 return null;
4476 }
Julien Desprez1dc6b392019-03-18 11:49:13 -07004477 if (mContentProvider == null) {
4478 mContentProvider = new ContentProviderHandler(this);
4479 }
4480 if (!mShouldSkipContentProviderSetup) {
4481 boolean res = mContentProvider.setUp();
4482 if (!res) {
jovanakf39a78c2019-04-02 17:08:43 -07004483 // TODO: once CP becomes a requirement, throw/fail the test if CP can't be found
Julien Desprez1dc6b392019-03-18 11:49:13 -07004484 return null;
4485 }
jovanakf39a78c2019-04-02 17:08:43 -07004486 mShouldSkipContentProviderSetup = true;
Julien Desprez1dc6b392019-03-18 11:49:13 -07004487 }
4488 return mContentProvider;
4489 }
Julien Desprez0b44ad32019-05-15 19:02:45 -07004490
4491 /** Reset the flag for content provider setup in order to trigger it again. */
4492 void resetContentProviderSetup() {
4493 mShouldSkipContentProviderSetup = false;
4494 }
Julien Desprez78de5532019-05-17 10:46:27 -07004495
4496 /** The log that contains all the {@link #executeShellCommand(String)} logs. */
4497 public final File getExecuteShellCommandLog() {
4498 return mExecuteShellCommandLogs;
4499 }
Julien Desprez6961b272016-02-01 09:58:23 +00004500}