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