blob: 8df4860e149444ff51b0123d61011b48c14250dd [file] [log] [blame]
Brett Chabot74121d82010-01-28 20:14:27 -08001/*
2 * Copyright (C) 2010 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 */
Brett Chabot74121d82010-01-28 20:14:27 -080016package com.android.tradefed.device;
17
Julien Desprezc8474552016-02-17 10:59:27 +000018import com.android.ddmlib.AdbCommandRejectedException;
Brett Chabot74121d82010-01-28 20:14:27 -080019import com.android.ddmlib.IDevice;
Julien Desprezc8474552016-02-17 10:59:27 +000020import com.android.ddmlib.InstallException;
jdesprezb1469112018-02-15 09:57:25 -080021import com.android.ddmlib.InstallReceiver;
Julien Desprezc8474552016-02-17 10:59:27 +000022import com.android.ddmlib.RawImage;
23import com.android.ddmlib.ShellCommandUnresponsiveException;
24import com.android.ddmlib.SyncException;
25import com.android.ddmlib.TimeoutException;
26import com.android.tradefed.log.LogUtil.CLog;
27import com.android.tradefed.result.ByteArrayInputStreamSource;
Julien Despreze722b232019-03-28 18:47:14 -070028import com.android.tradefed.result.FileInputStreamSource;
Julien Desprezc8474552016-02-17 10:59:27 +000029import com.android.tradefed.result.InputStreamSource;
Julien Desprez467a41c2019-03-26 09:04:40 -070030import com.android.tradefed.util.CommandResult;
31import com.android.tradefed.util.CommandStatus;
Julien Desprez14e96692017-01-12 12:31:29 +000032import com.android.tradefed.util.KeyguardControllerState;
Julien Desprez4c8aabc2016-03-14 15:53:48 +000033import com.android.tradefed.util.RunUtil;
Julien Desprezc8474552016-02-17 10:59:27 +000034import com.android.tradefed.util.StreamUtil;
Rett Berg2b683362019-02-13 11:36:34 -080035import com.android.tradefed.util.UserUtil;
Julien Desprezc8474552016-02-17 10:59:27 +000036
Julien Desprez1fadf1a2016-10-20 15:50:46 +010037import com.google.common.annotations.VisibleForTesting;
jdesprezd7670322017-08-30 14:12:28 -070038import com.google.common.base.Strings;
Julien Desprez1fadf1a2016-10-20 15:50:46 +010039
Julien Desprezc8474552016-02-17 10:59:27 +000040import java.awt.Image;
41import java.awt.image.BufferedImage;
42import java.io.ByteArrayOutputStream;
43import java.io.File;
44import java.io.IOException;
45import java.util.ArrayList;
46import java.util.Arrays;
Julien Desprez4c8aabc2016-03-14 15:53:48 +000047import java.util.HashMap;
Julien Desprezc8474552016-02-17 10:59:27 +000048import java.util.HashSet;
49import java.util.List;
50import java.util.Map;
51import java.util.Set;
jdesprezbc580f92017-06-02 11:41:40 -070052import java.util.concurrent.TimeUnit;
Julien Desprezc8474552016-02-17 10:59:27 +000053import java.util.regex.Matcher;
54import java.util.regex.Pattern;
55
56import javax.imageio.ImageIO;
Brett Chabotbbea34d2011-06-10 13:48:41 -070057
Brett Chabot74121d82010-01-28 20:14:27 -080058/**
Julien Desprez6961b272016-02-01 09:58:23 +000059 * Implementation of a {@link ITestDevice} for a full stack android device
Brett Chabot74121d82010-01-28 20:14:27 -080060 */
Julien Desprez2f34e382016-06-21 12:30:39 +010061public class TestDevice extends NativeDevice {
Guang Zhubd90e4d2015-09-08 17:44:12 -070062
Julien Desprezc8474552016-02-17 10:59:27 +000063 /** number of attempts made to clear dialogs */
64 private static final int NUM_CLEAR_ATTEMPTS = 5;
65 /** the command used to dismiss a error dialog. Currently sends a DPAD_CENTER key event */
66 static final String DISMISS_DIALOG_CMD = "input keyevent 23";
Julien Desprez0e849332017-01-18 12:22:30 +000067 /** Commands that can be used to dismiss the keyguard. */
Julien Desprez088e5c92019-03-20 09:10:25 -070068 public static final String DISMISS_KEYGUARD_CMD = "input keyevent 82";
Julien Desprez0e849332017-01-18 12:22:30 +000069
70 /**
71 * Alternative command to dismiss the keyguard by requesting the Window Manager service to do
72 * it. Api 23 and after.
73 */
74 static final String DISMISS_KEYGUARD_WM_CMD = "wm dismiss-keyguard";
75
Julien Desprezc8474552016-02-17 10:59:27 +000076 /** Timeout to wait for input dispatch to become ready **/
77 private static final long INPUT_DISPATCH_READY_TIMEOUT = 5 * 1000;
78 /** command to test input dispatch readiness **/
79 private static final String TEST_INPUT_CMD = "dumpsys input";
80
Julien Desprez4c8aabc2016-03-14 15:53:48 +000081 private static final long AM_COMMAND_TIMEOUT = 10 * 1000;
82 private static final long CHECK_NEW_USER = 1000;
83
Julien Desprezc8474552016-02-17 10:59:27 +000084 static final String LIST_PACKAGES_CMD = "pm list packages -f";
85 private static final Pattern PACKAGE_REGEX = Pattern.compile("package:(.*)=(.*)");
86
Jiyong Park3396a842018-12-17 14:01:54 +090087 static final String LIST_APEXES_CMD = "pm list packages --apex-only --show-versioncode";
88 private static final Pattern APEXES_REGEX = Pattern.compile("package:(.*) versionCode:(.*)");
89
Julien Desprezc8474552016-02-17 10:59:27 +000090 private static final int FLAG_PRIMARY = 1; // From the UserInfo class
91
Julien Desprez4c8aabc2016-03-14 15:53:48 +000092 private static final String[] SETTINGS_NAMESPACE = {"system", "secure", "global"};
93
Julien Desprez0f0a02a2017-03-01 16:59:10 +000094 /** user pattern in the output of "pm list users" = TEXT{<id>:<name>:<flags>} TEXT * */
95 private static final String USER_PATTERN = "(.*?\\{)(\\d+)(:)(.*)(:)(\\d+)(\\}.*)";
Julien Desprez467a41c2019-03-26 09:04:40 -070096 /** Pattern to find the display ids of "dumpsys SurfaceFlinger" */
97 private static final String DISPLAY_ID_PATTERN = "(Display )(?<id>\\d+)( color modes:)";
Julien Desprez4c8aabc2016-03-14 15:53:48 +000098
Julien Desprez3faaefd2016-05-24 19:25:12 +010099 private static final int API_LEVEL_GET_CURRENT_USER = 24;
jdesprezbc580f92017-06-02 11:41:40 -0700100 /** Timeout to wait for a screenshot before giving up to avoid hanging forever */
101 private static final long MAX_SCREENSHOT_TIMEOUT = 5 * 60 * 1000; // 5 min
Julien Desprez3faaefd2016-05-24 19:25:12 +0100102
jdesprezd7670322017-08-30 14:12:28 -0700103 /** adb shell am dumpheap <service pid> <dump file path> */
104 private static final String DUMPHEAP_CMD = "am dumpheap %s %s";
105 /** Time given to a file to be dumped on device side */
106 private static final long DUMPHEAP_TIME = 5000l;
107
jdesprezb1469112018-02-15 09:57:25 -0800108 /** Timeout in minutes for the package installation */
109 static final long INSTALL_TIMEOUT_MINUTES = 4;
110 /** Max timeout to output for package installation */
111 static final long INSTALL_TIMEOUT_TO_OUTPUT_MINUTES = 3;
112
Julien Desprezb1301842018-04-16 10:12:52 -0700113 private boolean mWasWifiHelperInstalled = false;
114
Jiyong Park30475a82018-12-03 22:25:00 +0900115 private static final String APEX_SUFFIX = ".apex";
116 private static final String APEX_ARG = "--apex";
117
Julien Desprezc8474552016-02-17 10:59:27 +0000118 /**
Julien Desprez6961b272016-02-01 09:58:23 +0000119 * @param device
120 * @param stateMonitor
121 * @param allocationMonitor
Omari Stephens718c9622011-02-02 18:17:32 -0800122 */
Julien Desprez6961b272016-02-01 09:58:23 +0000123 public TestDevice(IDevice device, IDeviceStateMonitor stateMonitor,
124 IDeviceMonitor allocationMonitor) {
125 super(device, stateMonitor, allocationMonitor);
Kevin Lau Fang3435aff2016-01-29 01:47:12 +0000126 }
Julien Desprezc8474552016-02-17 10:59:27 +0000127
128 /**
129 * Core implementation of package installation, with retries around
130 * {@link IDevice#installPackage(String, boolean, String...)}
131 * @param packageFile
132 * @param reinstall
133 * @param extraArgs
134 * @return the response from the installation
135 * @throws DeviceNotAvailableException
136 */
137 private String internalInstallPackage(
138 final File packageFile, final boolean reinstall, final List<String> extraArgs)
139 throws DeviceNotAvailableException {
Jiyong Park30475a82018-12-03 22:25:00 +0900140 List<String> args = new ArrayList<>(extraArgs);
141 if (packageFile.getName().endsWith(APEX_SUFFIX)) {
142 args.add(APEX_ARG);
143 }
Julien Desprezc8474552016-02-17 10:59:27 +0000144 // use array to store response, so it can be returned to caller
145 final String[] response = new String[1];
jdesprezb1469112018-02-15 09:57:25 -0800146 DeviceAction installAction =
147 new DeviceAction() {
148 @Override
149 public boolean run() throws InstallException {
150 try {
151 InstallReceiver receiver = createInstallReceiver();
152 getIDevice()
153 .installPackage(
154 packageFile.getAbsolutePath(),
155 reinstall,
156 receiver,
157 INSTALL_TIMEOUT_MINUTES,
158 INSTALL_TIMEOUT_TO_OUTPUT_MINUTES,
159 TimeUnit.MINUTES,
Jiyong Park30475a82018-12-03 22:25:00 +0900160 args.toArray(new String[] {}));
jdesprezb1469112018-02-15 09:57:25 -0800161 if (receiver.isSuccessfullyCompleted()) {
162 response[0] = null;
163 } else if (receiver.getErrorMessage() == null) {
164 response[0] =
165 String.format(
166 "Installation of %s timed out",
167 packageFile.getAbsolutePath());
168 } else {
169 response[0] = receiver.getErrorMessage();
170 }
171 } catch (InstallException e) {
Julien Desprez1307c5e2018-10-16 08:59:10 -0700172 String message = e.getMessage();
173 if (message == null) {
174 message =
175 String.format(
176 "InstallException during package installation. "
177 + "cause: %s",
178 StreamUtil.getStackTrace(e));
179 }
180 response[0] = message;
jdesprezb1469112018-02-15 09:57:25 -0800181 }
182 return response[0] == null;
183 }
184 };
Julien Desprezc8474552016-02-17 10:59:27 +0000185 performDeviceAction(String.format("install %s", packageFile.getAbsolutePath()),
186 installAction, MAX_RETRY_ATTEMPTS);
187 return response[0];
188 }
189
190 /**
jdesprezb1469112018-02-15 09:57:25 -0800191 * Creates and return an {@link InstallReceiver} for {@link #internalInstallPackage(File,
192 * boolean, List)} and {@link #installPackage(File, File, boolean, String...)} testing.
193 */
194 @VisibleForTesting
195 InstallReceiver createInstallReceiver() {
196 return new InstallReceiver();
197 }
198
199 /**
Julien Desprezc8474552016-02-17 10:59:27 +0000200 * {@inheritDoc}
201 */
202 @Override
203 public String installPackage(final File packageFile, final boolean reinstall,
204 final String... extraArgs) throws DeviceNotAvailableException {
205 boolean runtimePermissionSupported = isRuntimePermissionSupported();
206 List<String> args = new ArrayList<>(Arrays.asList(extraArgs));
207 // grant all permissions by default if feature is supported
208 if (runtimePermissionSupported) {
209 args.add("-g");
210 }
211 return internalInstallPackage(packageFile, reinstall, args);
212 }
213
214 /**
215 * {@inheritDoc}
216 */
217 @Override
218 public String installPackage(File packageFile, boolean reinstall, boolean grantPermissions,
219 String... extraArgs) throws DeviceNotAvailableException {
220 ensureRuntimePermissionSupported();
221 List<String> args = new ArrayList<>(Arrays.asList(extraArgs));
222 if (grantPermissions) {
223 args.add("-g");
224 }
225 return internalInstallPackage(packageFile, reinstall, args);
226 }
227
228 /**
229 * {@inheritDoc}
230 */
231 @Override
232 public String installPackageForUser(File packageFile, boolean reinstall, int userId,
233 String... extraArgs) throws DeviceNotAvailableException {
234 boolean runtimePermissionSupported = isRuntimePermissionSupported();
235 List<String> args = new ArrayList<>(Arrays.asList(extraArgs));
236 // grant all permissions by default if feature is supported
237 if (runtimePermissionSupported) {
238 args.add("-g");
239 }
240 args.add("--user");
241 args.add(Integer.toString(userId));
242 return internalInstallPackage(packageFile, reinstall, args);
243 }
244
245 /**
246 * {@inheritDoc}
247 */
248 @Override
249 public String installPackageForUser(File packageFile, boolean reinstall,
250 boolean grantPermissions, int userId, String... extraArgs)
251 throws DeviceNotAvailableException {
252 ensureRuntimePermissionSupported();
253 List<String> args = new ArrayList<>(Arrays.asList(extraArgs));
254 if (grantPermissions) {
255 args.add("-g");
256 }
257 args.add("--user");
258 args.add(Integer.toString(userId));
259 return internalInstallPackage(packageFile, reinstall, args);
260 }
261
262 public String installPackage(final File packageFile, final File certFile,
263 final boolean reinstall, final String... extraArgs) throws DeviceNotAvailableException {
264 // use array to store response, so it can be returned to caller
265 final String[] response = new String[1];
jdesprezb1469112018-02-15 09:57:25 -0800266 DeviceAction installAction =
267 new DeviceAction() {
268 @Override
269 public boolean run()
270 throws InstallException, SyncException, IOException, TimeoutException,
271 AdbCommandRejectedException {
272 // TODO: create a getIDevice().installPackage(File, File...) method when the
273 // dist cert functionality is ready to be open sourced
274 String remotePackagePath =
275 getIDevice().syncPackageToDevice(packageFile.getAbsolutePath());
276 String remoteCertPath =
277 getIDevice().syncPackageToDevice(certFile.getAbsolutePath());
278 // trick installRemotePackage into issuing a 'pm install <apk> <cert>'
279 // command, by adding apk path to extraArgs, and using cert as the
280 // 'apk file'.
281 String[] newExtraArgs = new String[extraArgs.length + 1];
282 System.arraycopy(extraArgs, 0, newExtraArgs, 0, extraArgs.length);
283 newExtraArgs[newExtraArgs.length - 1] =
284 String.format("\"%s\"", remotePackagePath);
285 try {
286 InstallReceiver receiver = createInstallReceiver();
287 getIDevice()
288 .installRemotePackage(
289 remoteCertPath,
290 reinstall,
291 receiver,
292 INSTALL_TIMEOUT_MINUTES,
293 INSTALL_TIMEOUT_TO_OUTPUT_MINUTES,
294 TimeUnit.MINUTES,
295 newExtraArgs);
296 if (receiver.isSuccessfullyCompleted()) {
297 response[0] = null;
298 } else if (receiver.getErrorMessage() == null) {
299 response[0] =
300 String.format(
301 "Installation of %s timed out.",
302 packageFile.getAbsolutePath());
303 } else {
304 response[0] = receiver.getErrorMessage();
305 }
306 } catch (InstallException e) {
Julien Desprez1307c5e2018-10-16 08:59:10 -0700307 String message = e.getMessage();
308 if (message == null) {
309 message =
310 String.format(
311 "InstallException during package installation. "
312 + "cause: %s",
313 StreamUtil.getStackTrace(e));
314 }
315 response[0] = message;
jdesprezb1469112018-02-15 09:57:25 -0800316 } finally {
317 getIDevice().removeRemotePackage(remotePackagePath);
318 getIDevice().removeRemotePackage(remoteCertPath);
319 }
320 return true;
321 }
322 };
Julien Desprezc8474552016-02-17 10:59:27 +0000323 performDeviceAction(String.format("install %s", packageFile.getAbsolutePath()),
324 installAction, MAX_RETRY_ATTEMPTS);
325 return response[0];
326 }
327
328 /**
329 * {@inheritDoc}
330 */
331 @Override
332 public String uninstallPackage(final String packageName) throws DeviceNotAvailableException {
333 // use array to store response, so it can be returned to caller
334 final String[] response = new String[1];
335 DeviceAction uninstallAction = new DeviceAction() {
336 @Override
337 public boolean run() throws InstallException {
338 CLog.d("Uninstalling %s", packageName);
339 String result = getIDevice().uninstallPackage(packageName);
340 response[0] = result;
341 return result == null;
342 }
343 };
344 performDeviceAction(String.format("uninstall %s", packageName), uninstallAction,
345 MAX_RETRY_ATTEMPTS);
346 return response[0];
347 }
348
Betty Zhoucd2abe12018-10-22 15:18:55 -0700349 /**
350 * Core implementation for installing application with split apk files {@link
Julien Desprez088e5c92019-03-20 09:10:25 -0700351 * IDevice#installPackages(List, boolean, List)} See
352 * "https://developer.android.com/studio/build/configure-apk-splits" on how to split apk to
353 * several files.
Betty Zhoucd2abe12018-10-22 15:18:55 -0700354 *
355 * @param packageFiles the local apk files
356 * @param reinstall <code>true</code> if a reinstall should be performed
357 * @param extraArgs optional extra arguments to pass. See 'adb shell pm install --help' for
358 * available options.
359 * @return the response from the installation <code>null</code> if installation succeeds.
360 * @throws DeviceNotAvailableException
361 */
362 private String internalInstallPackages(
363 final List<File> packageFiles, final boolean reinstall, final List<String> extraArgs)
364 throws DeviceNotAvailableException {
365 // use array to store response, so it can be returned to caller
366 final String[] response = new String[1];
367 DeviceAction installAction =
368 new DeviceAction() {
369 @Override
370 public boolean run() throws InstallException {
371 try {
372 getIDevice()
373 .installPackages(
374 packageFiles,
375 reinstall,
Betty Zhouf8c46a22019-01-16 12:08:16 -0800376 extraArgs,
Betty Zhoucd2abe12018-10-22 15:18:55 -0700377 INSTALL_TIMEOUT_MINUTES,
378 TimeUnit.MINUTES);
379 response[0] = null;
380 return true;
381 } catch (InstallException e) {
382 response[0] = e.getMessage();
383 if (response[0] == null) {
384 response[0] =
385 String.format(
386 "InstallException: %s",
387 StreamUtil.getStackTrace(e));
388 }
389 return false;
390 }
391 }
392 };
393 performDeviceAction(
394 String.format("install %s", packageFiles.toString()),
395 installAction,
396 MAX_RETRY_ATTEMPTS);
397 return response[0];
398 }
399
400 /** {@inheritDoc} */
401 @Override
402 public String installPackages(
403 final List<File> packageFiles, final boolean reinstall, final String... extraArgs)
404 throws DeviceNotAvailableException {
405 // Grant all permissions by default if feature is supported
406 return installPackages(packageFiles, reinstall, isRuntimePermissionSupported(), extraArgs);
407 }
408
409 /** {@inheritDoc} */
410 @Override
411 public String installPackages(
412 List<File> packageFiles,
413 boolean reinstall,
414 boolean grantPermissions,
415 String... extraArgs)
416 throws DeviceNotAvailableException {
417 List<String> args = new ArrayList<>(Arrays.asList(extraArgs));
418 if (grantPermissions) {
419 ensureRuntimePermissionSupported();
420 args.add("-g");
421 }
422 return internalInstallPackages(packageFiles, reinstall, args);
423 }
424
Betty Zhouf8c46a22019-01-16 12:08:16 -0800425 /** {@inheritDoc} */
426 @Override
427 public String installPackagesForUser(
428 List<File> packageFiles, boolean reinstall, int userId, String... extraArgs)
429 throws DeviceNotAvailableException {
430 // Grant all permissions by default if feature is supported
431 return installPackagesForUser(
432 packageFiles, reinstall, isRuntimePermissionSupported(), userId, extraArgs);
433 }
434
435 /** {@inheritDoc} */
436 @Override
437 public String installPackagesForUser(
438 List<File> packageFiles,
439 boolean reinstall,
440 boolean grantPermissions,
441 int userId,
442 String... extraArgs)
443 throws DeviceNotAvailableException {
444 List<String> args = new ArrayList<>(Arrays.asList(extraArgs));
445 if (grantPermissions) {
446 ensureRuntimePermissionSupported();
447 args.add("-g");
448 }
449 args.add("--user");
450 args.add(Integer.toString(userId));
451 return internalInstallPackages(packageFiles, reinstall, args);
452 }
453
Betty Zhoucd2abe12018-10-22 15:18:55 -0700454 /**
455 * Core implementation for split apk remote installation {@link IDevice#installPackage(String,
Julien Desprez088e5c92019-03-20 09:10:25 -0700456 * boolean, String...)} See "https://developer.android.com/studio/build/configure-apk-splits" on
457 * how to split apk to several files.
Betty Zhoucd2abe12018-10-22 15:18:55 -0700458 *
Julien Desprez088e5c92019-03-20 09:10:25 -0700459 * @param remoteApkPaths the remote apk file paths
Betty Zhoucd2abe12018-10-22 15:18:55 -0700460 * @param reinstall <code>true</code> if a reinstall should be performed
461 * @param extraArgs optional extra arguments to pass. See 'adb shell pm install --help' for
462 * available options.
463 * @return the response from the installation <code>null</code> if installation succeeds.
464 * @throws DeviceNotAvailableException
465 */
466 private String internalInstallRemotePackages(
467 final List<String> remoteApkPaths,
468 final boolean reinstall,
469 final List<String> extraArgs)
470 throws DeviceNotAvailableException {
471 // use array to store response, so it can be returned to caller
472 final String[] response = new String[1];
473 DeviceAction installAction =
474 new DeviceAction() {
475 @Override
476 public boolean run() throws InstallException {
477 try {
478 getIDevice()
479 .installRemotePackages(
480 remoteApkPaths,
481 reinstall,
Betty Zhouf8c46a22019-01-16 12:08:16 -0800482 extraArgs,
Betty Zhoucd2abe12018-10-22 15:18:55 -0700483 INSTALL_TIMEOUT_MINUTES,
484 TimeUnit.MINUTES);
485 response[0] = null;
486 return true;
487 } catch (InstallException e) {
488 response[0] = e.getMessage();
489 if (response[0] == null) {
490 response[0] = String.format(
491 "InstallException during package installation. cause: %s",
492 StreamUtil.getStackTrace(e));
493 }
494 return false;
495 }
496 }
497 };
498 performDeviceAction(
499 String.format("install %s", remoteApkPaths.toString()),
500 installAction,
501 MAX_RETRY_ATTEMPTS);
502 return response[0];
503 }
504
505 /** {@inheritDoc} */
506 @Override
507 public String installRemotePackages(
508 final List<String> remoteApkPaths, final boolean reinstall, final String... extraArgs)
509 throws DeviceNotAvailableException {
510 // Grant all permissions by default if feature is supported
511 return installRemotePackages(
512 remoteApkPaths, reinstall, isRuntimePermissionSupported(), extraArgs);
513 }
514
515 /** {@inheritDoc} */
516 @Override
517 public String installRemotePackages(
518 List<String> remoteApkPaths,
519 boolean reinstall,
520 boolean grantPermissions,
521 String... extraArgs)
522 throws DeviceNotAvailableException {
523 List<String> args = new ArrayList<>(Arrays.asList(extraArgs));
524 if (grantPermissions) {
525 ensureRuntimePermissionSupported();
526 args.add("-g");
527 }
528 return internalInstallRemotePackages(remoteApkPaths, reinstall, args);
529 }
530
Insaf Latypov73f7aeb2017-03-03 11:05:08 +0000531 /** {@inheritDoc} */
Julien Desprezc8474552016-02-17 10:59:27 +0000532 @Override
533 public InputStreamSource getScreenshot() throws DeviceNotAvailableException {
534 return getScreenshot("PNG");
535 }
536
537 /**
538 * {@inheritDoc}
539 */
540 @Override
541 public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException {
Insaf Latypov73f7aeb2017-03-03 11:05:08 +0000542 return getScreenshot(format, true);
543 }
544
545 /** {@inheritDoc} */
546 @Override
547 public InputStreamSource getScreenshot(String format, boolean rescale)
548 throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000549 if (!format.equalsIgnoreCase("PNG") && !format.equalsIgnoreCase("JPEG")){
550 CLog.e("Screenshot: Format %s is not supported, defaulting to PNG.", format);
551 format = "PNG";
552 }
553 ScreenshotAction action = new ScreenshotAction();
554 if (performDeviceAction("screenshot", action, MAX_RETRY_ATTEMPTS)) {
Insaf Latypov73f7aeb2017-03-03 11:05:08 +0000555 byte[] imageData =
556 compressRawImage(action.mRawScreenshot, format.toUpperCase(), rescale);
Julien Desprezc8474552016-02-17 10:59:27 +0000557 if (imageData != null) {
558 return new ByteArrayInputStreamSource(imageData);
559 }
560 }
jdesprez8694ce92017-06-01 16:56:14 -0700561 // Return an error in the buffer
562 return new ByteArrayInputStreamSource(
563 "Error: device reported null for screenshot.".getBytes());
Julien Desprezc8474552016-02-17 10:59:27 +0000564 }
565
Julien Despreze722b232019-03-28 18:47:14 -0700566 /** {@inheritDoc} */
567 @Override
568 public InputStreamSource getScreenshot(int displayId) throws DeviceNotAvailableException {
569 final String tmpDevicePath = String.format("/data/local/tmp/display_%s.png", displayId);
570 CommandResult result =
571 executeShellV2Command(
572 String.format("screencap -p -d %s %s", displayId, tmpDevicePath));
573 if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
574 // Return an error in the buffer
575 CLog.e("Error: device reported error for screenshot: %s", result.getStderr());
576 return null;
577 }
578 try {
579 File tmpScreenshot = pullFile(tmpDevicePath);
580 if (tmpScreenshot == null) {
581 return null;
582 }
583 return new FileInputStreamSource(tmpScreenshot, true);
584 } finally {
585 deleteFile(tmpDevicePath);
586 }
587 }
588
Julien Desprezc8474552016-02-17 10:59:27 +0000589 private class ScreenshotAction implements DeviceAction {
590
591 RawImage mRawScreenshot;
592
593 /**
594 * {@inheritDoc}
595 */
596 @Override
597 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
598 ShellCommandUnresponsiveException, InstallException, SyncException {
jdesprezbc580f92017-06-02 11:41:40 -0700599 mRawScreenshot =
600 getIDevice().getScreenshot(MAX_SCREENSHOT_TIMEOUT, TimeUnit.MILLISECONDS);
Julien Desprezc8474552016-02-17 10:59:27 +0000601 return mRawScreenshot != null;
602 }
603 }
604
Julien Desprez1fadf1a2016-10-20 15:50:46 +0100605 /**
606 * Helper to compress a rawImage obtained from the screen.
Insaf Latypov73f7aeb2017-03-03 11:05:08 +0000607 *
608 * @param rawImage {@link RawImage} to compress.
609 * @param format resulting format of compressed image. PNG and JPEG are supported.
610 * @param rescale if rescaling should be done to further reduce size of compressed image.
611 * @return compressed image.
Julien Desprez1fadf1a2016-10-20 15:50:46 +0100612 */
613 @VisibleForTesting
Insaf Latypov73f7aeb2017-03-03 11:05:08 +0000614 byte[] compressRawImage(RawImage rawImage, String format, boolean rescale) {
615 BufferedImage image = rawImageToBufferedImage(rawImage, format);
616
617 // Rescale to reduce size if needed
618 // Screenshot default format is 1080 x 1920, 8-bit/color RGBA
619 // By cutting in half we can easily keep good quality and smaller size
620 if (rescale) {
621 image = rescaleImage(image);
622 }
623
624 return getImageData(image, format);
625 }
626
627 /**
628 * Converts {@link RawImage} to {@link BufferedImage} in specified format.
629 *
630 * @param rawImage {@link RawImage} to convert.
631 * @param format resulting format of image. PNG and JPEG are supported.
632 * @return converted image.
633 */
634 @VisibleForTesting
635 BufferedImage rawImageToBufferedImage(RawImage rawImage, String format) {
Julien Desprezc8474552016-02-17 10:59:27 +0000636 BufferedImage image = null;
637
638 if ("JPEG".equalsIgnoreCase(format)) {
639 //JPEG does not support ARGB without a special encoder
Insaf Latypov73f7aeb2017-03-03 11:05:08 +0000640 image =
641 new BufferedImage(
642 rawImage.width, rawImage.height, BufferedImage.TYPE_3BYTE_BGR);
Julien Desprezc8474552016-02-17 10:59:27 +0000643 }
644 else {
Insaf Latypov73f7aeb2017-03-03 11:05:08 +0000645 image = new BufferedImage(rawImage.width, rawImage.height, BufferedImage.TYPE_INT_ARGB);
Julien Desprezc8474552016-02-17 10:59:27 +0000646 }
647
648 // borrowed conversion logic from platform/sdk/screenshot/.../Screenshot.java
649 int index = 0;
650 int IndexInc = rawImage.bpp >> 3;
651 for (int y = 0 ; y < rawImage.height ; y++) {
652 for (int x = 0 ; x < rawImage.width ; x++) {
653 int value = rawImage.getARGB(index);
654 index += IndexInc;
655 image.setRGB(x, y, value);
656 }
657 }
658
Insaf Latypov73f7aeb2017-03-03 11:05:08 +0000659 return image;
660 }
661
662 /**
663 * Rescales image cutting it in half.
664 *
665 * @param image source {@link BufferedImage}.
666 * @return resulting scaled image.
667 */
668 @VisibleForTesting
669 BufferedImage rescaleImage(BufferedImage image) {
Julien Desprezc8474552016-02-17 10:59:27 +0000670 int shortEdge = Math.min(image.getHeight(), image.getWidth());
671 if (shortEdge > 720) {
Insaf Latypov73f7aeb2017-03-03 11:05:08 +0000672 Image resized =
673 image.getScaledInstance(
674 image.getWidth() / 2, image.getHeight() / 2, Image.SCALE_SMOOTH);
675 image =
676 new BufferedImage(
677 image.getWidth() / 2, image.getHeight() / 2, Image.SCALE_REPLICATE);
Julien Desprezc8474552016-02-17 10:59:27 +0000678 image.getGraphics().drawImage(resized, 0, 0, null);
679 }
Insaf Latypov73f7aeb2017-03-03 11:05:08 +0000680 return image;
681 }
Julien Desprezc8474552016-02-17 10:59:27 +0000682
Insaf Latypov73f7aeb2017-03-03 11:05:08 +0000683 /**
684 * Gets byte array representation of {@link BufferedImage}.
685 *
686 * @param image source {@link BufferedImage}.
687 * @param format resulting format of image. PNG and JPEG are supported.
688 * @return byte array representation of the image.
689 */
690 @VisibleForTesting
691 byte[] getImageData(BufferedImage image, String format) {
Julien Desprezc8474552016-02-17 10:59:27 +0000692 // store compressed image in memory, and let callers write to persistent storage
693 // use initial buffer size of 128K
694 byte[] imageData = null;
695 ByteArrayOutputStream imageOut = new ByteArrayOutputStream(128*1024);
696 try {
697 if (ImageIO.write(image, format, imageOut)) {
698 imageData = imageOut.toByteArray();
699 } else {
700 CLog.e("Failed to compress screenshot to png");
701 }
702 } catch (IOException e) {
703 CLog.e("Failed to compress screenshot to png");
704 CLog.e(e);
705 }
706 StreamUtil.close(imageOut);
707 return imageData;
708 }
709
710 /**
711 * {@inheritDoc}
712 */
713 @Override
714 public boolean clearErrorDialogs() throws DeviceNotAvailableException {
715 // attempt to clear error dialogs multiple times
716 for (int i = 0; i < NUM_CLEAR_ATTEMPTS; i++) {
717 int numErrorDialogs = getErrorDialogCount();
718 if (numErrorDialogs == 0) {
719 return true;
720 }
721 doClearDialogs(numErrorDialogs);
722 }
723 if (getErrorDialogCount() > 0) {
724 // at this point, all attempts to clear error dialogs completely have failed
725 // it might be the case that the process keeps showing new dialogs immediately after
726 // clearing. There's really no workaround, but to dump an error
727 CLog.e("error dialogs still exist on %s.", getSerialNumber());
728 return false;
729 }
730 return true;
731 }
732
733 /**
734 * Detects the number of crash or ANR dialogs currently displayed.
735 * <p/>
736 * Parses output of 'dump activity processes'
737 *
738 * @return count of dialogs displayed
739 * @throws DeviceNotAvailableException
740 */
741 private int getErrorDialogCount() throws DeviceNotAvailableException {
742 int errorDialogCount = 0;
743 Pattern crashPattern = Pattern.compile(".*crashing=true.*AppErrorDialog.*");
744 Pattern anrPattern = Pattern.compile(".*notResponding=true.*AppNotRespondingDialog.*");
Julien Desprez9316f662019-05-15 17:32:07 -0700745 String systemStatusOutput =
746 executeShellCommand(
747 "dumpsys activity processes | grep -e .*crashing=true.*AppErrorDialog.* -e .*notResponding=true.*AppNotRespondingDialog.*");
Julien Desprezc8474552016-02-17 10:59:27 +0000748 Matcher crashMatcher = crashPattern.matcher(systemStatusOutput);
749 while (crashMatcher.find()) {
750 errorDialogCount++;
751 }
752 Matcher anrMatcher = anrPattern.matcher(systemStatusOutput);
753 while (anrMatcher.find()) {
754 errorDialogCount++;
755 }
756
757 return errorDialogCount;
758 }
759
760 private void doClearDialogs(int numDialogs) throws DeviceNotAvailableException {
761 CLog.i("Attempted to clear %d dialogs on %s", numDialogs, getSerialNumber());
762 for (int i=0; i < numDialogs; i++) {
763 // send DPAD_CENTER
764 executeShellCommand(DISMISS_DIALOG_CMD);
765 }
766 }
767
768 /**
Guang Zhu03c985e2017-04-28 14:53:55 -0700769 * {@inheritDoc}
Julien Desprezc8474552016-02-17 10:59:27 +0000770 */
Guang Zhu03c985e2017-04-28 14:53:55 -0700771 @Override
772 public void disableKeyguard() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000773 long start = System.currentTimeMillis();
774 while (true) {
775 Boolean ready = isDeviceInputReady();
776 if (ready == null) {
777 // unsupported API level, bail
778 break;
779 }
780 if (ready) {
781 // input dispatch is ready, bail
782 break;
783 }
784 long timeSpent = System.currentTimeMillis() - start;
785 if (timeSpent > INPUT_DISPATCH_READY_TIMEOUT) {
786 CLog.w("Timeout after waiting %dms on enabling of input dispatch", timeSpent);
787 // break & proceed anyway
788 break;
789 } else {
790 getRunUtil().sleep(1000);
791 }
792 }
Julien Desprez0e849332017-01-18 12:22:30 +0000793 if (getApiLevel() >= 23) {
794 CLog.i(
795 "Attempting to disable keyguard on %s using %s",
796 getSerialNumber(), DISMISS_KEYGUARD_WM_CMD);
797 String output = executeShellCommand(DISMISS_KEYGUARD_WM_CMD);
798 CLog.i("output of %s: %s", DISMISS_KEYGUARD_WM_CMD, output);
799 } else {
Tsu Chiang Chuangbfee7362017-01-25 14:22:54 -0800800 CLog.i("Command: %s, is not supported, falling back to %s", DISMISS_KEYGUARD_WM_CMD,
801 DISMISS_KEYGUARD_CMD);
Julien Desprez0e849332017-01-18 12:22:30 +0000802 executeShellCommand(DISMISS_KEYGUARD_CMD);
803 }
804 // TODO: check that keyguard was actually dismissed.
Julien Desprezc8474552016-02-17 10:59:27 +0000805 }
806
Julien Desprez14e96692017-01-12 12:31:29 +0000807 /** {@inheritDoc} */
808 @Override
809 public KeyguardControllerState getKeyguardState() throws DeviceNotAvailableException {
810 String output =
811 executeShellCommand("dumpsys activity activities | grep -A3 KeyguardController:");
Julien Desprez8a7bff32017-01-24 13:53:48 +0000812 CLog.d("Output from KeyguardController: %s", output);
Julien Desprez510a4832017-01-27 11:58:17 +0000813 KeyguardControllerState state =
814 KeyguardControllerState.create(Arrays.asList(output.trim().split("\n")));
815 return state;
Julien Desprez14e96692017-01-12 12:31:29 +0000816 }
817
Julien Desprezc8474552016-02-17 10:59:27 +0000818 /**
819 * Tests the device to see if input dispatcher is ready
Julien Desprez14e96692017-01-12 12:31:29 +0000820 *
Julien Desprezc8474552016-02-17 10:59:27 +0000821 * @return <code>null</code> if not supported by platform, or the actual readiness state
822 * @throws DeviceNotAvailableException
823 */
824 Boolean isDeviceInputReady() throws DeviceNotAvailableException {
825 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
826 executeShellCommand(TEST_INPUT_CMD, receiver);
827 String output = receiver.getOutput();
828 Matcher m = INPUT_DISPATCH_STATE_REGEX.matcher(output);
829 if (!m.find()) {
830 // output does not contain the line at all, implying unsupported API level, bail
831 return null;
832 }
833 return "1".equals(m.group(1));
834 }
835
836 /**
837 * {@inheritDoc}
838 */
839 @Override
840 protected void prePostBootSetup() throws DeviceNotAvailableException {
841 if (mOptions.isDisableKeyguard()) {
842 disableKeyguard();
843 }
844 }
845
846 /**
847 * Performs an reboot via framework power manager
848 *
849 * Must have root access, device must be API Level 18 or above
850 *
851 * @param into the mode to reboot into, currently supported: bootloader, recovery, leave it
852 * null for a plain reboot
853 * @return <code>true</code> if the device rebooted, <code>false</code> if not successful or
854 * unsupported
855 * @throws DeviceNotAvailableException
856 */
857 private boolean doAdbFrameworkReboot(final String into) throws DeviceNotAvailableException {
858 // use framework reboot when:
859 // 1. device API level >= 18
860 // 2. has adb root
861 // 3. framework is running
862 if (!isEnableAdbRoot()) {
863 CLog.i("framework reboot is not supported; when enable root is disabled");
864 return false;
865 }
866 enableAdbRoot();
867 if (getApiLevel() >= 18 && isAdbRoot()) {
868 try {
869 // check framework running
870 String output = executeShellCommand("pm path android");
871 if (output == null || !output.contains("package:")) {
872 CLog.v("framework reboot: can't detect framework running");
873 return false;
874 }
875 String command = "svc power reboot";
876 if (into != null && !into.isEmpty()) {
877 command = String.format("%s %s", command, into);
878 }
879 executeShellCommand(command);
880 } catch (DeviceUnresponsiveException due) {
881 CLog.v("framework reboot: device unresponsive to shell command, using fallback");
882 return false;
883 }
Julien Desprezba60f9d2018-08-20 20:59:10 +0000884 boolean notAvailable = waitForDeviceNotAvailable(30 * 1000);
885 if (notAvailable) {
Julien Desprez78344aa2018-09-04 16:06:05 -0700886 postAdbReboot();
Julien Desprezba60f9d2018-08-20 20:59:10 +0000887 }
888 return notAvailable;
Julien Desprezc8474552016-02-17 10:59:27 +0000889 } else {
890 CLog.v("framework reboot: not supported");
891 return false;
892 }
893 }
894
895 /**
896 * Perform a adb reboot.
897 *
898 * @param into the bootloader name to reboot into, or <code>null</code> to just reboot the
899 * device.
900 * @throws DeviceNotAvailableException
901 */
902 @Override
903 protected void doAdbReboot(final String into) throws DeviceNotAvailableException {
Guang Zhuac76f7a2019-05-22 16:46:23 -0700904 if (!TestDeviceState.ONLINE.equals(getDeviceState()) || !doAdbFrameworkReboot(into)) {
Julien Desprez78344aa2018-09-04 16:06:05 -0700905 super.doAdbReboot(into);
Julien Desprezc8474552016-02-17 10:59:27 +0000906 }
907 }
908
909 /**
910 * {@inheritDoc}
911 */
912 @Override
913 public Set<String> getInstalledPackageNames() throws DeviceNotAvailableException {
Julien Desprez0b44ad32019-05-15 19:02:45 -0700914 return getInstalledPackageNames(null, null);
Julien Desprez529381a2019-05-02 15:16:35 -0700915 }
916
917 /** {@inheritDoc} */
918 @Override
919 public boolean isPackageInstalled(String packageName) throws DeviceNotAvailableException {
Julien Desprez0b44ad32019-05-15 19:02:45 -0700920 return getInstalledPackageNames(packageName, null).contains(packageName);
921 }
922
923 /** {@inheritDoc} */
924 @Override
925 public boolean isPackageInstalled(String packageName, String userId)
926 throws DeviceNotAvailableException {
927 return getInstalledPackageNames(packageName, userId).contains(packageName);
Julien Desprezc8474552016-02-17 10:59:27 +0000928 }
929
Jiyong Park3396a842018-12-17 14:01:54 +0900930 /** {@inheritDoc} */
931 @Override
932 public Set<ApexInfo> getActiveApexes() throws DeviceNotAvailableException {
933 Set<ApexInfo> ret = new HashSet<>();
934 String output = executeShellCommand(LIST_APEXES_CMD);
935 if (output != null) {
936 Matcher m = APEXES_REGEX.matcher(output);
937 while (m.find()) {
938 String name = m.group(1);
939 long version = Long.valueOf(m.group(2));
940 ret.add(new ApexInfo(name, version));
941 }
942 }
943 return ret;
944 }
945
Julien Desprezc8474552016-02-17 10:59:27 +0000946 /**
Julien Desprez2f34e382016-06-21 12:30:39 +0100947 * A {@link com.android.tradefed.device.NativeDevice.DeviceAction}
Julien Desprezc8474552016-02-17 10:59:27 +0000948 * for retrieving package system service info, and do retries on
949 * failures.
950 */
951 private class DumpPkgAction implements DeviceAction {
952
953 Map<String, PackageInfo> mPkgInfoMap;
954
955 DumpPkgAction() {
956 }
957
958 @Override
959 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
960 ShellCommandUnresponsiveException, InstallException, SyncException {
961 DumpsysPackageReceiver receiver = new DumpsysPackageReceiver();
962 getIDevice().executeShellCommand("dumpsys package p", receiver);
963 mPkgInfoMap = receiver.getPackages();
964 if (mPkgInfoMap.size() == 0) {
965 // Package parsing can fail if package manager is currently down. throw exception
966 // to retry
967 CLog.w("no packages found from dumpsys package p.");
968 throw new IOException();
969 }
970 return true;
971 }
972 }
973
974 /**
975 * {@inheritDoc}
976 */
977 @Override
978 public Set<String> getUninstallablePackageNames() throws DeviceNotAvailableException {
979 DumpPkgAction action = new DumpPkgAction();
980 performDeviceAction("dumpsys package p", action, MAX_RETRY_ATTEMPTS);
981
982 Set<String> pkgs = new HashSet<String>();
983 for (PackageInfo pkgInfo : action.mPkgInfoMap.values()) {
984 if (!pkgInfo.isSystemApp() || pkgInfo.isUpdatedSystemApp()) {
985 CLog.d("Found uninstallable package %s", pkgInfo.getPackageName());
986 pkgs.add(pkgInfo.getPackageName());
987 }
988 }
989 return pkgs;
990 }
991
992 /**
993 * {@inheritDoc}
994 */
995 @Override
996 public PackageInfo getAppPackageInfo(String packageName) throws DeviceNotAvailableException {
997 DumpPkgAction action = new DumpPkgAction();
998 performDeviceAction("dumpsys package", action, MAX_RETRY_ATTEMPTS);
999 return action.mPkgInfoMap.get(packageName);
1000 }
1001
Julien Desprezc8474552016-02-17 10:59:27 +00001002 // TODO: convert this to use DumpPkgAction
Julien Desprez0b44ad32019-05-15 19:02:45 -07001003 private Set<String> getInstalledPackageNames(String packageNameSearched, String userId)
Julien Desprezc8474552016-02-17 10:59:27 +00001004 throws DeviceNotAvailableException {
1005 Set<String> packages= new HashSet<String>();
Julien Desprez529381a2019-05-02 15:16:35 -07001006 String command = LIST_PACKAGES_CMD;
Julien Desprez0b44ad32019-05-15 19:02:45 -07001007 if (userId != null) {
1008 command += String.format(" --user %s", userId);
1009 }
Julien Desprez529381a2019-05-02 15:16:35 -07001010 if (packageNameSearched != null) {
1011 command += (" | grep " + packageNameSearched);
1012 }
1013 String output = executeShellCommand(command);
Julien Desprezc8474552016-02-17 10:59:27 +00001014 if (output != null) {
1015 Matcher m = PACKAGE_REGEX.matcher(output);
1016 while (m.find()) {
Julien Desprezc8474552016-02-17 10:59:27 +00001017 String packageName = m.group(2);
Julien Desprez0b44ad32019-05-15 19:02:45 -07001018 if (packageNameSearched != null && packageName.equals(packageNameSearched)) {
1019 packages.add(packageName);
1020 } else if (packageNameSearched == null) {
Julien Desprezc8474552016-02-17 10:59:27 +00001021 packages.add(packageName);
1022 }
1023 }
1024 }
1025 return packages;
1026 }
1027
1028 /**
1029 * {@inheritDoc}
1030 */
1031 @Override
1032 public ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
1033 ArrayList<String[]> users = tokenizeListUsers();
Julien Desprezc8474552016-02-17 10:59:27 +00001034 ArrayList<Integer> userIds = new ArrayList<Integer>(users.size());
1035 for (String[] user : users) {
1036 userIds.add(Integer.parseInt(user[1]));
1037 }
1038 return userIds;
1039 }
1040
1041 /**
1042 * Tokenizes the output of 'pm list users'.
1043 * The returned tokens for each user have the form: {"\tUserInfo", Integer.toString(id), name,
1044 * Integer.toHexString(flag), "[running]"}; (the last one being optional)
1045 * @return a list of arrays of strings, each element of the list representing the tokens
1046 * for a user, or {@code null} if there was an error while tokenizing the adb command output.
1047 */
1048 private ArrayList<String[]> tokenizeListUsers() throws DeviceNotAvailableException {
1049 String command = "pm list users";
1050 String commandOutput = executeShellCommand(command);
1051 // Extract the id of all existing users.
1052 String[] lines = commandOutput.split("\\r?\\n");
Julien Desprezc8474552016-02-17 10:59:27 +00001053 if (!lines[0].equals("Users:")) {
jdesprez6b295b12017-11-02 16:00:48 -07001054 throw new DeviceRuntimeException(
1055 String.format("'%s' in not a valid output for 'pm list users'", commandOutput));
Julien Desprezc8474552016-02-17 10:59:27 +00001056 }
1057 ArrayList<String[]> users = new ArrayList<String[]>(lines.length - 1);
1058 for (int i = 1; i < lines.length; i++) {
1059 // Individual user is printed out like this:
1060 // \tUserInfo{$id$:$name$:$Integer.toHexString(flags)$} [running]
1061 String[] tokens = lines[i].split("\\{|\\}|:");
1062 if (tokens.length != 4 && tokens.length != 5) {
jdesprez6b295b12017-11-02 16:00:48 -07001063 throw new DeviceRuntimeException(
1064 String.format(
1065 "device output: '%s' \nline: '%s' was not in the expected "
1066 + "format for user info.",
1067 commandOutput, lines[i]));
Julien Desprezc8474552016-02-17 10:59:27 +00001068 }
1069 users.add(tokens);
1070 }
1071 return users;
1072 }
1073
1074 /**
1075 * {@inheritDoc}
1076 */
1077 @Override
1078 public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
1079 String command = "pm get-max-users";
1080 String commandOutput = executeShellCommand(command);
1081 try {
1082 return Integer.parseInt(commandOutput.substring(commandOutput.lastIndexOf(" ")).trim());
1083 } catch (NumberFormatException e) {
1084 CLog.e("Failed to parse result: %s", commandOutput);
1085 }
1086 return 0;
1087 }
1088
Alex Chau93617352018-01-16 15:22:25 +00001089 /** {@inheritDoc} */
1090 @Override
1091 public int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException {
1092 checkApiLevelAgainstNextRelease("get-max-running-users", 28);
1093 String command = "pm get-max-running-users";
1094 String commandOutput = executeShellCommand(command);
1095 try {
1096 return Integer.parseInt(commandOutput.substring(commandOutput.lastIndexOf(" ")).trim());
1097 } catch (NumberFormatException e) {
1098 CLog.e("Failed to parse result: %s", commandOutput);
1099 }
1100 return 0;
1101 }
1102
Julien Desprezc8474552016-02-17 10:59:27 +00001103 /**
1104 * {@inheritDoc}
1105 */
1106 @Override
1107 public boolean isMultiUserSupported() throws DeviceNotAvailableException {
1108 return getMaxNumberOfUsersSupported() > 1;
1109 }
1110
1111 /**
1112 * {@inheritDoc}
1113 */
1114 @Override
1115 public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException {
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001116 return createUser(name, false, false);
1117 }
1118
Rett Berg2b683362019-02-13 11:36:34 -08001119 /** {@inheritDoc} */
1120 @Override
1121 public int createUserNoThrow(String name) throws DeviceNotAvailableException {
1122 try {
1123 return createUser(name);
1124 } catch (IllegalStateException e) {
1125 CLog.e("Error creating user: " + e.toString());
1126 return -1;
1127 }
1128 }
1129
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001130 /**
1131 * {@inheritDoc}
1132 */
1133 @Override
1134 public int createUser(String name, boolean guest, boolean ephemeral)
1135 throws DeviceNotAvailableException, IllegalStateException {
1136 String command ="pm create-user " + (guest ? "--guest " : "")
1137 + (ephemeral ? "--ephemeral " : "") + name;
1138 final String output = executeShellCommand(command);
Julien Desprezc8474552016-02-17 10:59:27 +00001139 if (output.startsWith("Success")) {
1140 try {
Julien Desprez0b44ad32019-05-15 19:02:45 -07001141 resetContentProviderSetup();
Julien Desprezc8474552016-02-17 10:59:27 +00001142 return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
1143 } catch (NumberFormatException e) {
1144 CLog.e("Failed to parse result: %s", output);
1145 }
Julien Desprezc8474552016-02-17 10:59:27 +00001146 }
jdesprez86eb7742017-08-10 09:16:41 -07001147 throw new IllegalStateException(String.format("Failed to create user: %s", output));
Julien Desprezc8474552016-02-17 10:59:27 +00001148 }
1149
1150 /**
1151 * {@inheritDoc}
1152 */
1153 @Override
1154 public boolean removeUser(int userId) throws DeviceNotAvailableException {
1155 final String output = executeShellCommand(String.format("pm remove-user %s", userId));
1156 if (output.startsWith("Error")) {
1157 CLog.e("Failed to remove user: %s", output);
1158 return false;
1159 }
1160 return true;
1161 }
1162
1163 /**
1164 * {@inheritDoc}
1165 */
1166 @Override
1167 public boolean startUser(int userId) throws DeviceNotAvailableException {
Bookatzfd6f6942019-03-05 14:29:52 -08001168 return startUser(userId, false);
1169 }
1170
1171 /** {@inheritDoc} */
1172 @Override
1173 public boolean startUser(int userId, boolean waitFlag) throws DeviceNotAvailableException {
1174 if (waitFlag) {
1175 checkApiLevelAgainstNextRelease("start-user -w", 29);
1176 }
1177 String cmd = "am start-user " + (waitFlag ? "-w " : "") + userId;
1178
1179 CLog.d("Starting user with command: %s", cmd);
1180 final String output = executeShellCommand(cmd);
Julien Desprezc8474552016-02-17 10:59:27 +00001181 if (output.startsWith("Error")) {
1182 CLog.e("Failed to start user: %s", output);
1183 return false;
1184 }
Bookatzfd6f6942019-03-05 14:29:52 -08001185 if (waitFlag) {
1186 String state = executeShellCommand("am get-started-user-state " + userId);
1187 if (!state.contains("RUNNING_UNLOCKED")) {
1188 CLog.w("User %s is not RUNNING_UNLOCKED after start-user -w. (%s).", userId, state);
1189 return false;
1190 }
1191 }
Julien Desprezc8474552016-02-17 10:59:27 +00001192 return true;
1193 }
1194
1195 /**
1196 * {@inheritDoc}
1197 */
1198 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001199 public boolean stopUser(int userId) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00001200 // No error or status code is returned.
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001201 return stopUser(userId, false, false);
1202 }
1203
1204 /**
1205 * {@inheritDoc}
1206 */
1207 @Override
1208 public boolean stopUser(int userId, boolean waitFlag, boolean forceFlag)
1209 throws DeviceNotAvailableException {
Tony Mak2ec98fa2017-05-31 14:43:39 +01001210 final int apiLevel = getApiLevel();
1211 if (waitFlag && apiLevel < 23) {
1212 throw new IllegalArgumentException("stop-user -w requires API level >= 23");
1213 }
1214 if (forceFlag && apiLevel < 24) {
1215 throw new IllegalArgumentException("stop-user -f requires API level >= 24");
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001216 }
Julien Desprezf06ab1e2016-11-18 11:52:37 +00001217 StringBuilder cmd = new StringBuilder("am stop-user ");
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001218 if (waitFlag) {
Julien Desprezf06ab1e2016-11-18 11:52:37 +00001219 cmd.append("-w ");
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001220 }
1221 if (forceFlag) {
Julien Desprezf06ab1e2016-11-18 11:52:37 +00001222 cmd.append("-f ");
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001223 }
Julien Desprezf06ab1e2016-11-18 11:52:37 +00001224 cmd.append(userId);
1225
1226 CLog.d("stopping user with command: %s", cmd.toString());
1227 final String output = executeShellCommand(cmd.toString());
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001228 if (output.contains("Error: Can't stop system user")) {
1229 CLog.e("Cannot stop System user.");
1230 return false;
1231 }
Tony Mak2ec98fa2017-05-31 14:43:39 +01001232 if (output.contains("Can't stop current user")) {
1233 CLog.e("Cannot stop current user.");
1234 return false;
1235 }
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001236 if (isUserRunning(userId)) {
1237 CLog.w("User Id: %s is still running after the stop-user command.", userId);
1238 return false;
1239 }
1240 return true;
Julien Desprezc8474552016-02-17 10:59:27 +00001241 }
1242
1243 /**
1244 * {@inheritDoc}
1245 */
1246 @Override
1247 public Integer getPrimaryUserId() throws DeviceNotAvailableException {
1248 ArrayList<String[]> users = tokenizeListUsers();
Julien Desprezc8474552016-02-17 10:59:27 +00001249 for (String[] user : users) {
1250 int flag = Integer.parseInt(user[3], 16);
1251 if ((flag & FLAG_PRIMARY) != 0) {
1252 return Integer.parseInt(user[1]);
1253 }
1254 }
1255 return null;
1256 }
1257
Julien Desprez14703a92019-05-14 17:45:39 -07001258 /** {@inheritDoc} */
Julien Desprezc8474552016-02-17 10:59:27 +00001259 @Override
Julien Desprez14703a92019-05-14 17:45:39 -07001260 public int getCurrentUser() throws DeviceNotAvailableException, DeviceRuntimeException {
Julien Desprez3faaefd2016-05-24 19:25:12 +01001261 checkApiLevelAgainstNextRelease("get-current-user", API_LEVEL_GET_CURRENT_USER);
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001262 final String output = executeShellCommand("am get-current-user");
1263 try {
Julien Desprez34ce18b2016-04-21 22:17:18 +01001264 int userId = Integer.parseInt(output.trim());
Julien Desprez14703a92019-05-14 17:45:39 -07001265 if (userId < 0) {
1266 throw new DeviceRuntimeException(
1267 String.format(
1268 "Invalid user id '%s' was returned for get-current-user", userId));
1269 }
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001270 return userId;
1271 } catch (NumberFormatException e) {
Julien Desprez14703a92019-05-14 17:45:39 -07001272 throw new DeviceRuntimeException(e);
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001273 }
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001274 }
1275
1276 private Matcher findUserInfo(String pmListUsersOutput) {
1277 Pattern pattern = Pattern.compile(USER_PATTERN);
1278 Matcher matcher = pattern.matcher(pmListUsersOutput);
1279 return matcher;
1280 }
1281
1282 /**
1283 * {@inheritDoc}
1284 */
1285 @Override
1286 public int getUserFlags(int userId) throws DeviceNotAvailableException {
1287 checkApiLevelAgainst("getUserFlags", 22);
1288 final String commandOutput = executeShellCommand("pm list users");
1289 Matcher matcher = findUserInfo(commandOutput);
1290 while(matcher.find()) {
1291 if (Integer.parseInt(matcher.group(2)) == userId) {
1292 return Integer.parseInt(matcher.group(6), 16);
1293 }
1294 }
1295 CLog.w("Could not find any flags for userId: %d in output: %s", userId, commandOutput);
1296 return INVALID_USER_ID;
1297 }
1298
Rett Berg2b683362019-02-13 11:36:34 -08001299 /** {@inheritDoc} */
1300 @Override
1301 public boolean isUserSecondary(int userId) throws DeviceNotAvailableException {
1302 if (userId == UserUtil.USER_SYSTEM) {
1303 return false;
1304 }
1305 int flags = getUserFlags(userId);
1306 if (flags == INVALID_USER_ID) {
1307 return false;
1308 }
1309 return (flags & UserUtil.FLAGS_NOT_SECONDARY) == 0;
1310 }
1311
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001312 /**
1313 * {@inheritDoc}
1314 */
1315 @Override
1316 public boolean isUserRunning(int userId) throws DeviceNotAvailableException {
1317 checkApiLevelAgainst("isUserIdRunning", 22);
1318 final String commandOutput = executeShellCommand("pm list users");
1319 Matcher matcher = findUserInfo(commandOutput);
1320 while(matcher.find()) {
1321 if (Integer.parseInt(matcher.group(2)) == userId) {
1322 if (matcher.group(7).contains("running")) {
1323 return true;
1324 }
1325 }
1326 }
1327 return false;
1328 }
1329
1330 /**
1331 * {@inheritDoc}
1332 */
1333 @Override
1334 public int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
1335 checkApiLevelAgainst("getUserSerialNumber", 22);
1336 final String commandOutput = executeShellCommand("dumpsys user");
1337 // example: UserInfo{0:Test:13} serialNo=0
1338 String userSerialPatter = "(.*\\{)(\\d+)(.*\\})(.*=)(\\d+)";
1339 Pattern pattern = Pattern.compile(userSerialPatter);
1340 Matcher matcher = pattern.matcher(commandOutput);
1341 while(matcher.find()) {
1342 if (Integer.parseInt(matcher.group(2)) == userId) {
1343 return Integer.parseInt(matcher.group(5));
1344 }
1345 }
1346 CLog.w("Could not find user serial number for userId: %d, in output: %s",
1347 userId, commandOutput);
1348 return INVALID_USER_ID;
1349 }
1350
1351 /**
1352 * {@inheritDoc}
1353 */
1354 @Override
1355 public boolean switchUser(int userId) throws DeviceNotAvailableException {
1356 return switchUser(userId, AM_COMMAND_TIMEOUT);
1357 }
1358
1359 /**
1360 * {@inheritDoc}
1361 */
1362 @Override
1363 public boolean switchUser(int userId, long timeout) throws DeviceNotAvailableException {
Julien Desprez3faaefd2016-05-24 19:25:12 +01001364 checkApiLevelAgainstNextRelease("switchUser", API_LEVEL_GET_CURRENT_USER);
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001365 if (userId == getCurrentUser()) {
1366 CLog.w("Already running as user id: %s. Nothing to be done.", userId);
1367 return true;
1368 }
Julien Desprez0b44ad32019-05-15 19:02:45 -07001369 resetContentProviderSetup();
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001370 executeShellCommand(String.format("am switch-user %d", userId));
1371 long initialTime = getHostCurrentTime();
1372 while (getHostCurrentTime() - initialTime <= timeout) {
1373 if (userId == getCurrentUser()) {
1374 // disable keyguard if option is true
1375 prePostBootSetup();
1376 return true;
1377 } else {
1378 RunUtil.getDefault().sleep(getCheckNewUserSleep());
1379 }
1380 }
1381 CLog.e("User did not switch in the given %d timeout", timeout);
1382 return false;
1383 }
1384
1385 /**
1386 * Exposed for testing.
1387 */
1388 protected long getCheckNewUserSleep() {
1389 return CHECK_NEW_USER;
1390 }
1391
1392 /**
1393 * Exposed for testing
1394 */
1395 protected long getHostCurrentTime() {
1396 return System.currentTimeMillis();
1397 }
1398
1399 /**
1400 * {@inheritDoc}
1401 */
1402 @Override
1403 public boolean hasFeature(String feature) throws DeviceNotAvailableException {
1404 final String output = executeShellCommand("pm list features");
1405 if (output.contains(feature)) {
1406 return true;
1407 }
1408 CLog.w("Feature: %s is not available on %s", feature, getSerialNumber());
1409 return false;
1410 }
1411
1412 /**
1413 * {@inheritDoc}
1414 */
1415 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00001416 public String getSetting(String namespace, String key) throws DeviceNotAvailableException {
Julien Desprez70d8ebd2016-06-01 16:04:21 +01001417 return getSettingInternal("", namespace.trim(), key.trim());
Julien Despreza3d9c0f2016-03-21 12:18:22 +00001418 }
1419
1420 /**
1421 * {@inheritDoc}
1422 */
1423 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001424 public String getSetting(int userId, String namespace, String key)
1425 throws DeviceNotAvailableException {
Julien Desprez70d8ebd2016-06-01 16:04:21 +01001426 return getSettingInternal(String.format("--user %d", userId), namespace.trim(), key.trim());
Julien Despreza3d9c0f2016-03-21 12:18:22 +00001427 }
1428
1429 /**
1430 * Internal Helper to get setting with or without a userId provided.
1431 */
1432 private String getSettingInternal(String userFlag, String namespace, String key)
1433 throws DeviceNotAvailableException {
Julien Desprez70d8ebd2016-06-01 16:04:21 +01001434 namespace = namespace.toLowerCase();
1435 if (Arrays.asList(SETTINGS_NAMESPACE).contains(namespace)) {
Julien Despreza3d9c0f2016-03-21 12:18:22 +00001436 String cmd = String.format("settings %s get %s %s", userFlag, namespace, key);
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001437 String output = executeShellCommand(cmd);
1438 if ("null".equals(output)) {
1439 CLog.w("settings returned null for command: %s. "
1440 + "please check if the namespace:key exists", cmd);
1441 return null;
1442 }
1443 return output.trim();
1444 }
Julien Desprez70d8ebd2016-06-01 16:04:21 +01001445 CLog.e("Namespace requested: '%s' is not part of {system, secure, global}", namespace);
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001446 return null;
1447 }
1448
Yichun Lib1900022018-05-11 09:54:48 -07001449 /** {@inheritDoc} */
1450 @Override
1451 public Map<String, String> getAllSettings(String namespace) throws DeviceNotAvailableException {
1452 return getAllSettingsInternal(namespace.trim());
1453 }
1454
1455 /** Internal helper to get all settings */
1456 private Map<String, String> getAllSettingsInternal(String namespace)
1457 throws DeviceNotAvailableException {
1458 namespace = namespace.toLowerCase();
1459 if (Arrays.asList(SETTINGS_NAMESPACE).contains(namespace)) {
1460 Map<String, String> map = new HashMap<>();
1461 String cmd = String.format("settings list %s", namespace);
1462 String output = executeShellCommand(cmd);
1463 for (String line : output.split("\\n")) {
1464 // Setting's value could be empty
1465 String[] pair = line.trim().split("=", -1);
1466 if (pair.length > 1) {
1467 map.putIfAbsent(pair[0], pair[1]);
1468 } else {
1469 CLog.e("Unable to get setting from string: %s", line);
1470 }
1471 }
1472 return map;
1473 }
1474 CLog.e("Namespace requested: '%s' is not part of {system, secure, global}", namespace);
1475 return null;
1476 }
1477
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001478 /**
1479 * {@inheritDoc}
1480 */
1481 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00001482 public void setSetting(String namespace, String key, String value)
1483 throws DeviceNotAvailableException {
Julien Desprez70d8ebd2016-06-01 16:04:21 +01001484 setSettingInternal("", namespace.trim(), key.trim(), value.trim());
Julien Despreza3d9c0f2016-03-21 12:18:22 +00001485 }
1486
1487 /**
1488 * {@inheritDoc}
1489 */
1490 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001491 public void setSetting(int userId, String namespace, String key, String value)
1492 throws DeviceNotAvailableException {
Julien Desprez70d8ebd2016-06-01 16:04:21 +01001493 setSettingInternal(String.format("--user %d", userId), namespace.trim(), key.trim(),
1494 value.trim());
Julien Despreza3d9c0f2016-03-21 12:18:22 +00001495 }
1496
1497 /**
1498 * Internal helper to set a setting with or without a userId provided.
1499 */
1500 private void setSettingInternal(String userFlag, String namespace, String key, String value)
1501 throws DeviceNotAvailableException {
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001502 checkApiLevelAgainst("Changing settings", 22);
1503 if (Arrays.asList(SETTINGS_NAMESPACE).contains(namespace.toLowerCase())) {
Julien Despreza3d9c0f2016-03-21 12:18:22 +00001504 executeShellCommand(String.format("settings %s put %s %s %s",
1505 userFlag, namespace, key, value));
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001506 } else {
1507 throw new IllegalArgumentException("Namespace must be one of system, secure, global."
1508 + " You provided: " + namespace);
1509 }
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001510 }
1511
1512 /**
1513 * {@inheritDoc}
1514 */
1515 @Override
1516 public String getAndroidId(int userId) throws DeviceNotAvailableException {
1517 if (isAdbRoot()) {
1518 String cmd = String.format(
1519 "sqlite3 /data/user/%d/com.google.android.gsf/databases/gservices.db "
1520 + "'select value from main where name = \"android_id\"'", userId);
1521 String output = executeShellCommand(cmd).trim();
1522 if (!output.contains("unable to open database")) {
1523 return output;
1524 }
1525 CLog.w("Couldn't find android-id, output: %s", output);
1526 } else {
1527 CLog.w("adb root is required.");
1528 }
1529 return null;
1530 }
1531
1532 /**
1533 * {@inheritDoc}
1534 */
1535 @Override
1536 public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException {
1537 ArrayList<Integer> userIds = listUsers();
1538 if (userIds == null) {
1539 return null;
1540 }
1541 Map<Integer, String> androidIds = new HashMap<Integer, String>();
1542 for (Integer id : userIds) {
1543 String androidId = getAndroidId(id);
1544 androidIds.put(id, androidId);
1545 }
1546 return androidIds;
1547 }
1548
1549 /**
1550 * {@inheritDoc}
1551 */
1552 @Override
Julien Desprezc8474552016-02-17 10:59:27 +00001553 IWifiHelper createWifiHelper() throws DeviceNotAvailableException {
Julien Desprezb1301842018-04-16 10:12:52 -07001554 mWasWifiHelperInstalled = true;
Avinankumar Vellore Suriyakumarc321ecd2017-08-08 16:44:40 -07001555 return new WifiHelper(this, mOptions.getWifiUtilAPKPath());
Julien Desprezc8474552016-02-17 10:59:27 +00001556 }
Julien Desprez4c8aabc2016-03-14 15:53:48 +00001557
Julien Desprezb1301842018-04-16 10:12:52 -07001558 /**
1559 * Alternative to {@link #createWifiHelper()} where we can choose whether to do the wifi helper
1560 * setup or not.
1561 */
1562 @VisibleForTesting
1563 IWifiHelper createWifiHelper(boolean doSetup) throws DeviceNotAvailableException {
1564 if (doSetup) {
1565 mWasWifiHelperInstalled = true;
1566 }
1567 return new WifiHelper(this, mOptions.getWifiUtilAPKPath(), doSetup);
1568 }
1569
1570 /** {@inheritDoc} */
1571 @Override
1572 public void postInvocationTearDown() {
1573 super.postInvocationTearDown();
1574 // If wifi was installed and it's a real device, attempt to clean it.
1575 if (mWasWifiHelperInstalled) {
1576 mWasWifiHelperInstalled = false;
1577 if (getIDevice() instanceof StubDevice) {
1578 return;
1579 }
1580 if (!TestDeviceState.ONLINE.equals(getDeviceState())) {
1581 return;
1582 }
1583 try {
1584 // Uninstall the wifi utility if it was installed.
1585 IWifiHelper wifi = createWifiHelper(false);
1586 wifi.cleanUp();
1587 } catch (DeviceNotAvailableException e) {
1588 CLog.e("Device became unavailable while uninstalling wifi util.");
1589 CLog.e(e);
1590 }
1591 }
1592 }
1593
Tony Mak14e4dee2017-04-12 14:37:34 +01001594 /** {@inheritDoc} */
1595 @Override
1596 public boolean setDeviceOwner(String componentName, int userId)
1597 throws DeviceNotAvailableException {
1598 final String command = "dpm set-device-owner --user " + userId + " '" + componentName + "'";
1599 final String commandOutput = executeShellCommand(command);
1600 return commandOutput.startsWith("Success:");
1601 }
1602
1603 /** {@inheritDoc} */
1604 @Override
1605 public boolean removeAdmin(String componentName, int userId)
1606 throws DeviceNotAvailableException {
1607 final String command =
1608 "dpm remove-active-admin --user " + userId + " '" + componentName + "'";
1609 final String commandOutput = executeShellCommand(command);
1610 return commandOutput.startsWith("Success:");
1611 }
1612
1613 /** {@inheritDoc} */
1614 @Override
1615 public void removeOwners() throws DeviceNotAvailableException {
1616 String command = "dumpsys device_policy";
1617 String commandOutput = executeShellCommand(command);
1618 String[] lines = commandOutput.split("\\r?\\n");
1619 for (int i = 0; i < lines.length; ++i) {
1620 String line = lines[i].trim();
1621 if (line.contains("Profile Owner")) {
1622 // Line is "Profile owner (User <id>):
1623 String[] tokens = line.split("\\(|\\)| ");
1624 int userId = Integer.parseInt(tokens[4]);
Jonathan Scott8c033db2018-12-13 17:08:30 +00001625
1626 i = moveToNextIndexMatchingRegex(".*admin=.*", lines, i);
Tony Mak14e4dee2017-04-12 14:37:34 +01001627 line = lines[i].trim();
1628 // Line is admin=ComponentInfo{<component>}
1629 tokens = line.split("\\{|\\}");
1630 String componentName = tokens[1];
1631 CLog.d("Cleaning up profile owner " + userId + " " + componentName);
1632 removeAdmin(componentName, userId);
1633 } else if (line.contains("Device Owner:")) {
Jonathan Scott8c033db2018-12-13 17:08:30 +00001634 i = moveToNextIndexMatchingRegex(".*admin=.*", lines, i);
Tony Mak14e4dee2017-04-12 14:37:34 +01001635 line = lines[i].trim();
1636 // Line is admin=ComponentInfo{<component>}
1637 String[] tokens = line.split("\\{|\\}");
1638 String componentName = tokens[1];
Jonathan Scott8c033db2018-12-13 17:08:30 +00001639
Tony Mak14e4dee2017-04-12 14:37:34 +01001640 // Skip to user id line.
Jonathan Scott8c033db2018-12-13 17:08:30 +00001641 i = moveToNextIndexMatchingRegex(".*User ID:.*", lines, i);
Tony Mak14e4dee2017-04-12 14:37:34 +01001642 line = lines[i].trim();
1643 // Line is User ID: <N>
1644 tokens = line.split(":");
1645 int userId = Integer.parseInt(tokens[1].trim());
1646 CLog.d("Cleaning up device owner " + userId + " " + componentName);
1647 removeAdmin(componentName, userId);
1648 }
1649 }
1650 }
1651
Julien Desprez3faaefd2016-05-24 19:25:12 +01001652 /**
Jonathan Scott8c033db2018-12-13 17:08:30 +00001653 * Search forward from the current index to find a string matching the given regex.
1654 *
1655 * @param regex The regex to match each line against.
1656 * @param lines An array of strings to be searched.
1657 * @param currentIndex the index to start searching from.
1658 * @return The index of a string beginning with the regex.
1659 * @throws IllegalStateException if the line cannot be found.
1660 */
1661 private int moveToNextIndexMatchingRegex(String regex, String[] lines, int currentIndex) {
1662 while (currentIndex < lines.length && !lines[currentIndex].matches(regex)) {
1663 currentIndex++;
1664 }
1665
1666 if (currentIndex >= lines.length) {
1667 throw new IllegalStateException(
1668 "The output of 'dumpsys device_policy' was not as expected. Owners have not "
1669 + "been removed. This will leave the device in an unstable state and "
1670 + "will lead to further test failures.");
1671 }
1672
1673 return currentIndex;
1674 }
1675
1676 /**
Julien Desprez3faaefd2016-05-24 19:25:12 +01001677 * Helper for Api level checking of features in the new release before we incremented the api
1678 * number.
1679 */
1680 private void checkApiLevelAgainstNextRelease(String feature, int strictMinLevel)
1681 throws DeviceNotAvailableException {
Julien Desprez3c4fb012019-04-17 09:12:30 -07001682 if (checkApiLevelAgainstNextRelease(strictMinLevel)) {
1683 return;
Julien Desprez3faaefd2016-05-24 19:25:12 +01001684 }
Julien Desprez3c4fb012019-04-17 09:12:30 -07001685 throw new IllegalArgumentException(
1686 String.format(
1687 "%s not supported on %s. Must be API %d.",
1688 feature, getSerialNumber(), strictMinLevel));
Julien Desprez3faaefd2016-05-24 19:25:12 +01001689 }
jdesprezd7670322017-08-30 14:12:28 -07001690
1691 @Override
1692 public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException {
1693 if (Strings.isNullOrEmpty(devicePath) || Strings.isNullOrEmpty(process)) {
1694 throw new IllegalArgumentException("devicePath or process cannot be null or empty.");
1695 }
1696 String pid = getProcessPid(process);
1697 if (pid == null) {
1698 return null;
1699 }
1700 File dump = dumpAndPullHeap(pid, devicePath);
1701 // Clean the device.
Julien Desprezcb5ffce2019-04-12 09:12:10 -07001702 deleteFile(devicePath);
jdesprezd7670322017-08-30 14:12:28 -07001703 return dump;
1704 }
1705
1706 /** Dump the heap file and pull it from the device. */
1707 private File dumpAndPullHeap(String pid, String devicePath) throws DeviceNotAvailableException {
1708 executeShellCommand(String.format(DUMPHEAP_CMD, pid, devicePath));
1709 // Allow a little bit of time for the file to populate on device side.
1710 int attempt = 0;
1711 // TODO: add an API to check device file size
1712 while (!doesFileExist(devicePath) && attempt < 3) {
1713 getRunUtil().sleep(DUMPHEAP_TIME);
1714 attempt++;
1715 }
1716 File dumpFile = pullFile(devicePath);
1717 return dumpFile;
1718 }
Julien Desprez467a41c2019-03-26 09:04:40 -07001719
1720 /** {@inheritDoc} */
1721 @Override
1722 public Set<Integer> listDisplayIds() throws DeviceNotAvailableException {
1723 Set<Integer> displays = new HashSet<>();
1724 // Zero is the default display
1725 displays.add(0);
1726 CommandResult res = executeShellV2Command("dumpsys SurfaceFlinger | grep 'color modes:'");
1727 if (!CommandStatus.SUCCESS.equals(res.getStatus())) {
1728 CLog.e("Something went wrong while listing displays: %s", res.getStderr());
1729 return displays;
1730 }
1731 String output = res.getStdout();
1732 Pattern p = Pattern.compile(DISPLAY_ID_PATTERN);
1733 for (String line : output.split("\n")) {
1734 Matcher m = p.matcher(line);
1735 if (m.matches()) {
1736 displays.add(Integer.parseInt(m.group("id")));
1737 }
1738 }
1739 return displays;
1740 }
Brett Chabot74121d82010-01-28 20:14:27 -08001741}