• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright 2016 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5#
6# arc_util.py is supposed to be called from chrome.py for ARC specific logic.
7# It should not import arc.py since it will create a import loop.
8
9import collections
10import logging
11import os
12import tempfile
13
14from autotest_lib.client.bin import utils
15from autotest_lib.client.common_lib import error
16from autotest_lib.client.common_lib import file_utils
17from autotest_lib.client.common_lib.cros import arc_common
18from telemetry.core import exceptions
19from telemetry.internal.browser import extension_page
20
21_ARC_SUPPORT_HOST_URL = 'chrome-extension://cnbgggchhmkkdmeppjobngjoejnihlei/'
22_ARC_SUPPORT_HOST_PAGENAME = '_generated_background_page.html'
23_DUMPSTATE_PATH = '/var/log/arc-dumpstate.log'
24_USERNAME = 'crosarcplusplustest@gmail.com'
25_ARCP_URL = 'https://sites.google.com/a/chromium.org/dev/chromium-os' \
26                '/testing/arcplusplus-testing/arcp'
27_OPT_IN_BEGIN = 'Initializing ARC opt-in flow.'
28_OPT_IN_SKIP = 'Skip waiting provisioning completed.'
29_OPT_IN_FINISH = 'ARC opt-in flow complete.'
30
31_SIGN_IN_TIMEOUT = 120
32_SIGN_IN_CHECK_INTERVAL = 1
33
34
35# Describes ARC session state.
36# - provisioned is set to True once ARC is provisioned.
37# - tos_needed is set to True in case ARC Terms of Service need to be shown.
38#              This depends on ARC Terms of Service acceptance and policy for
39#              ARC preferences, such as backup and restore and use of location
40#              services.
41ArcSessionState = collections.namedtuple(
42    'ArcSessionState', ['provisioned', 'tos_needed'])
43
44
45def get_arc_session_state(autotest_ext):
46    """Returns the runtime state of ARC session.
47
48    @param autotest_ext private autotest API.
49
50    @raises error.TestFail in case autotest API failure or if ARC is not
51                           allowed for the device.
52
53    @return ArcSessionState that describes the state of current ARC session.
54
55    """
56    get_arc_state = '''
57        new Promise((resolve, reject) => {
58            chrome.autotestPrivate.getArcState((state) => {
59                if (chrome.runtime.lastError) {
60                    reject(new Error(chrome.runtime.lastError.message));
61                    return;
62                }
63                resolve(state);
64            });
65        })
66    '''
67    try:
68        state = autotest_ext.EvaluateJavaScript(get_arc_state, promise=True)
69        return ArcSessionState(state['provisioned'], state['tosNeeded'])
70    except exceptions.EvaluateException as e:
71        raise error.TestFail('Could not get ARC session state "%s".' % e)
72
73
74def _wait_arc_provisioned(autotest_ext):
75    """Waits until ARC provisioning is completed.
76
77    @param autotest_ext private autotest API.
78
79    @raises: error.TimeoutException if provisioning is not complete.
80
81    """
82    utils.poll_for_condition(
83            condition=lambda: get_arc_session_state(autotest_ext).provisioned,
84            desc='Wait for ARC is provisioned',
85            timeout=_SIGN_IN_TIMEOUT,
86            sleep_interval=_SIGN_IN_CHECK_INTERVAL)
87
88
89def should_start_arc(arc_mode):
90    """
91    Determines whether ARC should be started.
92
93    @param arc_mode: mode as defined in arc_common.
94
95    @returns: True or False.
96
97    """
98    logging.debug('ARC is enabled in mode %s', arc_mode)
99    assert arc_mode is None or arc_mode in arc_common.ARC_MODES
100    return arc_mode in [arc_common.ARC_MODE_ENABLED,
101                        arc_common.ARC_MODE_ENABLED_ASYNC]
102
103
104def post_processing_after_browser(chrome, timeout):
105    """
106    Called when a new browser instance has been initialized.
107
108    Note that this hook function is called regardless of arc_mode.
109
110    @param chrome: Chrome object.
111
112    """
113    # Remove any stale dumpstate files.
114    if os.path.isfile(_DUMPSTATE_PATH):
115        os.unlink(_DUMPSTATE_PATH)
116
117    # Wait for Android container ready if ARC is enabled.
118    if chrome.arc_mode == arc_common.ARC_MODE_ENABLED:
119        try:
120            arc_common.wait_for_android_boot(timeout)
121        except Exception:
122            # Save dumpstate so that we can figure out why boot does not
123            # complete.
124            _save_android_dumpstate()
125            raise
126
127
128def pre_processing_before_close(chrome):
129    """
130    Called when the browser instance is being closed.
131
132    Note that this hook function is called regardless of arc_mode.
133
134    @param chrome: Chrome object.
135
136    """
137    if not should_start_arc(chrome.arc_mode):
138        return
139    # TODO(b/29341443): Implement stopping of adb logcat when we start adb
140    # logcat for all tests
141
142    # Save dumpstate just before logout.
143    _save_android_dumpstate()
144
145
146def _save_android_dumpstate():
147    """
148    Triggers a dumpstate and saves its contents to to /var/log/arc-dumpstate.log
149    with logging.
150
151    Exception thrown while doing dumpstate will be ignored.
152    """
153
154    try:
155        logging.info('Saving Android dumpstate.')
156        with open(_DUMPSTATE_PATH, 'w') as out:
157            log = utils.system_output('android-sh -c arc-bugreport')
158            out.write(log)
159        logging.info('Android dumpstate successfully saved.')
160    except Exception:
161        # Dumpstate is nice-to-have stuff. Do not make it as a fatal error.
162        logging.exception('Failed to save Android dumpstate.')
163
164
165def get_test_account_info():
166    """Retrieve test account information."""
167    with tempfile.NamedTemporaryFile() as pltp:
168        file_utils.download_file(_ARCP_URL, pltp.name)
169        password = pltp.read().rstrip()
170    return (_USERNAME, password)
171
172
173def set_browser_options_for_opt_in(b_options):
174    """
175    Setup Chrome for gaia login and opt_in.
176
177    @param b_options: browser options object used by chrome.Chrome.
178
179    """
180    b_options.username, b_options.password = get_test_account_info()
181    b_options.disable_default_apps = False
182    b_options.disable_component_extensions_with_background_pages = False
183    b_options.gaia_login = True
184
185
186def enable_play_store(autotest_ext, enabled, enable_managed_policy=True):
187    """
188    Enable ARC++ Play Store
189
190    Do nothing if the account is managed and enable_managed_policy is set to
191    True.
192
193    @param autotest_ext: autotest extension object.
194
195    @param enabled: if True then perform opt-in, otherwise opt-out.
196
197    @param enable_managed_policy: If False then policy check is ignored for
198            managed user case and ARC++ is forced enabled or disabled.
199
200    @returns: True if the opt-in should continue; else False.
201
202    """
203
204    if autotest_ext is None:
205         raise error.TestFail(
206                 'Could not change the Play Store enabled state because '
207                 'autotest API does not exist')
208
209    # Skip enabling for managed users, since value is policy enforced.
210    # Return early if a managed user has ArcEnabled set to false.
211    get_play_store_state = '''
212            new Promise((resolve, reject) => {
213                chrome.autotestPrivate.getPlayStoreState((state) => {
214                    if (chrome.runtime.lastError) {
215                        reject(new Error(chrome.runtime.lastError.message));
216                        return;
217                    }
218                    resolve(state);
219                });
220            })
221    '''
222    try:
223        state = autotest_ext.EvaluateJavaScript(get_play_store_state,
224                                                promise=True)
225        if state['managed']:
226            # Handle managed case.
227            logging.info('Determined that ARC is managed by user policy.')
228            if enable_managed_policy:
229                if enabled == state['enabled']:
230                    return True
231                logging.info('Returning early since ARC is policy-enforced.')
232                return False
233            logging.info('Forcing ARC %s, ignore managed state.',
234                    ('enabled' if enabled else 'disabled'))
235
236        autotest_ext.ExecuteJavaScript('''
237                chrome.autotestPrivate.setPlayStoreEnabled(
238                    %s, function(enabled) {});
239            ''' % ('true' if enabled else 'false'))
240    except exceptions.EvaluateException as e:
241        raise error.TestFail('Could not change the Play Store enabled state '
242                             ' via autotest API. "%s".' % e)
243
244    return True
245
246
247def find_opt_in_extension_page(browser):
248    """
249    Find and verify the opt-in extension extension page.
250
251    @param browser: chrome.Chrome broswer object.
252
253    @returns: the extension page.
254
255    @raises: error.TestFail if extension is not found or is mal-formed.
256
257    """
258    opt_in_extension_id = extension_page.UrlToExtensionId(_ARC_SUPPORT_HOST_URL)
259    try:
260        extension_pages = browser.extensions.GetByExtensionId(
261            opt_in_extension_id)
262    except Exception as e:
263        raise error.TestFail('Could not locate extension for arc opt-in. '
264                             'Make sure disable_default_apps is False. '
265                             '"%s".' % e)
266
267    extension_main_page = None
268    for page in extension_pages:
269        url = page.EvaluateJavaScript('location.href;')
270        if url.endswith(_ARC_SUPPORT_HOST_PAGENAME):
271            extension_main_page = page
272            break
273    if not extension_main_page:
274        raise error.TestError('Found opt-in extension but not correct page!')
275    extension_main_page.WaitForDocumentReadyStateToBeComplete()
276
277    js_code_did_start_conditions = ['termsPage != null',
278            '(termsPage.isManaged_ || termsPage.state_ == LoadState.LOADED)']
279    try:
280        for condition in js_code_did_start_conditions:
281            extension_main_page.WaitForJavaScriptCondition(condition,
282                                                           timeout=60)
283    except Exception as e:
284        raise error.TestError('Error waiting for "%s": "%s".' % (condition, e))
285
286    return extension_main_page
287
288
289def opt_in_and_wait_for_completion(extension_main_page):
290    """
291    Step through the user input of the opt-in extension and wait for completion.
292
293    @param extension_main_page: opt-in extension object.
294
295    @raises error.TestFail if opt-in doesn't complete after timeout.
296
297    """
298    extension_main_page.ExecuteJavaScript('termsPage.onAgree()')
299
300    try:
301        extension_main_page.WaitForJavaScriptCondition('!appWindow',
302                                                       timeout=_SIGN_IN_TIMEOUT)
303    except Exception as e:
304        js_read_error_message = """
305            err = appWindow.contentWindow.document.getElementById(
306                    "error-message");
307            if (err) {
308                err.innerText;
309            }
310        """
311        err_msg = extension_main_page.EvaluateJavaScript(js_read_error_message)
312        err_msg = err_msg.strip()
313        logging.error('Error: %r', err_msg)
314        if err_msg:
315            raise error.TestFail('Opt-in app error: %r' % err_msg)
316        else:
317            raise error.TestFail('Opt-in app did not finish running after %s '
318                                 'seconds!' % _SIGN_IN_TIMEOUT)
319    # Reset termsPage to be able to reuse OptIn page and wait condition for ToS
320    # are loaded.
321    extension_main_page.ExecuteJavaScript('termsPage = null')
322
323
324def opt_in(browser,
325           autotest_ext,
326           enable_managed_policy=True,
327           wait_for_provisioning=True):
328    """
329    Step through opt in and wait for it to complete.
330
331    Return early if the arc_setting cannot be set True.
332
333    @param browser: chrome.Chrome browser object.
334    @param autotest_ext: autotest extension object.
335    @param enable_managed_policy: If False then policy check is ignored for
336            managed user case and ARC++ is forced enabled.
337    @param wait_for_provisioning: True in case we have to wait for provisioning
338                                       to complete.
339
340    @raises: error.TestFail if opt in fails.
341
342    """
343
344    logging.info(_OPT_IN_BEGIN)
345
346    # TODO(b/124391451): Enterprise tests have race, when ARC policy appears
347    # after profile prefs sync completed. That means they may appear as
348    # non-managed first, treated like this but finally turns as managed. This
349    # leads to test crash. Solution is to wait until prefs are synced or, if
350    # possible tune test accounts to wait sync is completed before starting the
351    # Chrome session.
352
353    # Enabling Play Store may affect this flag, capture it before.
354    tos_needed = get_arc_session_state(autotest_ext).tos_needed
355
356    if not enable_play_store(autotest_ext, True, enable_managed_policy):
357        return
358
359    if not wait_for_provisioning:
360        logging.info(_OPT_IN_SKIP)
361        return
362
363    if tos_needed:
364        extension_main_page = find_opt_in_extension_page(browser)
365        opt_in_and_wait_for_completion(extension_main_page)
366    else:
367        _wait_arc_provisioned(autotest_ext)
368
369    logging.info(_OPT_IN_FINISH)
370