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