xref: /aosp_15_r20/external/autotest/server/cros/faft/utils/menu_mode_switcher.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright 2021 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
6import abc
7import logging
8import six
9
10from autotest_lib.client.common_lib import error
11
12
13@six.add_metaclass(abc.ABCMeta)
14class _BaseMenuModeSwitcher:
15    """
16    Base class for mode switch with menu navigator.
17    """
18
19    def __init__(self, faft_framework, menu_navigator):
20        self.test = faft_framework
21        self.faft_config = self.test.faft_config
22        self.servo = self.test.servo
23        self.menu = menu_navigator
24        self.checkers = faft_framework.checkers
25
26        self.minidiag_enabled = self.faft_config.minidiag_enabled
27        self.minios_enabled = self.faft_config.minios_enabled
28
29    @abc.abstractmethod
30    def trigger_rec_to_dev(self):
31        """
32        Trigger to-dev transition.
33        """
34        raise NotImplementedError
35
36    @abc.abstractmethod
37    def dev_boot_from_internal(self):
38        """
39        Boot from internal disk in developer mode.
40        """
41        raise NotImplementedError
42
43    @abc.abstractmethod
44    def dev_boot_from_external(self):
45        """Boot from external disk in developer mode."""
46        raise NotImplementedError
47
48    @abc.abstractmethod
49    def trigger_dev_to_normal(self):
50        """
51        Trigger dev-to-norm transition.
52        """
53        raise NotImplementedError
54
55    @abc.abstractmethod
56    def power_off(self):
57        """
58        Power off the device.
59
60        This method should work in both developer and recovery screens.
61        """
62        raise NotImplementedError
63
64
65class _TabletDetachableMenuModeSwitcher(_BaseMenuModeSwitcher):
66    """
67    Mode switcher with menu navigator for legacy menu UI.
68
69    The "legacy menu UI" is an old menu-based UI, which has been replaced
70    by the new one, called "menu UI".
71    """
72
73    def trigger_rec_to_dev(self):
74        """
75        Trigger to-dev transition.
76        """
77        self.test.switcher.trigger_rec_to_dev()
78
79    def dev_boot_from_internal(self):
80        """
81        Boot from internal disk in developer mode.
82
83        Menu items in developer warning screen:
84            0. Developer Options
85            1. Show Debug Info
86            2. Enable OS Verification
87           *3. Power Off
88            4. Language
89
90        Menu items in developer boot options screen:
91            0. Boot From Network
92            1. Boot Legacy BIOS
93            2. Boot From USB or SD Card
94           *3. Boot From Internal Disk
95            4. Cancel
96            5. Power Off
97
98        (*) is the default selection.
99        """
100        self.test.wait_for('firmware_screen')
101        self.menu.move_to(3, 0)
102        self.menu.select('Selecting "Developer Options"...')
103        self.test.wait_for('keypress_delay')
104        self.menu.select('Selecting "Boot From Internal Disk"...')
105
106    def dev_boot_from_external(self):
107        """Boot from external disk in developer mode.
108
109        Menu items in developer warning screen:
110            0. Developer Options
111            1. Show Debug Info
112            2. Enable OS Verification
113           *3. Power Off
114            4. Language
115
116        Menu items in developer boot options screen:
117            0. Boot From Network
118            1. Boot Legacy BIOS
119            2. Boot From USB or SD Card
120           *3. Boot From Internal Disk
121            4. Cancel
122            5. Power Off
123            6. Language
124        """
125        self.test.wait_for('firmware_screen')
126        self.menu.move_to(3, 0)
127        self.menu.select('Selecting "Developer Options"...')
128        self.test.wait_for('keypress_delay')
129        self.menu.move_to(3, 2)
130        self.menu.select('Selecting "Boot From USB or SD Card"...')
131
132    def trigger_dev_to_normal(self):
133        """
134        Trigger dev-to-norm transition.
135
136        Menu items in developer warning screen:
137            0. Developer Options
138            1. Show Debug Info
139            2. Enable OS Verification
140           *3. Power Off
141            4. Language
142
143        Menu items in to-norm confirmation screen:
144           *0. Confirm Enabling OS Verification
145            1. Cancel
146            2. Power Off
147            3. Language
148
149        (*) is the default selection.
150        """
151        self.test.wait_for('firmware_screen')
152        self.menu.move_to(3, 2)
153        self.menu.select('Selecting "Enable OS Verification"...')
154        self.test.wait_for('keypress_delay')
155        self.menu.select('Selecing "Confirm Enabling OS Verification"...')
156
157    def power_off(self):
158        """
159        Power off the device.
160
161        This method should work in both developer and recovery screens.
162        """
163        self.test.wait_for('firmware_screen')
164        # Either in developer or recovery screen, the "Power Off" option is the
165        # default one.
166        self.menu.select('Selecting "Power Off"...')
167
168
169class _MenuModeSwitcher(_BaseMenuModeSwitcher):
170    """
171    Mode switcher with menu navigator for menu UI.
172
173    The "menu UI" aims to replace both "legacy clamshell UI" and "legacy
174    menu UI". See chromium:1033815 for the discussion about the naming.
175
176    Menu items in recovery select screen:
177        0. Language
178        1. Recovery using phone (always hidden)
179        2. Recovery using external disk
180        3. Recovery using internet connection (shown if minios_enabled)
181        4. Launch diagnostics (shown if minidiag_enabled)
182        5. Advanced options
183        6. Power off
184    """
185    RECOVERY_SELECT_ITEM_COUNT = 7
186
187    def _confirm_to_dev(self):
188        if self.faft_config.rec_button_dev_switch:
189            logging.info('Confirm to-dev by RECOVERY button')
190            self.servo.toggle_recovery_switch()
191        elif self.faft_config.power_button_dev_switch:
192            logging.info('Confirm to-dev by POWER button')
193            self.servo.power_normal_press()
194        else:
195            self.menu.select('Confirm to-dev by menu selection')
196
197    def trigger_rec_to_dev(self):
198        """
199        Trigger to-dev transition.
200
201        Menu items in advanced options screen:
202            0. Language
203           *1. Enable developer mode
204            2. Back
205            3. Power off
206
207        Menu items in to-dev screen:
208            0. Language
209           *1. Confirm
210            2. Cancel
211            3. Power off
212
213        (*) is the default selection.
214        """
215        self.test.wait_for('firmware_screen')
216        # The default selection is unknown, navigate to the last item first
217        self.menu.move_to(0, self.RECOVERY_SELECT_ITEM_COUNT)
218        # Navigate to "Advanced options"
219        self.menu.up()
220        self.test.wait_for('keypress_delay')
221        self.menu.select('Selecting "Advanced options"...')
222        self.test.wait_for('keypress_delay')
223        self.menu.select('Selecting "Enable developer mode"...')
224        self.test.wait_for('keypress_delay')
225        # Confirm to-dev transition
226        self._confirm_to_dev()
227
228    def dev_boot_from_internal(self):
229        """
230        Boot from internal disk in developer mode.
231
232        Menu items in developer mode screen:
233            0. Language
234            1. Return to secure mode
235            2. Boot from internal disk
236            3. Boot from external disk
237            4. Advanced options
238            5. Power off
239        """
240        self.test.wait_for('firmware_screen')
241        # Since the default selection is unknown, navigate to item 0 first
242        self.menu.move_to(5, 0)
243        # Navigate to "Boot from internal disk"
244        self.menu.move_to(0, 2)
245        self.menu.select('Selecting "Boot from internal disk"...')
246
247    def dev_boot_from_external(self):
248        """Boot from external disk in developer mode.
249
250        Menu items in developer mode screen:
251            0. Language
252            1. Return to secure mode
253            2. Boot from internal disk
254            3. Boot from external disk
255            4. Advanced options
256            5. Power off
257        """
258        self.test.wait_for('firmware_screen')
259        # Since the default selection is unknown, navigate to item 0 first
260        self.menu.move_to(5, 0)
261        # Navigate to "Boot from external disk"
262        self.menu.move_to(0, 3)
263        self.menu.select('Selecting "Boot from external disk"...')
264
265    def trigger_dev_to_normal(self):
266        """
267        Trigger dev-to-norm transition.
268
269        Menu items in developer mode screen:
270            0. Language
271            1. Return to secure mode
272            2. Boot from internal disk
273            3. Boot from external disk
274            4. Advanced options
275            5. Power off
276
277        Menu items in to-norm screen:
278            0. Language
279           *1. Confirm
280            2. Cancel
281            3. Power off
282
283        (*) is the default selection.
284        """
285        self.test.wait_for('firmware_screen')
286        # Since the default selection is unknown, navigate to item 0 first
287        self.menu.move_to(5, 0)
288        # Navigate to "Return to secure mode"
289        self.menu.down()
290        self.test.wait_for('keypress_delay')
291        self.menu.select('Selecting "Return to secure mode"...')
292        self.test.wait_for('keypress_delay')
293        self.menu.select('Selecing "Confirm"...')
294
295    def power_off(self):
296        """
297        Power off the device.
298
299        This method should work in both developer and recovery screens.
300        """
301        self.test.wait_for('firmware_screen')
302        # Since there are at most 6 menu items in dev/rec screen, move the
303        # cursor down 6 times to ensure we reach the last menu item.
304        self.menu.move_to(0, 6)
305        self.menu.select('Selecting "Power off"...')
306
307    def trigger_rec_to_minidiag(self):
308        """
309        Trigger rec-to-MiniDiag.
310
311        @raise TestError if MiniDiag is not enabled.
312        """
313
314        # Validity check; this only applicable for MiniDiag enabled devices.
315        if not self.minidiag_enabled:
316            raise error.TestError('MiniDiag is not enabled for this board')
317
318        self.test.wait_for('firmware_screen')
319        # The default selection is unknown, so navigate to the last item first
320        self.menu.move_to(0, self.RECOVERY_SELECT_ITEM_COUNT)
321        # Navigate to "Launch diagnostics"
322        self.menu.up()
323        self.test.wait_for('keypress_delay')
324        self.menu.up()
325        self.test.wait_for('keypress_delay')
326        self.menu.select('Selecting "Launch diagnostics"...')
327        self.test.wait_for('firmware_screen')
328
329    def navigate_minidiag_storage(self):
330        """
331        Navigate to storage screen.
332
333        Menu items in storage screen:
334            0. Language
335            1. Page up (disabled)
336            2. Page down
337            3. Back
338            4. Power off
339
340        @raise TestError if MiniDiag is not enabled.
341        """
342
343        # Validity check; this only applicable for MiniDiag enabled devices.
344        if not self.minidiag_enabled:
345            raise error.TestError('MiniDiag is not enabled for this board')
346
347        # From root screen to storage screen
348        self.menu.select('Selecting "Storage"...')
349        self.test.wait_for('keypress_delay')
350        # Since the default selection is unknown, navigate to item 4 first
351        self.menu.move_to(0, 4)
352        # Navigate to "Back"
353        self.menu.up()
354        self.test.wait_for('keypress_delay')
355        self.menu.select('Back to MiniDiag root screen...')
356        self.test.wait_for('keypress_delay')
357
358    def navigate_minidiag_quick_memory_check(self):
359        """
360        Navigate to quick memory test screen.
361
362        Menu items in quick memory test screen:
363            0. Language
364            1. Page up (disabled)
365            2. Page down (disabled
366            3. Back
367            4. Power off
368
369        @raise TestError if MiniDiag is not enabled.
370        """
371
372        # Validity check; this only applicable for MiniDiag enabled devices.
373        if not self.minidiag_enabled:
374            raise error.TestError('MiniDiag is not enabled for this board')
375
376        # From root screen to quick memory test screen
377        # There might be self test items, so navigate to the last item first
378        self.menu.move_to(0, 5)
379        self.menu.up()  # full memory test
380        self.test.wait_for('keypress_delay')
381        self.menu.up()  # quick memory test
382        self.test.wait_for('keypress_delay')
383        self.menu.select('Selecting "Quick memory test"...')
384        self.test.wait_for('keypress_delay')
385        # Wait for quick memory test
386        self.menu.select('Back to MiniDiag root screen...')
387        self.test.wait_for('keypress_delay')
388
389    def reset_and_leave_minidiag(self):
390        """
391        Reset the DUT and normal boot to leave MiniDiag.
392
393        @raise TestError if MiniDiag is not enabled or no apreset support.
394        """
395
396        # Validity check; this only applicable for MiniDiag enabled devices.
397        if not self.minidiag_enabled:
398            raise error.TestError('MiniDiag is not enabled for this board')
399
400        # Since we want to keep the cbmem log, we need an AP reset and reboot to
401        # normal mode
402        if self.test.ec.has_command('apreset'):
403            logging.info('Trigger apreset')
404            self.test.ec.send_command('apreset')
405        else:
406            raise error.TestError('EC command apreset is not supported')
407
408    def trigger_rec_to_minios(self, older_version=False):
409        """
410        Trigger recovery-to-MiniOS transition.
411
412        Menu items in advanced options screen, developer mode:
413            0. Language
414           *1. Debug info
415            2. Firmware log
416            3. Internet recovery (older version)
417            4. Back
418            5. Power off
419
420        (*) is the default selection.
421
422        @param older_version: True for selecting the older version button in the
423                              advanced options screen, and False for selecting
424                              the newer one in the recovery selection screen.
425        @raise TestError if MiniOS is not enabled.
426        """
427        # Validity check
428        if not self.minios_enabled:
429            raise NotImplementedError
430
431        # Boot to MiniOS through UI menu
432        if older_version:
433            logging.info('Boot to MiniOS (older version)')
434            # The default selection is unknown, so navigate to the last item
435            # first
436            self.menu.move_to(0, self.RECOVERY_SELECT_ITEM_COUNT)
437            # Navigate to "Advanced options"
438            self.menu.up()
439            self.test.wait_for('keypress_delay')
440            self.menu.select('Selecting "Advanced options"...')
441            self.test.wait_for('keypress_delay')
442            # Navigate to the last item in advanced options
443            self.menu.move_to(0, 5)
444            self.menu.move_to(5, 3)
445            self.menu.select(
446                    'Selecting "Internet recovery (older version)"...')
447        else:
448            logging.info('Boot to MiniOS')
449            self.menu.down()
450            self.menu.select(
451                    'Selecting "Recovery using internet connection"...')
452
453        self.test.wait_for('minios_screen')
454
455
456_MENU_MODE_SWITCHER_CLASSES = {
457        'menu_switcher': _MenuModeSwitcher,
458        'tablet_detachable_switcher': _TabletDetachableMenuModeSwitcher,
459}
460
461
462def create_menu_mode_switcher(faft_framework, menu_navigator):
463    """
464    Create a proper navigator based on its mode switcher type.
465
466    @param faft_framework: The main FAFT framework object.
467    @param menu_navigator: The menu navigator for base logic of navigation.
468    """
469    switcher_type = faft_framework.faft_config.mode_switcher_type
470    switcher_class = _MENU_MODE_SWITCHER_CLASSES.get(switcher_type, None)
471    if switcher_class is None:
472        # Not all devices support menu-based UI, so it is fine to return None.
473        logging.info('Switcher type %s is menuless, return None',
474                     switcher_type)
475        return None
476    return switcher_class(faft_framework, menu_navigator)
477