1#!/usr/bin/python2.4
2#
3#
4# Copyright 2008, The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18"""Provides an interface to communicate with the device via the adb command.
19
20Assumes adb binary is currently on system path.
21"""
22# Python imports
23import os
24import string
25import time
26
27# local imports
28import am_instrument_parser
29import errors
30import logger
31import run_command
32
33
34class AdbInterface:
35  """Helper class for communicating with Android device via adb."""
36
37  # argument to pass to adb, to direct command to specific device
38  _target_arg = ""
39
40  DEVICE_TRACE_DIR = "/data/test_results/"
41
42  def SetEmulatorTarget(self):
43    """Direct all future commands to the only running emulator."""
44    self._target_arg = "-e"
45
46  def SetDeviceTarget(self):
47    """Direct all future commands to the only connected USB device."""
48    self._target_arg = "-d"
49
50  def SetTargetSerial(self, serial):
51    """Direct all future commands to Android target with the given serial."""
52    self._target_arg = "-s %s" % serial
53
54  def SendCommand(self, command_string, timeout_time=60, retry_count=3):
55    """Send a command via adb.
56
57    Args:
58      command_string: adb command to run
59      timeout_time: number of seconds to wait for command to respond before
60        retrying
61      retry_count: number of times to retry command before raising
62        WaitForResponseTimedOutError
63    Returns:
64      string output of command
65
66    Raises:
67      WaitForResponseTimedOutError if device does not respond to command within time
68    """
69    adb_cmd = "adb %s %s" % (self._target_arg, command_string)
70    logger.SilentLog("about to run %s" % adb_cmd)
71    return run_command.RunCommand(adb_cmd, timeout_time=timeout_time,
72                                  retry_count=retry_count)
73
74  def SendShellCommand(self, cmd, timeout_time=20, retry_count=3):
75    """Send a adb shell command.
76
77    Args:
78      cmd: adb shell command to run
79      timeout_time: number of seconds to wait for command to respond before
80        retrying
81      retry_count: number of times to retry command before raising
82        WaitForResponseTimedOutError
83
84    Returns:
85      string output of command
86
87    Raises:
88      WaitForResponseTimedOutError: if device does not respond to command
89    """
90    return self.SendCommand("shell %s" % cmd, timeout_time=timeout_time,
91                            retry_count=retry_count)
92
93  def BugReport(self, path):
94    """Dumps adb bugreport to the file specified by the path.
95
96    Args:
97      path: Path of the file where adb bugreport is dumped to.
98    """
99    bug_output = self.SendShellCommand("bugreport", timeout_time=60)
100    bugreport_file = open(path, "w")
101    bugreport_file.write(bug_output)
102    bugreport_file.close()
103
104  def Push(self, src, dest):
105    """Pushes the file src onto the device at dest.
106
107    Args:
108      src: file path of host file to push
109      dest: destination absolute file path on device
110    """
111    self.SendCommand("push %s %s" % (src, dest), timeout_time=60)
112
113  def Pull(self, src, dest):
114    """Pulls the file src on the device onto dest on the host.
115
116    Args:
117      src: absolute file path of file on device to pull
118      dest: destination file path on host
119
120    Returns:
121      True if success and False otherwise.
122    """
123    # Create the base dir if it doesn't exist already
124    if not os.path.exists(os.path.dirname(dest)):
125      os.makedirs(os.path.dirname(dest))
126
127    if self.DoesFileExist(src):
128      self.SendCommand("pull %s %s" % (src, dest), timeout_time=60)
129      return True
130    else:
131      logger.Log("ADB Pull Failed: Source file %s does not exist." % src)
132      return False
133
134  def Install(self, apk_path, extra_flags):
135    """Installs apk on device.
136
137    Args:
138      apk_path: file path to apk file on host
139      extra_flags: Additional flags to use with adb install
140
141    Returns:
142      output of install command
143    """
144    return self.SendCommand("install -r %s %s" % (extra_flags, apk_path))
145
146  def DoesFileExist(self, src):
147    """Checks if the given path exists on device target.
148
149    Args:
150      src: file path to be checked.
151
152    Returns:
153      True if file exists
154    """
155
156    output = self.SendShellCommand("ls %s" % src)
157    error = "No such file or directory"
158
159    if error in output:
160      return False
161    return True
162
163  def EnableAdbRoot(self):
164    """Enable adb root on device."""
165    output = self.SendCommand("root")
166    if "adbd is already running as root" in output:
167      return True
168    elif "restarting adbd as root" in output:
169      # device will disappear from adb, wait for it to come back
170      time.sleep(2)
171      self.SendCommand("wait-for-device")
172      return True
173    else:
174      logger.Log("Unrecognized output from adb root: %s" % output)
175      return False
176
177  def StartInstrumentationForPackage(
178      self, package_name, runner_name, timeout_time=60*10,
179      no_window_animation=False, instrumentation_args={}, user=None,
180      no_hidden_api_checks=False):
181    """Run instrumentation test for given package and runner.
182
183    Equivalent to StartInstrumentation, except instrumentation path is
184    separated into its package and runner components.
185    """
186    instrumentation_path = "%s/%s" % (package_name, runner_name)
187    return self.StartInstrumentation(instrumentation_path, timeout_time=timeout_time,
188                                     no_window_animation=no_window_animation,
189                                     instrumentation_args=instrumentation_args,
190                                     user=user,
191                                     no_hidden_api_checks=no_hidden_api_checks)
192
193  def StartInstrumentation(
194      self, instrumentation_path, timeout_time=60*10, no_window_animation=False,
195      profile=False, instrumentation_args={}, user=None,
196      no_hidden_api_checks=False):
197
198    """Runs an instrumentation class on the target.
199
200    Returns a dictionary containing the key value pairs from the
201    instrumentations result bundle and a list of TestResults. Also handles the
202    interpreting of error output from the device and raises the necessary
203    exceptions.
204
205    Args:
206      instrumentation_path: string. It should be the fully classified package
207      name, and instrumentation test runner, separated by "/"
208        e.g. com.android.globaltimelaunch/.GlobalTimeLaunch
209      timeout_time: Timeout value for the am command.
210      no_window_animation: boolean, Whether you want window animations enabled
211        or disabled
212      profile: If True, profiling will be turned on for the instrumentation.
213      instrumentation_args: Dictionary of key value bundle arguments to pass to
214      instrumentation.
215      user: The user id to start the instrumentation with.
216
217    Returns:
218      (test_results, inst_finished_bundle)
219
220      test_results: a list of TestResults
221      inst_finished_bundle (dict): Key/value pairs contained in the bundle that
222        is passed into ActivityManager.finishInstrumentation(). Included in this
223        bundle is the return code of the Instrumentation process, any error
224        codes reported by the activity manager, and any results explicitly added
225        by the instrumentation code.
226
227     Raises:
228       WaitForResponseTimedOutError: if timeout occurred while waiting for
229         response to adb instrument command
230       DeviceUnresponsiveError: if device system process is not responding
231       InstrumentationError: if instrumentation failed to run
232    """
233
234    command_string = self._BuildInstrumentationCommandPath(
235        instrumentation_path, no_window_animation=no_window_animation,
236        profile=profile, raw_mode=True,
237        instrumentation_args=instrumentation_args,
238        user=user, no_hidden_api_checks=no_hidden_api_checks)
239    logger.Log(command_string)
240    (test_results, inst_finished_bundle) = (
241        am_instrument_parser.ParseAmInstrumentOutput(
242            self.SendShellCommand(command_string, timeout_time=timeout_time,
243                                  retry_count=2)))
244
245    if "code" not in inst_finished_bundle:
246      raise errors.InstrumentationError("no test results... device setup "
247                                        "correctly?")
248
249    if inst_finished_bundle["code"] == "0":
250      short_msg_result = "no error message"
251      if "shortMsg" in inst_finished_bundle:
252        short_msg_result = inst_finished_bundle["shortMsg"]
253        logger.Log("Error! Test run failed: %s" % short_msg_result)
254      raise errors.InstrumentationError(short_msg_result)
255
256    if "INSTRUMENTATION_ABORTED" in inst_finished_bundle:
257      logger.Log("INSTRUMENTATION ABORTED!")
258      raise errors.DeviceUnresponsiveError
259
260    return (test_results, inst_finished_bundle)
261
262  def StartInstrumentationNoResults(
263      self, package_name, runner_name, no_window_animation=False,
264      raw_mode=False, instrumentation_args={}, user=None,
265      no_hidden_api_checks=False):
266    """Runs instrumentation and dumps output to stdout.
267
268    Equivalent to StartInstrumentation, but will dump instrumentation
269    'normal' output to stdout, instead of parsing return results. Command will
270    never timeout.
271    """
272    adb_command_string = self.PreviewInstrumentationCommand(
273        package_name, runner_name, no_window_animation=no_window_animation,
274        raw_mode=raw_mode, instrumentation_args=instrumentation_args,
275        user=user, no_hidden_api_checks=no_hidden_api_checks)
276    logger.Log(adb_command_string)
277    run_command.RunCommand(adb_command_string, return_output=False)
278
279  def PreviewInstrumentationCommand(
280      self, package_name, runner_name, no_window_animation=False,
281      raw_mode=False, instrumentation_args={}, user=None,
282      no_hidden_api_checks=False):
283    """Returns a string of adb command that will be executed."""
284    inst_command_string = self._BuildInstrumentationCommand(
285        package_name, runner_name, no_window_animation=no_window_animation,
286        raw_mode=raw_mode, instrumentation_args=instrumentation_args,
287        user=user, no_hidden_api_checks=no_hidden_api_checks)
288    return self.PreviewShellCommand(inst_command_string)
289
290  def PreviewShellCommand(self, cmd):
291    return "adb %s shell %s" % (self._target_arg, cmd)
292
293  def _BuildInstrumentationCommand(
294      self, package, runner_name, no_window_animation=False, profile=False,
295      raw_mode=True, instrumentation_args={}, user=None,
296      no_hidden_api_checks=False):
297    instrumentation_path = "%s/%s" % (package, runner_name)
298
299    return self._BuildInstrumentationCommandPath(
300        instrumentation_path, no_window_animation=no_window_animation,
301        profile=profile, raw_mode=raw_mode,
302        instrumentation_args=instrumentation_args, user=user,
303        no_hidden_api_checks=no_hidden_api_checks)
304
305  def _BuildInstrumentationCommandPath(
306      self, instrumentation_path, no_window_animation=False, profile=False,
307      raw_mode=True, instrumentation_args={}, user=None,
308      no_hidden_api_checks=False):
309    command_string = "am instrument"
310    if no_hidden_api_checks:
311      command_string += " --no-hidden-api-checks"
312    if user:
313      command_string += " --user %s" % user
314    if no_window_animation:
315      command_string += " --no_window_animation"
316    if profile:
317      self._CreateTraceDir()
318      command_string += (
319          " -p %s/%s.dmtrace" %
320          (self.DEVICE_TRACE_DIR, instrumentation_path.split(".")[-1]))
321
322    for key, value in instrumentation_args.items():
323      command_string += " -e %s '%s'" % (key, value)
324    if raw_mode:
325      command_string += " -r"
326    command_string += " -w '%s'" % instrumentation_path
327    return command_string
328
329  def _CreateTraceDir(self):
330    ls_response = self.SendShellCommand("ls /data/trace")
331    if ls_response.strip("#").strip(string.whitespace) != "":
332      self.SendShellCommand("create /data/trace", "mkdir /data/trace")
333      self.SendShellCommand("make /data/trace world writeable",
334                            "chmod 777 /data/trace")
335
336  def WaitForDevicePm(self, wait_time=120):
337    """Waits for targeted device's package manager to be up.
338
339    Args:
340      wait_time: time in seconds to wait
341
342    Raises:
343      WaitForResponseTimedOutError if wait_time elapses and pm still does not
344      respond.
345    """
346    logger.Log("Waiting for device package manager...")
347    self.SendCommand("wait-for-device")
348    # Now the device is there, but may not be running.
349    # Query the package manager with a basic command
350    try:
351      self._WaitForShellCommandContents("pm path android", "package:",
352                                        wait_time)
353    except errors.WaitForResponseTimedOutError:
354      raise errors.WaitForResponseTimedOutError(
355          "Package manager did not respond after %s seconds" % wait_time)
356
357  def IsInstrumentationInstalled(self, package_name, runner_name):
358    """Checks if instrumentation is present on device."""
359    instrumentation_path = "%s/%s" % (package_name, runner_name)
360    command = "pm list instrumentation | grep %s" % instrumentation_path
361    try:
362      output = self.SendShellCommand(command)
363      return output.startswith("instrumentation:")
364    except errors.AbortError:
365      # command can return error code on failure
366      return False
367
368  def WaitForProcess(self, name, wait_time=120):
369    """Wait until a process is running on the device.
370
371    Args:
372      name: the process name as it appears in `ps`
373      wait_time: time in seconds to wait
374
375    Raises:
376      WaitForResponseTimedOutError if wait_time elapses and the process is
377          still not running
378    """
379    logger.Log("Waiting for process %s" % name)
380    self.SendCommand("wait-for-device")
381    self._WaitForShellCommandContents("ps", name, wait_time)
382
383  def WaitForProcessEnd(self, name, wait_time=120):
384    """Wait until a process is no longer running on the device.
385
386    Args:
387      name: the process name as it appears in `ps`
388      wait_time: time in seconds to wait
389
390    Raises:
391      WaitForResponseTimedOutError if wait_time elapses and the process is
392          still running
393    """
394    logger.Log("Waiting for process %s to end" % name)
395    self._WaitForShellCommandContents("ps", name, wait_time, invert=True)
396
397  def _WaitForShellCommandContents(self, command, expected, wait_time,
398                                   raise_abort=True, invert=False):
399    """Wait until the response to a command contains a given output.
400
401    Assumes that a only successful execution of "adb shell <command>" contains
402    the substring expected. Assumes that a device is present.
403
404    Args:
405      command: adb shell command to execute
406      expected: the string that should appear to consider the
407          command successful.
408      wait_time: time in seconds to wait
409      raise_abort: if False, retry when executing the command raises an
410          AbortError, rather than failing.
411      invert: if True, wait until the command output no longer contains the
412          expected contents.
413
414    Raises:
415      WaitForResponseTimedOutError: If wait_time elapses and the command has not
416          returned an output containing expected yet.
417    """
418    # Query the device with the command
419    success = False
420    attempts = 0
421    wait_period = 5
422    while not success and (attempts*wait_period) < wait_time:
423      # assume the command will always contain expected in the success case
424      try:
425        output = self.SendShellCommand(command, retry_count=1)
426        if ((not invert and expected in output)
427            or (invert and expected not in output)):
428          success = True
429      except errors.AbortError, e:
430        if raise_abort:
431          raise
432        # ignore otherwise
433
434      if not success:
435        time.sleep(wait_period)
436        attempts += 1
437
438    if not success:
439      raise errors.WaitForResponseTimedOutError()
440
441  def WaitForBootComplete(self, wait_time=120):
442    """Waits for targeted device's bootcomplete flag to be set.
443
444    Args:
445      wait_time: time in seconds to wait
446
447    Raises:
448      WaitForResponseTimedOutError if wait_time elapses and pm still does not
449      respond.
450    """
451    logger.Log("Waiting for boot complete...")
452    self.SendCommand("wait-for-device")
453    # Now the device is there, but may not be running.
454    # Query the package manager with a basic command
455    boot_complete = False
456    attempts = 0
457    wait_period = 5
458    while not boot_complete and (attempts*wait_period) < wait_time:
459      output = self.SendShellCommand("getprop dev.bootcomplete", retry_count=1)
460      output = output.strip()
461      if output == "1":
462        boot_complete = True
463      else:
464        time.sleep(wait_period)
465        attempts += 1
466    if not boot_complete:
467      raise errors.WaitForResponseTimedOutError(
468          "dev.bootcomplete flag was not set after %s seconds" % wait_time)
469
470  def Sync(self, retry_count=3, runtime_restart=False):
471    """Perform a adb sync.
472
473    Blocks until device package manager is responding.
474
475    Args:
476      retry_count: number of times to retry sync before failing
477      runtime_restart: stop runtime during sync and restart afterwards, useful
478        for syncing system libraries (core, framework etc)
479
480    Raises:
481      WaitForResponseTimedOutError if package manager does not respond
482      AbortError if unrecoverable error occurred
483    """
484    output = ""
485    error = None
486    if runtime_restart:
487      self.SendShellCommand("setprop ro.test_harness 1", retry_count=retry_count)
488      # manual rest bootcomplete flag
489      self.SendShellCommand("setprop dev.bootcomplete 0",
490                            retry_count=retry_count)
491      self.SendShellCommand("stop", retry_count=retry_count)
492
493    try:
494      output = self.SendCommand("sync", retry_count=retry_count)
495    except errors.AbortError, e:
496      error = e
497      output = e.msg
498    if "Read-only file system" in output:
499      logger.SilentLog(output)
500      logger.Log("Remounting read-only filesystem")
501      self.SendCommand("remount")
502      output = self.SendCommand("sync", retry_count=retry_count)
503    elif "No space left on device" in output:
504      logger.SilentLog(output)
505      logger.Log("Restarting device runtime")
506      self.SendShellCommand("stop", retry_count=retry_count)
507      output = self.SendCommand("sync", retry_count=retry_count)
508      self.SendShellCommand("start", retry_count=retry_count)
509    elif error is not None:
510      # exception occurred that cannot be recovered from
511      raise error
512    logger.SilentLog(output)
513    if runtime_restart:
514      # start runtime and wait till boot complete flag is set
515      self.SendShellCommand("start", retry_count=retry_count)
516      self.WaitForBootComplete()
517      # press the MENU key, this will disable key guard if runtime is started
518      # with ro.monkey set to 1
519      self.SendShellCommand("input keyevent 82", retry_count=retry_count)
520    else:
521      self.WaitForDevicePm()
522    return output
523
524  def GetSerialNumber(self):
525    """Returns the serial number of the targeted device."""
526    return self.SendCommand("get-serialno").strip()
527
528  def RuntimeReset(self, disable_keyguard=False, retry_count=3, preview_only=False):
529    """
530    Resets the Android runtime (does *not* reboot the kernel).
531
532    Blocks until the reset is complete and the package manager
533    is available.
534
535    Args:
536      disable_keyguard: if True, presses the MENU key to disable
537        key guard, after reset is finished
538      retry_count: number of times to retry reset before failing
539
540    Raises:
541      WaitForResponseTimedOutError if package manager does not respond
542      AbortError if unrecoverable error occurred
543    """
544
545    logger.Log("adb shell stop")
546    logger.Log("adb shell start")
547
548    if not preview_only:
549      self.SendShellCommand("stop", retry_count=retry_count)
550      self.SendShellCommand("start", retry_count=retry_count)
551
552    self.WaitForDevicePm()
553
554    if disable_keyguard:
555      logger.Log("input keyevent 82 ## disable keyguard")
556      if not preview_only:
557        self.SendShellCommand("input keyevent 82", retry_count=retry_count)
558