adb_interface.py revision cdfaae1a3409d380fab8228545d1b0d28ca070f7
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=20, 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 DoesFileExist(self, src):
135    """Checks if the given path exists on device target.
136
137    Args:
138      src: file path to be checked.
139
140    Returns:
141      True if file exists
142    """
143
144    output = self.SendShellCommand("ls %s" % src)
145    error = "No such file or directory"
146
147    if error in output:
148      return False
149    return True
150
151  def EnableAdbRoot(self):
152    """Enable adb root on device."""
153    output = self.SendCommand("root")
154    if "adbd is already running as root" in output:
155      return True
156    elif "restarting adbd as root" in output:
157      # device will disappear from adb, wait for it to come back
158      time.sleep(2)
159      self.SendCommand("wait-for-device")
160      return True
161    else:
162      logger.Log("Unrecognized output from adb root: %s" % output)
163      return False
164
165  def StartInstrumentationForPackage(
166      self, package_name, runner_name, timeout_time=60*10,
167      no_window_animation=False, instrumentation_args={}):
168    """Run instrumentation test for given package and runner.
169
170    Equivalent to StartInstrumentation, except instrumentation path is
171    separated into its package and runner components.
172    """
173    instrumentation_path = "%s/%s" % (package_name, runner_name)
174    return self.StartInstrumentation(instrumentation_path, timeout_time=timeout_time,
175                                     no_window_animation=no_window_animation,
176                                     instrumentation_args=instrumentation_args)
177
178  def StartInstrumentation(
179      self, instrumentation_path, timeout_time=60*10, no_window_animation=False,
180      profile=False, instrumentation_args={}):
181
182    """Runs an instrumentation class on the target.
183
184    Returns a dictionary containing the key value pairs from the
185    instrumentations result bundle and a list of TestResults. Also handles the
186    interpreting of error output from the device and raises the necessary
187    exceptions.
188
189    Args:
190      instrumentation_path: string. It should be the fully classified package
191      name, and instrumentation test runner, separated by "/"
192        e.g. com.android.globaltimelaunch/.GlobalTimeLaunch
193      timeout_time: Timeout value for the am command.
194      no_window_animation: boolean, Whether you want window animations enabled
195        or disabled
196      profile: If True, profiling will be turned on for the instrumentation.
197      instrumentation_args: Dictionary of key value bundle arguments to pass to
198      instrumentation.
199
200    Returns:
201      (test_results, inst_finished_bundle)
202
203      test_results: a list of TestResults
204      inst_finished_bundle (dict): Key/value pairs contained in the bundle that
205        is passed into ActivityManager.finishInstrumentation(). Included in this
206        bundle is the return code of the Instrumentation process, any error
207        codes reported by the activity manager, and any results explicitly added
208        by the instrumentation code.
209
210     Raises:
211       WaitForResponseTimedOutError: if timeout occurred while waiting for
212         response to adb instrument command
213       DeviceUnresponsiveError: if device system process is not responding
214       InstrumentationError: if instrumentation failed to run
215    """
216
217    command_string = self._BuildInstrumentationCommandPath(
218        instrumentation_path, no_window_animation=no_window_animation,
219        profile=profile, raw_mode=True,
220        instrumentation_args=instrumentation_args)
221    logger.Log(command_string)
222    (test_results, inst_finished_bundle) = (
223        am_instrument_parser.ParseAmInstrumentOutput(
224            self.SendShellCommand(command_string, timeout_time=timeout_time,
225                                  retry_count=2)))
226
227    if "code" not in inst_finished_bundle:
228      raise errors.InstrumentationError("no test results... device setup "
229                                        "correctly?")
230
231    if inst_finished_bundle["code"] == "0":
232      short_msg_result = "no error message"
233      if "shortMsg" in inst_finished_bundle:
234        short_msg_result = inst_finished_bundle["shortMsg"]
235        logger.Log("Error! Test run failed: %s" % short_msg_result)
236      raise errors.InstrumentationError(short_msg_result)
237
238    if "INSTRUMENTATION_ABORTED" in inst_finished_bundle:
239      logger.Log("INSTRUMENTATION ABORTED!")
240      raise errors.DeviceUnresponsiveError
241
242    return (test_results, inst_finished_bundle)
243
244  def StartInstrumentationNoResults(
245      self, package_name, runner_name, no_window_animation=False,
246      raw_mode=False, instrumentation_args={}):
247    """Runs instrumentation and dumps output to stdout.
248
249    Equivalent to StartInstrumentation, but will dump instrumentation
250    'normal' output to stdout, instead of parsing return results. Command will
251    never timeout.
252    """
253    adb_command_string = self.PreviewInstrumentationCommand(
254        package_name, runner_name, no_window_animation=no_window_animation,
255        raw_mode=raw_mode, instrumentation_args=instrumentation_args)
256    logger.Log(adb_command_string)
257    run_command.RunCommand(adb_command_string, return_output=False)
258
259  def PreviewInstrumentationCommand(
260      self, package_name, runner_name, no_window_animation=False,
261      raw_mode=False, instrumentation_args={}):
262    """Returns a string of adb command that will be executed."""
263    inst_command_string = self._BuildInstrumentationCommand(
264        package_name, runner_name, no_window_animation=no_window_animation,
265        raw_mode=raw_mode, instrumentation_args=instrumentation_args)
266    command_string = "adb %s shell %s" % (self._target_arg, inst_command_string)
267    return command_string
268
269  def _BuildInstrumentationCommand(
270      self, package, runner_name, no_window_animation=False, profile=False,
271      raw_mode=True, instrumentation_args={}):
272    instrumentation_path = "%s/%s" % (package, runner_name)
273
274    return self._BuildInstrumentationCommandPath(
275        instrumentation_path, no_window_animation=no_window_animation,
276        profile=profile, raw_mode=raw_mode,
277        instrumentation_args=instrumentation_args)
278
279  def _BuildInstrumentationCommandPath(
280      self, instrumentation_path, no_window_animation=False, profile=False,
281      raw_mode=True, instrumentation_args={}):
282    command_string = "am instrument"
283    if no_window_animation:
284      command_string += " --no_window_animation"
285    if profile:
286      self._CreateTraceDir()
287      command_string += (
288          " -p %s/%s.dmtrace" %
289          (self.DEVICE_TRACE_DIR, instrumentation_path.split(".")[-1]))
290
291    for key, value in instrumentation_args.items():
292      command_string += " -e %s '%s'" % (key, value)
293    if raw_mode:
294      command_string += " -r"
295    command_string += " -w %s" % instrumentation_path
296    return command_string
297
298  def _CreateTraceDir(self):
299    ls_response = self.SendShellCommand("ls /data/trace")
300    if ls_response.strip("#").strip(string.whitespace) != "":
301      self.SendShellCommand("create /data/trace", "mkdir /data/trace")
302      self.SendShellCommand("make /data/trace world writeable",
303                            "chmod 777 /data/trace")
304
305  def WaitForDevicePm(self, wait_time=120):
306    """Waits for targeted device's package manager to be up.
307
308    Args:
309      wait_time: time in seconds to wait
310
311    Raises:
312      WaitForResponseTimedOutError if wait_time elapses and pm still does not
313      respond.
314    """
315    logger.Log("Waiting for device package manager...")
316    self.SendCommand("wait-for-device")
317    # Now the device is there, but may not be running.
318    # Query the package manager with a basic command
319    try:
320      self._WaitForShellCommandContents("pm path android", "package:",
321                                        wait_time)
322    except errors.WaitForResponseTimedOutError:
323      raise errors.WaitForResponseTimedOutError(
324          "Package manager did not respond after %s seconds" % wait_time)
325
326  def WaitForInstrumentation(self, package_name, runner_name, wait_time=120):
327    """Waits for given instrumentation to be present on device
328
329    Args:
330      wait_time: time in seconds to wait
331
332    Raises:
333      WaitForResponseTimedOutError if wait_time elapses and instrumentation
334      still not present.
335    """
336    instrumentation_path = "%s/%s" % (package_name, runner_name)
337    logger.Log("Waiting for instrumentation to be present")
338    # Query the package manager
339    try:
340      command = "pm list instrumentation | grep %s" % instrumentation_path
341      self._WaitForShellCommandContents(command, "instrumentation:", wait_time,
342                                        raise_abort=False)
343    except errors.WaitForResponseTimedOutError :
344      logger.Log(
345          "Could not find instrumentation %s on device. Does the "
346          "instrumentation in test's AndroidManifest.xml match definition"
347          "in test_defs.xml?" % instrumentation_path)
348      raise
349
350  def WaitForProcess(self, name, wait_time=120):
351    """Wait until a process is running on the device.
352
353    Args:
354      name: the process name as it appears in `ps`
355      wait_time: time in seconds to wait
356
357    Raises:
358      WaitForResponseTimedOutError if wait_time elapses and the process is
359          still not running
360    """
361    logger.Log("Waiting for process %s" % name)
362    self.SendCommand("wait-for-device")
363    self._WaitForShellCommandContents("ps", name, wait_time)
364
365  def WaitForProcessEnd(self, name, wait_time=120):
366    """Wait until a process is no longer running on the device.
367
368    Args:
369      name: the process name as it appears in `ps`
370      wait_time: time in seconds to wait
371
372    Raises:
373      WaitForResponseTimedOutError if wait_time elapses and the process is
374          still running
375    """
376    logger.Log("Waiting for process %s to end" % name)
377    self._WaitForShellCommandContents("ps", name, wait_time, invert=True)
378
379  def _WaitForShellCommandContents(self, command, expected, wait_time,
380                                   raise_abort=True, invert=False):
381    """Wait until the response to a command contains a given output.
382
383    Assumes that a only successful execution of "adb shell <command>" contains
384    the substring expected. Assumes that a device is present.
385
386    Args:
387      command: adb shell command to execute
388      expected: the string that should appear to consider the
389          command successful.
390      wait_time: time in seconds to wait
391      raise_abort: if False, retry when executing the command raises an
392          AbortError, rather than failing.
393      invert: if True, wait until the command output no longer contains the
394          expected contents.
395
396    Raises:
397      WaitForResponseTimedOutError: If wait_time elapses and the command has not
398          returned an output containing expected yet.
399    """
400    # Query the device with the command
401    success = False
402    attempts = 0
403    wait_period = 5
404    while not success and (attempts*wait_period) < wait_time:
405      # assume the command will always contain expected in the success case
406      try:
407        output = self.SendShellCommand(command, retry_count=1)
408        if ((not invert and expected in output)
409            or (invert and expected not in output)):
410          success = True
411      except errors.AbortError, e:
412        if raise_abort:
413          raise
414        # ignore otherwise
415
416      if not success:
417        time.sleep(wait_period)
418        attempts += 1
419
420    if not success:
421      raise errors.WaitForResponseTimedOutError()
422
423  def WaitForBootComplete(self, wait_time=120):
424    """Waits for targeted device's bootcomplete flag to be set.
425
426    Args:
427      wait_time: time in seconds to wait
428
429    Raises:
430      WaitForResponseTimedOutError if wait_time elapses and pm still does not
431      respond.
432    """
433    logger.Log("Waiting for boot complete...")
434    self.SendCommand("wait-for-device")
435    # Now the device is there, but may not be running.
436    # Query the package manager with a basic command
437    boot_complete = False
438    attempts = 0
439    wait_period = 5
440    while not boot_complete and (attempts*wait_period) < wait_time:
441      output = self.SendShellCommand("getprop dev.bootcomplete", retry_count=1)
442      output = output.strip()
443      if output == "1":
444        boot_complete = True
445      else:
446        time.sleep(wait_period)
447        attempts += 1
448    if not boot_complete:
449      raise errors.WaitForResponseTimedOutError(
450          "dev.bootcomplete flag was not set after %s seconds" % wait_time)
451
452  def Sync(self, retry_count=3, runtime_restart=False):
453    """Perform a adb sync.
454
455    Blocks until device package manager is responding.
456
457    Args:
458      retry_count: number of times to retry sync before failing
459      runtime_restart: stop runtime during sync and restart afterwards, useful
460        for syncing system libraries (core, framework etc)
461
462    Raises:
463      WaitForResponseTimedOutError if package manager does not respond
464      AbortError if unrecoverable error occurred
465    """
466    output = ""
467    error = None
468    if runtime_restart:
469      self.SendShellCommand("setprop ro.test_harness 1", retry_count=retry_count)
470      # manual rest bootcomplete flag
471      self.SendShellCommand("setprop dev.bootcomplete 0",
472                            retry_count=retry_count)
473      self.SendShellCommand("stop", retry_count=retry_count)
474
475    try:
476      output = self.SendCommand("sync", retry_count=retry_count)
477    except errors.AbortError, e:
478      error = e
479      output = e.msg
480    if "Read-only file system" in output:
481      logger.SilentLog(output)
482      logger.Log("Remounting read-only filesystem")
483      self.SendCommand("remount")
484      output = self.SendCommand("sync", retry_count=retry_count)
485    elif "No space left on device" in output:
486      logger.SilentLog(output)
487      logger.Log("Restarting device runtime")
488      self.SendShellCommand("stop", retry_count=retry_count)
489      output = self.SendCommand("sync", retry_count=retry_count)
490      self.SendShellCommand("start", retry_count=retry_count)
491    elif error is not None:
492      # exception occurred that cannot be recovered from
493      raise error
494    logger.SilentLog(output)
495    if runtime_restart:
496      # start runtime and wait till boot complete flag is set
497      self.SendShellCommand("start", retry_count=retry_count)
498      self.WaitForBootComplete()
499      # press the MENU key, this will disable key guard if runtime is started
500      # with ro.monkey set to 1
501      self.SendShellCommand("input keyevent 82", retry_count=retry_count)
502    else:
503      self.WaitForDevicePm()
504    return output
505
506  def GetSerialNumber(self):
507    """Returns the serial number of the targeted device."""
508    return self.SendCommand("get-serialno").strip()
509