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
5import logging
6import pipes
7import sys
8
9from devil.android import device_errors  # pylint: disable=import-error
10
11
12def _QuoteIfNeeded(arg):
13  # Properly escape "key=valueA valueB" to "key='valueA valueB'"
14  # Values without spaces, or that seem to be quoted are left untouched.
15  # This is required so CommandLine.java can parse valueB correctly rather
16  # than as a separate switch.
17  params = arg.split('=', 1)
18  if len(params) != 2:
19    return arg
20  key, values = params
21  if ' ' not in values:
22    return arg
23  if values[0] in '"\'' and values[-1] == values[0]:
24    return arg
25  return '%s=%s' % (key, pipes.quote(values))
26
27
28class SetUpCommandLineFlags(object):
29  """A context manager for setting up the android command line flags.
30
31  This provides a readable way of using the android command line backend class.
32  Example usage:
33
34      with android_command_line_backend.SetUpCommandLineFlags(
35          device, backend_settings, startup_args):
36        # Something to run while the command line flags are set appropriately.
37  """
38  def __init__(self, device, backend_settings, startup_args):
39    self._android_command_line_backend = _AndroidCommandLineBackend(
40        device, backend_settings, startup_args)
41
42  def __enter__(self):
43    self._android_command_line_backend.SetUpCommandLineFlags()
44
45  def __exit__(self, *args):
46    self._android_command_line_backend.RestoreCommandLineFlags()
47
48
49class _AndroidCommandLineBackend(object):
50  """The backend for providing command line flags on android.
51
52  There are command line flags that Chromium accept in order to enable
53  particular features or modify otherwise default functionality. To set the
54  flags for Chrome on Android, specific files on the device must be updated
55  with the flags to enable. This class provides a wrapper around this
56  functionality.
57  """
58
59  def __init__(self, device, backend_settings, startup_args):
60    self._device = device
61    self._backend_settings = backend_settings
62    self._startup_args = startup_args
63    self._saved_command_line_file_contents = None
64
65  @property
66  def command_line_file(self):
67    return self._backend_settings.GetCommandLineFile(self._device.IsUserBuild())
68
69  def SetUpCommandLineFlags(self):
70    args = [self._backend_settings.pseudo_exec_name]
71    args.extend(self._startup_args)
72    content = ' '.join(_QuoteIfNeeded(arg) for arg in args)
73
74    try:
75      # Save the current command line to restore later, except if it appears to
76      # be a  Telemetry created one. This is to prevent a common bug where
77      # --host-resolver-rules borks people's browsers if something goes wrong
78      # with Telemetry.
79      self._saved_command_line_file_contents = self._ReadFile()
80      if (self._saved_command_line_file_contents and
81          '--host-resolver-rules' in self._saved_command_line_file_contents):
82        self._saved_command_line_file_contents = None
83    except device_errors.CommandFailedError:
84      self._saved_command_line_file_contents = None
85
86    try:
87      self._WriteFile(content)
88    except device_errors.CommandFailedError as exc:
89      logging.critical(exc)
90      logging.critical('Cannot set Chrome command line. '
91                       'Fix this by flashing to a userdebug build.')
92      sys.exit(1)
93
94  def RestoreCommandLineFlags(self):
95    if self._saved_command_line_file_contents is None:
96      self._RemoveFile()
97    else:
98      self._WriteFile(self._saved_command_line_file_contents)
99
100  def _ReadFile(self):
101    if self._device.PathExists(self.command_line_file):
102      return self._device.ReadFile(self.command_line_file, as_root=True)
103    else:
104      return None
105
106  def _WriteFile(self, contents):
107    self._device.WriteFile(self.command_line_file, contents, as_root=True)
108
109  def _RemoveFile(self):
110    self._device.RunShellCommand(['rm', '-f', self.command_line_file],
111                                 as_root=True, check_return=True)
112