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