| Index: build/android/devil/android/device_utils.py | 
| diff --git a/build/android/devil/android/device_utils.py b/build/android/devil/android/device_utils.py | 
| index c35bc264ef9e48da1cfbcb10aa4df75fbaf2c42f..8a70c3c9e539551c8171dcbd29784933f82a9533 100644 | 
| --- a/build/android/devil/android/device_utils.py | 
| +++ b/build/android/devil/android/device_utils.py | 
| @@ -53,30 +53,6 @@ _DEFAULT_RETRIES = 3 | 
| # the timeout_retry decorators. | 
| DEFAULT = object() | 
|  | 
| -_CONTROL_CHARGING_COMMANDS = [ | 
| -  { | 
| -    # Nexus 4 | 
| -    'witness_file': '/sys/module/pm8921_charger/parameters/disabled', | 
| -    'enable_command': 'echo 0 > /sys/module/pm8921_charger/parameters/disabled', | 
| -    'disable_command': | 
| -        'echo 1 > /sys/module/pm8921_charger/parameters/disabled', | 
| -  }, | 
| -  { | 
| -    # Nexus 5 | 
| -    # Setting the HIZ bit of the bq24192 causes the charger to actually ignore | 
| -    # energy coming from USB. Setting the power_supply offline just updates the | 
| -    # Android system to reflect that. | 
| -    'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT', | 
| -    'enable_command': ( | 
| -        'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' | 
| -        'echo 1 > /sys/class/power_supply/usb/online'), | 
| -    'disable_command': ( | 
| -        'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' | 
| -        'chmod 644 /sys/class/power_supply/usb/online && ' | 
| -        'echo 0 > /sys/class/power_supply/usb/online'), | 
| -  }, | 
| -] | 
| - | 
| _RESTART_ADBD_SCRIPT = """ | 
| trap '' HUP | 
| trap '' TERM | 
| @@ -88,6 +64,34 @@ _RESTART_ADBD_SCRIPT = """ | 
| restart & | 
| """ | 
|  | 
| +# Not all permissions can be set. | 
| +_PERMISSIONS_BLACKLIST = [ | 
| +    'android.permission.ACCESS_MOCK_LOCATION', | 
| +    'android.permission.ACCESS_NETWORK_STATE', | 
| +    'android.permission.BLUETOOTH', | 
| +    'android.permission.BLUETOOTH_ADMIN', | 
| +    'android.permission.INTERNET', | 
| +    'android.permission.MANAGE_ACCOUNTS', | 
| +    'android.permission.MODIFY_AUDIO_SETTINGS', | 
| +    'android.permission.NFC', | 
| +    'android.permission.READ_SYNC_SETTINGS', | 
| +    'android.permission.READ_SYNC_STATS', | 
| +    'android.permission.USE_CREDENTIALS', | 
| +    'android.permission.VIBRATE', | 
| +    'android.permission.WAKE_LOCK', | 
| +    'android.permission.WRITE_SYNC_SETTINGS', | 
| +    'com.android.browser.permission.READ_HISTORY_BOOKMARKS', | 
| +    'com.android.browser.permission.WRITE_HISTORY_BOOKMARKS', | 
| +    'com.android.launcher.permission.INSTALL_SHORTCUT', | 
| +    'com.chrome.permission.DEVICE_EXTRAS', | 
| +    'com.google.android.apps.chrome.permission.C2D_MESSAGE', | 
| +    'com.google.android.apps.chrome.permission.READ_WRITE_BOOKMARK_FOLDERS', | 
| +    'com.google.android.apps.chrome.TOS_ACKED', | 
| +    'com.google.android.c2dm.permission.RECEIVE', | 
| +    'com.google.android.providers.gsf.permission.READ_GSERVICES', | 
| +    'com.sec.enterprise.knox.MDM_CONTENT_PROVIDER', | 
| +] | 
| + | 
| _CURRENT_FOCUS_CRASH_RE = re.compile( | 
| r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}') | 
|  | 
| @@ -517,13 +521,16 @@ class DeviceUtils(object): | 
| @decorators.WithTimeoutAndRetriesDefaults( | 
| INSTALL_DEFAULT_TIMEOUT, | 
| INSTALL_DEFAULT_RETRIES) | 
| -  def Install(self, apk_path, reinstall=False, timeout=None, retries=None): | 
| +  def Install(self, apk_path, reinstall=False, permissions=None, timeout=None, | 
| +              retries=None): | 
| """Install an APK. | 
|  | 
| Noop if an identical APK is already installed. | 
|  | 
| Args: | 
| apk_path: A string containing the path to the APK to install. | 
| +      permissions: Set of permissions to set. If not set, finds permissions with | 
| +          apk helper. To set no permissions, pass []. | 
| reinstall: A boolean indicating if we should keep any existing app data. | 
| timeout: timeout in seconds | 
| retries: number of retries | 
| @@ -555,6 +562,12 @@ class DeviceUtils(object): | 
| self.adb.Install(apk_path, reinstall=reinstall) | 
| self._cache['package_apk_checksums'][package_name] = host_checksums | 
|  | 
| +    if (permissions is None | 
| +        and self.build_version_sdk >= version_codes.MARSHMALLOW): | 
| +      permissions = apk_helper.ApkHelper(apk_path).GetPermissions() | 
| +    self.GrantPermissions(package_name, permissions) | 
| + | 
| + | 
| @decorators.WithTimeoutAndRetriesDefaults( | 
| INSTALL_DEFAULT_TIMEOUT, | 
| INSTALL_DEFAULT_RETRIES) | 
| @@ -1934,3 +1947,24 @@ class DeviceUtils(object): | 
| self.WriteFile(script.name, _RESTART_ADBD_SCRIPT) | 
| self.RunShellCommand(['source', script.name], as_root=True) | 
| self.adb.WaitForDevice() | 
| + | 
| +  @decorators.WithTimeoutAndRetriesFromInstance() | 
| +  def GrantPermissions(self, package, permissions, timeout=None, retries=None): | 
| +    # Permissions only need to be set on M and above because of the changes to | 
| +    # the permission model. | 
| +    if not permissions or self.build_version_sdk < version_codes.MARSHMALLOW: | 
| +      return | 
| +    # TODO(rnephew): After permission blacklist is complete, switch to using | 
| +    # &&s instead of ;s. | 
| +    cmd = '' | 
| +    logging.info('Setting permissions for %s', package) | 
| +    for p in permissions: | 
| +      if p not in _PERMISSIONS_BLACKLIST: | 
| +        cmd += 'pm grant %s %s;' % (package, p) | 
| +        logging.info('  %s', p) | 
| +    if cmd: | 
| +      output = self.RunShellCommand(cmd) | 
| +      if output: | 
| +        logging.warning('Possible problem when granting permissions. Blacklist ' | 
| +                        'may need to be updated.') | 
| +        logging.warning(output) | 
|  |