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