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