summaryrefslogtreecommitdiff
path: root/tests/bettertogether/quickstart/performance_test/gms_auto_updates_util.py
blob: f65d7d92ca8b071eb61c301a1815d5a34acdd84b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#  Copyright (C) 2023 The Android Open Source Project
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

"""class to enable/disable GMS auto update."""

import logging
import os
import tempfile
# from xml import etree has a problem, don't use it.
from xml.etree import ElementTree
from mobly.controllers import android_device
from mobly.controllers.android_device_lib import adb


_FINSKY_CONFIG_FILE = '/data/data/com.android.vending/shared_prefs/finsky.xml'
_FINSKY_CONFIG_NAME = 'auto_update_enabled'
_FINSKY_CONFIG_VALUE_DISABLE = 'false'
_FINSKY_CONFIG_VALUE_ENABLE = 'true'
_VENDING_CONFIG_FILE = '/data/data/com.android.vending/shared_prefs/com.android.vending_preferences.xml'
_VENDING_CONFIG_NAME = 'auto-update-mode'
_VENDING_CONFIG_VALUE_DISABLE = 'AUTO_UPDATE_NEVER'
_VENDING_CONFIG_VALUE_ENABLE = 'AUTO_UPDATE_WIFI'
_BLANK_CONFIG = '<?xml version="1.0" encoding="utf-8"?><map></map>'
_XML_BOOL_TYPE = 'boolean'
_XML_STRING_TYPE = 'string'
_ENABLE_GSERVICES_CMD_TEMPLATE = [
    (
        'am broadcast '
        '-a com.google.gservices.intent.action.GSERVICES_OVERRIDE '
        '-e finsky.play_services_auto_update_enabled {}'
    ),
    (
        'am broadcast '
        '-a com.google.gservices.intent.action.GSERVICES_OVERRIDE '
        '-e finsky.setup_wizard_additional_account_vpa_enable {}'
    ),
]


class GmsAutoUpdatesUtil:
  """class to enable/disable GMS auto updates."""

  def __init__(self, ad: android_device.AndroidDevice):
    self._device: android_device.AndroidDevice = ad

  def enable_gms_auto_updates(self) -> None:
    self._config_gms_auto_updates(True)

  def disable_gms_auto_updates(self) -> None:
    self._config_gms_auto_updates(False)

  def _config_gms_auto_updates(self, enable_updates: bool) -> None:
    """Configures GMS auto updates."""
    if not self._device.is_adb_root:
      self._device.log.info(
          f'failed to set the play store auto updates as {enable_updates}'
          'you should enable/disable it manually on an unrooted device.')
    else:
      if enable_updates:
        self._configure_play_store_updates(
            _FINSKY_CONFIG_VALUE_ENABLE, _VENDING_CONFIG_VALUE_ENABLE
        )
      else:
        self._configure_play_store_updates(
            _FINSKY_CONFIG_VALUE_DISABLE, _VENDING_CONFIG_VALUE_DISABLE
        )
    self._configure_gservice_updates(enable_updates)

  def _configure_gservice_updates(self, enable_updates: bool) -> None:
    """Overwites Gservice to enable/disable updates."""
    for cmd in _ENABLE_GSERVICES_CMD_TEMPLATE:
      self._device.adb.shell(
          cmd.format('true' if enable_updates else 'false')
      )

  def _create_or_update_play_store_config(
      self,
      tmp_dir: str,
      value_type: str,
      name: str,
      value: str,
      device_path: str,
  ) -> str:
    """Creates or updates a Play Store configuration file.

    The function retrieves the Play Store configuration file from the device
    then update it. If the file does not exist, it creates a new one.

    Args:
        tmp_dir: The temporary directory to store the configuration file.
        value_type: The type of the configuration field.
        name: The name of the configuration field.
        value: The value of the configuration field.
        device_path: The path to the configuration file on the device.

    Returns:
        The path to the updated configuration file.
    """
    path = os.path.join(tmp_dir, f'play_store_config_{name}.xml')
    try:
      self._device.adb.pull([device_path, path])
    except adb.AdbError as e:
      self._device.log.warning('failed to pull %s: %s', device_path, e)

    config_doc = ElementTree.parse(path) if os.path.isfile(path) else None

    changing_element = None
    root = (
        ElementTree.fromstring(_BLANK_CONFIG.encode())
        if config_doc is None
        else config_doc.getroot()
    )

    # find the element, xPath doesn't work as the name is a reserved word.
    for child in root:
      if child.attrib['name'] == name:
        changing_element = child
        break
    if changing_element is None:
      if value_type == _XML_BOOL_TYPE:
        changing_element = ElementTree.SubElement(root, 'boolean')
      else:
        changing_element = ElementTree.SubElement(root, 'string')
    logging.info('element for %s is %s, %s', name, changing_element.tag,
                 changing_element.attrib)
    if value_type == _XML_BOOL_TYPE:
      changing_element.set('name', name)
      changing_element.set('value', value)
    else:
      changing_element.attrib['name'] = name
      changing_element.text = value

    tree = ElementTree.ElementTree(root)
    tree.write(path, xml_declaration=True, encoding='utf-8')
    return path

  def _configure_play_store_updates(
      self, finsky_config_value: str, vending_config_value: str
  ) -> None:
    """Configures the Play Store update related settings."""
    with tempfile.TemporaryDirectory() as tmp_dir:
      finsky_config = self._create_or_update_play_store_config(
          tmp_dir,
          _XML_BOOL_TYPE,
          _FINSKY_CONFIG_NAME,
          finsky_config_value,
          _FINSKY_CONFIG_FILE,
      )
      self._device.adb.push([finsky_config, _FINSKY_CONFIG_FILE])
      try:
        os.remove(finsky_config)
      except OSError as e:
        logging.warning('failed to remove %s: %s', finsky_config, e)

      vending_config = self._create_or_update_play_store_config(
          tmp_dir,
          _XML_STRING_TYPE,
          _VENDING_CONFIG_NAME,
          vending_config_value,
          _VENDING_CONFIG_FILE,
      )
      self._device.adb.push([vending_config, _VENDING_CONFIG_FILE])
      try:
        os.remove(vending_config)
      except OSError as e:
        logging.warning('failed to remove %s: %s', vending_config, e)