adb_commands.py revision f2477e01787aa58f445919b809d89e252beef54f
1# Copyright 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Brings in Chrome Android's android_commands module, which itself is a
5thin(ish) wrapper around adb."""
6
7import logging
8import os
9import shutil
10import stat
11import sys
12
13from telemetry.core import util
14from telemetry.core.platform.profiler import android_prebuilt_profiler_helper
15
16# This is currently a thin wrapper around Chrome Android's
17# build scripts, located in chrome/build/android. This file exists mainly to
18# deal with locating the module.
19
20util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android')
21try:
22  from pylib import android_commands  # pylint: disable=F0401
23  from pylib import constants  # pylint: disable=F0401
24  from pylib import forwarder  # pylint: disable=F0401
25  from pylib import ports  # pylint: disable=F0401
26  from pylib.utils import apk_helper # #pylint: disable=F0401
27except Exception:
28  android_commands = None
29
30
31def IsAndroidSupported():
32  return android_commands != None
33
34
35def GetAttachedDevices():
36  """Returns a list of attached, online android devices.
37
38  If a preferred device has been set with ANDROID_SERIAL, it will be first in
39  the returned list."""
40  return android_commands.GetAttachedDevices()
41
42
43def AllocateTestServerPort():
44  return ports.AllocateTestServerPort()
45
46
47def ResetTestServerPortAllocation():
48  return ports.ResetTestServerPortAllocation()
49
50
51class AdbCommands(object):
52  """A thin wrapper around ADB"""
53
54  def __init__(self, device):
55    self._adb = android_commands.AndroidCommands(device)
56    self._device = device
57
58  def device(self):
59    return self._device
60
61  def Adb(self):
62    return self._adb
63
64  def Forward(self, local, remote):
65    ret = self._adb.Adb().SendCommand('forward %s %s' % (local, remote))
66    assert ret == ''
67
68  def RunShellCommand(self, command, timeout_time=20, log_result=False):
69    """Send a command to the adb shell and return the result.
70
71    Args:
72      command: String containing the shell command to send. Must not include
73               the single quotes as we use them to escape the whole command.
74      timeout_time: Number of seconds to wait for command to respond before
75        retrying, used by AdbInterface.SendShellCommand.
76      log_result: Boolean to indicate whether we should log the result of the
77                  shell command.
78
79    Returns:
80      list containing the lines of output received from running the command
81    """
82    return self._adb.RunShellCommand(command, timeout_time, log_result)
83
84  def CloseApplication(self, package):
85    """Attempt to close down the application, using increasing violence.
86
87    Args:
88      package: Name of the process to kill off, e.g.
89      com.google.android.apps.chrome
90    """
91    self._adb.CloseApplication(package)
92
93  def KillAll(self, process):
94    """Android version of killall, connected via adb.
95
96    Args:
97      process: name of the process to kill off
98
99    Returns:
100      the number of processess killed
101    """
102    return self._adb.KillAll(process)
103
104  def ExtractPid(self, process_name):
105    """Extracts Process Ids for a given process name from Android Shell.
106
107    Args:
108      process_name: name of the process on the device.
109
110    Returns:
111      List of all the process ids (as strings) that match the given name.
112      If the name of a process exactly matches the given name, the pid of
113      that process will be inserted to the front of the pid list.
114    """
115    return self._adb.ExtractPid(process_name)
116
117  def Install(self, apk_path):
118    """Installs specified package if necessary.
119
120    Args:
121      apk_path: Path to .apk file to install.
122    """
123
124    if (os.path.exists(os.path.join(
125        constants.GetOutDirectory('Release'), 'md5sum_bin_host'))):
126      constants.SetBuildType('Release')
127    elif (os.path.exists(os.path.join(
128        constants.GetOutDirectory('Debug'), 'md5sum_bin_host'))):
129      constants.SetBuildType('Debug')
130
131    apk_package_name = apk_helper.GetPackageName(apk_path)
132    return self._adb.ManagedInstall(apk_path, package_name=apk_package_name)
133
134  def StartActivity(self, package, activity, wait_for_completion=False,
135                    action='android.intent.action.VIEW',
136                    category=None, data=None,
137                    extras=None, trace_file_name=None,
138                    flags=None):
139    """Starts |package|'s activity on the device.
140
141    Args:
142      package: Name of package to start (e.g. 'com.google.android.apps.chrome').
143      activity: Name of activity (e.g. '.Main' or
144        'com.google.android.apps.chrome.Main').
145      wait_for_completion: wait for the activity to finish launching (-W flag).
146      action: string (e.g. 'android.intent.action.MAIN'). Default is VIEW.
147      category: string (e.g. 'android.intent.category.HOME')
148      data: Data string to pass to activity (e.g. 'http://www.example.com/').
149      extras: Dict of extras to pass to activity. Values are significant.
150      trace_file_name: If used, turns on and saves the trace to this file name.
151    """
152    return self._adb.StartActivity(package, activity, wait_for_completion,
153                    action,
154                    category, data,
155                    extras, trace_file_name,
156                    flags)
157
158  def Push(self, local, remote):
159    return self._adb.Adb().Push(local, remote)
160
161  def Pull(self, remote, local):
162    return self._adb.Adb().Pull(remote, local)
163
164  def FileExistsOnDevice(self, file_name):
165    return self._adb.FileExistsOnDevice(file_name)
166
167  def IsRootEnabled(self):
168    return self._adb.IsRootEnabled()
169
170  def GoHome(self):
171    return self._adb.GoHome()
172
173  def RestartAdbdOnDevice(self):
174    return self._adb.RestartAdbdOnDevice()
175
176
177def GetBuildTypeOfPath(path):
178  if not path:
179    return None
180  for build_dir, build_type in util.GetBuildDirectories():
181    if os.path.join(build_dir, build_type) in path:
182      return build_type
183  return None
184
185
186def SetupPrebuiltTools(adb):
187  # TODO(bulach): build the host tools for mac, and the targets for x86/mips.
188  # Prebuilt tools from r226197.
189  has_prebuilt = sys.platform.startswith('linux')
190  if has_prebuilt:
191    abi = adb.RunShellCommand('getprop ro.product.cpu.abi')
192    has_prebuilt = abi and abi[0].startswith('armeabi')
193  if not has_prebuilt:
194    logging.error(
195        'Prebuilt android tools only available for Linux host and ARM device.')
196    return False
197
198  prebuilt_tools = [
199      'forwarder_dist/device_forwarder',
200      'host_forwarder',
201      'md5sum_dist/md5sum_bin',
202      'md5sum_bin_host',
203      'purge_ashmem',
204  ]
205  build_type = None
206  for t in prebuilt_tools:
207    src = os.path.basename(t)
208    android_prebuilt_profiler_helper.GetIfChanged(src)
209    bin_path = util.FindSupportBinary(t)
210    if not build_type:
211      build_type = GetBuildTypeOfPath(bin_path) or 'Release'
212      constants.SetBuildType(build_type)
213    dest = os.path.join(constants.GetOutDirectory(), t)
214    if not bin_path:
215      logging.warning('Setting up prebuilt %s', dest)
216      if not os.path.exists(os.path.dirname(dest)):
217        os.makedirs(os.path.dirname(dest))
218      prebuilt_path = android_prebuilt_profiler_helper.GetHostPath(src)
219      if not os.path.exists(prebuilt_path):
220        raise NotImplementedError("""
221%s must be checked into cloud storage.
222Instructions:
223http://www.chromium.org/developers/telemetry/upload_to_cloud_storage
224""" % t)
225      shutil.copyfile(prebuilt_path, dest)
226      os.chmod(dest, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
227  return True
228
229
230class Forwarder(object):
231  def __init__(self, adb, *port_pairs):
232    self._adb = adb.Adb()
233    self._host_port = port_pairs[0].local_port
234
235    new_port_pairs = [(port_pair.local_port, port_pair.remote_port)
236                      for port_pair in port_pairs]
237
238    self._port_pairs = new_port_pairs
239    forwarder.Forwarder.Map(new_port_pairs, self._adb)
240
241  @property
242  def url(self):
243    return 'http://127.0.0.1:%i' % self._host_port
244
245  def Close(self):
246    for (device_port, _) in self._port_pairs:
247      forwarder.Forwarder.UnmapDevicePort(device_port, self._adb)
248