vboot/ui: add screen stack functionality

Add a stack storing previous screen states.  When the user clicks
"Back" or presses ESC, revert to the previous state.

In order to deal with the possibility of a UI cycle (repeatedly
selecting the same sequence of screens) which would eventually
use up all available memory, re-use existing target screen states
within the stack.  In other words, when switching to a specific
screen which already exists in the stack, pop until that screen
is reached, rather than creating a duplicate stack entry.

BUG=b:146399181, b:158256196
TEST=make clean && make runtests
BRANCH=none

Change-Id: I6fbebc2abb11b26d95d4fcf841eb195b3d589396
Signed-off-by: Joel Kitching <kitching@google.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2214617
Reviewed-by: Joel Kitching <kitching@chromium.org>
Reviewed-by: Yu-Ping Wu <yupingso@chromium.org>
Tested-by: Joel Kitching <kitching@chromium.org>
Commit-Queue: Joel Kitching <kitching@chromium.org>
diff --git a/firmware/2lib/2ui.c b/firmware/2lib/2ui.c
index 4d363f6..81243ac 100644
--- a/firmware/2lib/2ui.c
+++ b/firmware/2lib/2ui.c
@@ -95,11 +95,11 @@
 		.num_items = 0,
 		.items = NULL,
 	};
-	if (ui->state.screen->get_menu) {
-		menu = ui->state.screen->get_menu(ui);
+	if (ui->state->screen->get_menu) {
+		menu = ui->state->screen->get_menu(ui);
 		return menu ? menu : &empty_menu;
 	} else {
-		return &ui->state.screen->menu;
+		return &ui->state->screen->menu;
 	}
 }
 
@@ -125,7 +125,7 @@
 	case VB_KEY_ENTER:
 		return vb2_ui_menu_select(ui);
 	case VB_KEY_ESC:
-		return vb2_ui_change_root(ui);
+		return vb2_ui_screen_back(ui);
 	default:
 		if (key != 0)
 			VB2_DEBUG("Pressed key %#x, trusted? %d\n",
@@ -142,13 +142,13 @@
 	if (!DETACHABLE && ui->key == VB_BUTTON_VOL_UP_SHORT_PRESS)
 		return VB2_REQUEST_UI_CONTINUE;
 
-	item = ui->state.selected_item - 1;
+	item = ui->state->selected_item - 1;
 	while (item >= 0 &&
-	       ((1 << item) & ui->state.disabled_item_mask))
+	       ((1 << item) & ui->state->disabled_item_mask))
 		item--;
 	/* Only update if item is valid */
 	if (item >= 0)
-		ui->state.selected_item = item;
+		ui->state->selected_item = item;
 
 	return VB2_REQUEST_UI_CONTINUE;
 }
@@ -162,13 +162,13 @@
 		return VB2_REQUEST_UI_CONTINUE;
 
 	menu = get_menu(ui);
-	item = ui->state.selected_item + 1;
+	item = ui->state->selected_item + 1;
 	while (item < menu->num_items &&
-	       ((1 << item) & ui->state.disabled_item_mask))
+	       ((1 << item) & ui->state->disabled_item_mask))
 		item++;
 	/* Only update if item is valid */
 	if (item < menu->num_items)
-		ui->state.selected_item = item;
+		ui->state->selected_item = item;
 
 	return VB2_REQUEST_UI_CONTINUE;
 }
@@ -185,7 +185,7 @@
 	if (menu->num_items == 0)
 		return VB2_REQUEST_UI_CONTINUE;
 
-	menu_item = &menu->items[ui->state.selected_item];
+	menu_item = &menu->items[ui->state->selected_item];
 
 	if (menu_item->action) {
 		VB2_DEBUG("Menu item <%s> run action\n", menu_item->text);
@@ -193,7 +193,7 @@
 	} else if (menu_item->target) {
 		VB2_DEBUG("Menu item <%s> to target screen %#x\n",
 			  menu_item->text, menu_item->target);
-		return vb2_ui_change_screen(ui, menu_item->target);
+		return vb2_ui_screen_change(ui, menu_item->target);
 	}
 
 	VB2_DEBUG("Menu item <%s> no action or target screen\n",
@@ -204,28 +204,35 @@
 /*****************************************************************************/
 /* Screen navigation functions */
 
-vb2_error_t vb2_ui_change_root(struct vb2_ui_context *ui)
+vb2_error_t vb2_ui_screen_back(struct vb2_ui_context *ui)
 {
-	return vb2_ui_change_screen(ui, ui->root_screen->id);
+	struct vb2_screen_state *tmp;
+
+	if (ui->state && ui->state->prev) {
+		tmp = ui->state->prev;
+		free(ui->state);
+		ui->state = tmp;
+	} else {
+		VB2_DEBUG("ERROR: No previous screen; ignoring\n");
+	}
+
+	return VB2_REQUEST_UI_CONTINUE;
 }
 
 static vb2_error_t default_screen_init(struct vb2_ui_context *ui)
 {
 	const struct vb2_menu *menu = get_menu(ui);
-	ui->state.selected_item = 0;
+	ui->state->selected_item = 0;
 	if (menu->num_items > 1 && menu->items[0].is_language_select)
-		ui->state.selected_item = 1;
+		ui->state->selected_item = 1;
 	return VB2_REQUEST_UI_CONTINUE;
 }
 
-vb2_error_t vb2_ui_change_screen(struct vb2_ui_context *ui, enum vb2_screen id)
+vb2_error_t vb2_ui_screen_change(struct vb2_ui_context *ui, enum vb2_screen id)
 {
 	const struct vb2_screen_info *new_screen_info;
-
-	if (ui->state.screen && ui->state.screen->id == id) {
-		VB2_DEBUG("WARNING: Already on screen %#x; ignoring\n", id);
-		return VB2_REQUEST_UI_CONTINUE;
-	}
+	struct vb2_screen_state *cur_state;
+	int state_exists = 0;
 
 	new_screen_info = vb2_get_screen_info(id);
 	if (new_screen_info == NULL) {
@@ -233,13 +240,41 @@
 		return VB2_REQUEST_UI_CONTINUE;
 	}
 
-	memset(&ui->state, 0, sizeof(ui->state));
-	ui->state.screen = new_screen_info;
+	/* Check to see if the screen state already exists in our stack. */
+	cur_state = ui->state;
+	while (cur_state != NULL) {
+		if (cur_state->screen->id == id) {
+			state_exists = 1;
+			break;
+		}
+		cur_state = cur_state->prev;
+	}
 
-	if (ui->state.screen->init)
-		return ui->state.screen->init(ui);
-	else
-		return default_screen_init(ui);
+	if (state_exists) {
+		/* Pop until the requested screen is at the top of stack. */
+		while (ui->state->screen->id != id) {
+			cur_state = ui->state;
+			ui->state = cur_state->prev;
+			free(cur_state);
+		}
+	} else {
+		/* Allocate the requested screen on top of the stack. */
+		cur_state = malloc(sizeof(*ui->state));
+		memset(cur_state, 0, sizeof(*ui->state));
+		if (cur_state == NULL) {
+			VB2_DEBUG("WARNING: malloc failed; ignoring\n");
+			return VB2_REQUEST_UI_CONTINUE;
+		}
+		cur_state->prev = ui->state;
+		cur_state->screen = new_screen_info;
+		ui->state = cur_state;
+		if (ui->state->screen->init)
+			return ui->state->screen->init(ui);
+		else
+			return default_screen_init(ui);
+	}
+
+	return VB2_REQUEST_UI_CONTINUE;
 }
 
 /*****************************************************************************/
@@ -252,16 +287,17 @@
 	struct vb2_screen_state prev_state;
 	enum vb2_ui_error prev_error_code;
 	const struct vb2_menu *menu;
+	const struct vb2_screen_info *root_info;
 	uint32_t key_flags;
 	vb2_error_t rv;
 
 	memset(&ui, 0, sizeof(ui));
 	ui.ctx = ctx;
-	ui.root_screen = vb2_get_screen_info(root_screen_id);
-	if (ui.root_screen == NULL)
+	root_info = vb2_get_screen_info(root_screen_id);
+	if (root_info == NULL)
 		VB2_DIE("Root screen not found.\n");
 	ui.locale_id = vb2_nv_get(ctx, VB2_NV_LOCALIZATION_INDEX);
-	rv = vb2_ui_change_screen(&ui, ui.root_screen->id);
+	rv = vb2_ui_screen_change(&ui, root_screen_id);
 	if (rv != VB2_REQUEST_UI_CONTINUE)
 		return rv;
 	memset(&prev_state, 0, sizeof(prev_state));
@@ -269,19 +305,19 @@
 
 	while (1) {
 		/* Draw if there are state changes. */
-		if (memcmp(&prev_state, &ui.state, sizeof(ui.state)) ||
+		if (memcmp(&prev_state, ui.state, sizeof(*ui.state)) ||
 		    /* we want to redraw/beep on a transition */
 		    prev_error_code != ui.error_code) {
 
 			menu = get_menu(&ui);
 			VB2_DEBUG("<%s> menu item <%s>\n",
-				  ui.state.screen->name,
+				  ui.state->screen->name,
 				  menu->num_items ?
-				  menu->items[ui.state.selected_item].text :
+				  menu->items[ui.state->selected_item].text :
 				  "null");
-			vb2ex_display_ui(ui.state.screen->id, ui.locale_id,
-					 ui.state.selected_item,
-					 ui.state.disabled_item_mask,
+			vb2ex_display_ui(ui.state->screen->id, ui.locale_id,
+					 ui.state->selected_item,
+					 ui.state->disabled_item_mask,
 					 ui.error_code);
 			/*
 			 * Only beep if we're transitioning from no
@@ -292,7 +328,7 @@
 				vb2ex_beep(250, 400);
 
 			/* Update prev variables. */
-			memcpy(&prev_state, &ui.state, sizeof(ui.state));
+			memcpy(&prev_state, ui.state, sizeof(*ui.state));
 			prev_error_code = ui.error_code;
 		}
 
@@ -313,8 +349,8 @@
 			return rv;
 
 		/* Run screen action. */
-		if (ui.state.screen->action) {
-			rv = ui.state.screen->action(&ui);
+		if (ui.state->screen->action) {
+			rv = ui.state->screen->action(&ui);
 			if (rv != VB2_REQUEST_UI_CONTINUE)
 				return rv;
 		}
@@ -350,7 +386,7 @@
 {
 	/* Developer mode keyboard shortcuts */
 	if (ui->key == VB_KEY_CTRL('S'))
-		return vb2_ui_change_screen(ui, VB2_SCREEN_DEVELOPER_TO_NORM);
+		return vb2_ui_screen_change(ui, VB2_SCREEN_DEVELOPER_TO_NORM);
 	if (ui->key == VB_KEY_CTRL('U') ||
 	    (DETACHABLE && ui->key == VB_BUTTON_VOL_UP_LONG_PRESS))
 		return vb2_ui_developer_mode_boot_external_action(ui);
@@ -402,7 +438,7 @@
 		VB2_DEBUG("Recovery VbTryLoadKernel %#x --> %#x\n",
 			  ui->recovery_rv, rv);
 		ui->recovery_rv = rv;
-		return vb2_ui_change_screen(ui,
+		return vb2_ui_screen_change(ui,
 					    rv == VB2_ERROR_LK_NO_DISK_FOUND ?
 					    VB2_SCREEN_RECOVERY_SELECT :
 					    VB2_SCREEN_RECOVERY_INVALID);
@@ -411,7 +447,7 @@
 	/* Manual recovery keyboard shortcuts */
 	if (ui->key == VB_KEY_CTRL('D') ||
 	    (DETACHABLE && ui->key == VB_BUTTON_VOL_UP_DOWN_COMBO_PRESS))
-		return vb2_ui_change_screen(ui, VB2_SCREEN_RECOVERY_TO_DEV);
+		return vb2_ui_screen_change(ui, VB2_SCREEN_RECOVERY_TO_DEV);
 
 	/* TODO(b/144969088): Re-implement as debug info screen. */
 	if (ui->key == '\t')
diff --git a/firmware/2lib/2ui_screens.c b/firmware/2lib/2ui_screens.c
index c43abb8..97c9b5e 100644
--- a/firmware/2lib/2ui_screens.c
+++ b/firmware/2lib/2ui_screens.c
@@ -32,7 +32,7 @@
 
 #define BACK_ITEM { \
 	.text = "Back", \
-	.action = vb2_ui_change_root, \
+	.action = vb2_ui_screen_back, \
 }
 
 #define ADVANCED_OPTIONS_ITEM { \
@@ -65,7 +65,7 @@
 static vb2_error_t language_select_action(struct vb2_ui_context *ui)
 {
 	vb2_error_t rv;
-	ui->locale_id = ui->state.selected_item;
+	ui->locale_id = ui->state->selected_item;
 	VB2_DEBUG("Locale changed to %u\n", ui->locale_id);
 
 	/* Write locale id back to nvdata. */
@@ -77,7 +77,7 @@
 	if (rv && !(ui->ctx->flags & VB2_CONTEXT_RECOVERY_MODE))
 		return rv;
 
-	return vb2_ui_change_root(ui);
+	return vb2_ui_screen_back(ui);
 }
 
 const struct vb2_menu *get_language_menu(struct vb2_ui_context *ui)
@@ -117,14 +117,14 @@
 	if (menu->num_items == 0) {
 		VB2_DEBUG("ERROR: No menu items found; "
 			  "rejecting entering language selection screen\n");
-		return vb2_ui_change_root(ui);
+		return vb2_ui_screen_back(ui);
 	}
 	if (ui->locale_id < menu->num_items) {
-		ui->state.selected_item = ui->locale_id;
+		ui->state->selected_item = ui->locale_id;
 	} else {
 		VB2_DEBUG("WARNING: Current locale not found in menu items; "
 			  "initializing selected_item to 0\n");
-		ui->state.selected_item = 0;
+		ui->state->selected_item = 0;
 	}
 	return VB2_REQUEST_UI_CONTINUE;
 }
@@ -159,12 +159,12 @@
 
 vb2_error_t advanced_options_init(struct vb2_ui_context *ui)
 {
-	ui->state.selected_item = ADVANCED_OPTIONS_ITEM_DEVELOPER_MODE;
+	ui->state->selected_item = ADVANCED_OPTIONS_ITEM_DEVELOPER_MODE;
 	if (vb2_get_sd(ui->ctx)->flags & VB2_SD_FLAG_DEV_MODE_ENABLED ||
 	    !vb2_allow_recovery(ui->ctx)) {
-		ui->state.disabled_item_mask |=
+		ui->state->disabled_item_mask |=
 			1 << ADVANCED_OPTIONS_ITEM_DEVELOPER_MODE;
-		ui->state.selected_item = ADVANCED_OPTIONS_ITEM_BACK;
+		ui->state->selected_item = ADVANCED_OPTIONS_ITEM_BACK;
 	}
 
 	return VB2_REQUEST_UI_CONTINUE;
@@ -195,12 +195,12 @@
 
 vb2_error_t recovery_select_init(struct vb2_ui_context *ui)
 {
-	ui->state.selected_item = RECOVERY_SELECT_ITEM_PHONE;
+	ui->state->selected_item = RECOVERY_SELECT_ITEM_PHONE;
 	if (!vb2api_phone_recovery_ui_enabled(ui->ctx)) {
 		VB2_DEBUG("WARNING: Phone recovery not available\n");
-		ui->state.disabled_item_mask |=
+		ui->state->disabled_item_mask |=
 			1 << RECOVERY_SELECT_ITEM_PHONE;
-		ui->state.selected_item = RECOVERY_SELECT_ITEM_EXTERNAL_DISK;
+		ui->state->selected_item = RECOVERY_SELECT_ITEM_EXTERNAL_DISK;
 	}
 	return VB2_REQUEST_UI_CONTINUE;
 }
@@ -251,21 +251,21 @@
 	if (vb2_get_sd(ui->ctx)->flags & VB2_SD_FLAG_DEV_MODE_ENABLED) {
 		/* We're in dev mode, so let user know they can't transition */
 		ui->error_code = VB2_UI_ERROR_DEV_MODE_ALREADY_ENABLED;
-		return vb2_ui_change_root(ui);
+		return vb2_ui_screen_back(ui);
 	}
 
 	if (!PHYSICAL_PRESENCE_KEYBOARD && vb2ex_physical_presence_pressed()) {
 		VB2_DEBUG("Presence button stuck?\n");
-		return vb2_ui_change_root(ui);
+		return vb2_ui_screen_back(ui);
 	}
 
-	ui->state.selected_item = RECOVERY_TO_DEV_ITEM_CONFIRM;
+	ui->state->selected_item = RECOVERY_TO_DEV_ITEM_CONFIRM;
 
 	/* Disable "Confirm" button for other physical presence types. */
 	if (!PHYSICAL_PRESENCE_KEYBOARD) {
-		ui->state.disabled_item_mask |=
+		ui->state->disabled_item_mask |=
 			1 << RECOVERY_TO_DEV_ITEM_CONFIRM;
-		ui->state.selected_item = RECOVERY_TO_DEV_ITEM_CANCEL;
+		ui->state->selected_item = RECOVERY_TO_DEV_ITEM_CANCEL;
 	}
 
 	ui->physical_presence_button_pressed = 0;
@@ -278,7 +278,7 @@
 	VB2_DEBUG("Physical presence confirmed!\n");
 
 	/* Sanity check, should never happen. */
-	if (ui->state.screen->id != VB2_SCREEN_RECOVERY_TO_DEV ||
+	if (ui->state->screen->id != VB2_SCREEN_RECOVERY_TO_DEV ||
 	    (vb2_get_sd(ui->ctx)->flags & VB2_SD_FLAG_DEV_MODE_ENABLED) ||
 	    !vb2_allow_recovery(ui->ctx)) {
 		VB2_DEBUG("ERROR: Dev transition sanity check failed\n");
@@ -306,7 +306,7 @@
 
 	if (ui->key == ' ') {
 		VB2_DEBUG("SPACE means cancel dev mode transition\n");
-		return vb2_ui_change_root(ui);
+		return vb2_ui_screen_back(ui);
 	}
 
 	/* Keyboard physical presence case covered by "Confirm" action. */
@@ -335,7 +335,7 @@
 	},
 	[RECOVERY_TO_DEV_ITEM_CANCEL] = {
 		.text = "Cancel",
-		.action = vb2_ui_change_root,
+		.action = vb2_ui_screen_back,
 	},
 	POWER_OFF_ITEM,
 };
@@ -438,27 +438,27 @@
 	enum vb2_dev_default_boot_target default_boot =
 		vb2api_get_dev_default_boot_target(ui->ctx);
 
-	/* Get me outta here! */
+	/* TODO(b/159579189): Split this case into a separate root screen */
 	if (!vb2_dev_boot_allowed(ui->ctx))
-		vb2_ui_change_screen(ui, VB2_SCREEN_DEVELOPER_TO_NORM);
+		vb2_ui_screen_change(ui, VB2_SCREEN_DEVELOPER_TO_NORM);
 
 	/* Don't show "Return to secure mode" button if GBB forces dev mode. */
 	if (vb2_get_gbb(ui->ctx)->flags & VB2_GBB_FLAG_FORCE_DEV_SWITCH_ON)
-		ui->state.disabled_item_mask |=
+		ui->state->disabled_item_mask |=
 			1 << DEVELOPER_MODE_ITEM_RETURN_TO_SECURE;
 
 	/* Don't show "Boot from external disk" button if not allowed. */
 	if (!vb2_dev_boot_external_allowed(ui->ctx))
-		ui->state.disabled_item_mask |=
+		ui->state->disabled_item_mask |=
 			1 << DEVELOPER_MODE_ITEM_BOOT_EXTERNAL;
 
 	/* Choose the default selection. */
 	switch (default_boot) {
 	case VB2_DEV_DEFAULT_BOOT_TARGET_EXTERNAL:
-		ui->state.selected_item = DEVELOPER_MODE_ITEM_BOOT_EXTERNAL;
+		ui->state->selected_item = DEVELOPER_MODE_ITEM_BOOT_EXTERNAL;
 		break;
 	default:
-		ui->state.selected_item = DEVELOPER_MODE_ITEM_BOOT_INTERNAL;
+		ui->state->selected_item = DEVELOPER_MODE_ITEM_BOOT_INTERNAL;
 		break;
 	}
 
@@ -498,18 +498,20 @@
 	if (rv == VB2_SUCCESS) {
 		return VB2_SUCCESS;
 	} else if (rv == VB2_ERROR_LK_NO_DISK_FOUND) {
-		if (ui->state.screen->id != VB2_SCREEN_DEVELOPER_BOOT_EXTERNAL) {
+		if (ui->state->screen->id !=
+		    VB2_SCREEN_DEVELOPER_BOOT_EXTERNAL) {
 			VB2_DEBUG("No external disk found\n");
 			ui->error_code = VB2_UI_ERROR_DEV_EXTERNAL_BOOT_FAILED;
 		}
-		return vb2_ui_change_screen(
+		return vb2_ui_screen_change(
 			ui, VB2_SCREEN_DEVELOPER_BOOT_EXTERNAL);
 	} else {
-		if (ui->state.screen->id != VB2_SCREEN_DEVELOPER_INVALID_DISK) {
+		if (ui->state->screen->id !=
+		    VB2_SCREEN_DEVELOPER_INVALID_DISK) {
 			VB2_DEBUG("Invalid external disk: %#x\n", rv);
 			ui->error_code = VB2_UI_ERROR_DEV_EXTERNAL_BOOT_FAILED;
 		}
-		return vb2_ui_change_screen(
+		return vb2_ui_screen_change(
 			ui, VB2_SCREEN_DEVELOPER_INVALID_DISK);
 	}
 }
@@ -519,6 +521,10 @@
 	const int use_short = vb2api_use_short_dev_screen_delay(ui->ctx);
 	uint64_t elapsed;
 
+	/* TODO(b/159579189): Split this case into a separate root screen */
+	if (!vb2_dev_boot_allowed(ui->ctx))
+		vb2_ui_screen_change(ui, VB2_SCREEN_DEVELOPER_TO_NORM);
+
 	/* Once any user interaction occurs, stop the timer. */
 	if (ui->key)
 		ui->disable_timer = 1;
@@ -600,7 +606,7 @@
 	},
 	{
 		.text = "Cancel",
-		.action = vb2_ui_change_root,
+		.action = vb2_ui_screen_back,
 	},
 	POWER_OFF_ITEM,
 };
diff --git a/firmware/2lib/include/2ui.h b/firmware/2lib/include/2ui.h
index 652362d..35dddb8 100644
--- a/firmware/2lib/include/2ui.h
+++ b/firmware/2lib/include/2ui.h
@@ -56,6 +56,7 @@
 	const struct vb2_screen_info *screen;
 	uint32_t selected_item;
 	uint32_t disabled_item_mask;
+	struct vb2_screen_state *prev;
 };
 
 enum vb2_power_button {
@@ -66,8 +67,7 @@
 
 struct vb2_ui_context {
 	struct vb2_context *ctx;
-	const struct vb2_screen_info *root_screen;
-	struct vb2_screen_state state;
+	struct vb2_screen_state *state;
 	uint32_t locale_id;
 	uint32_t key;
 	int key_trusted;
@@ -150,14 +150,14 @@
 /* Screen navigation functions */
 
 /**
- * Return back to the root screen.
+ * Return back to the previous screen.
  *
- * Return to the root screen originally provided to the ui_loop() function.
+ * If the current screen is already the root scren, the request is ignored.
  *
  * @param ui		UI context pointer
  * @return VB2_REQUEST_UI_CONTINUE, or error code on error.
  */
-vb2_error_t vb2_ui_change_root(struct vb2_ui_context *ui);
+vb2_error_t vb2_ui_screen_back(struct vb2_ui_context *ui);
 
 /**
  * Change to the given screen.
@@ -167,7 +167,7 @@
  * @param ui		UI context pointer
  * @return VB2_REQUEST_UI_CONTINUE, or error code on error.
  */
-vb2_error_t vb2_ui_change_screen(struct vb2_ui_context *ui, enum vb2_screen id);
+vb2_error_t vb2_ui_screen_change(struct vb2_ui_context *ui, enum vb2_screen id);
 
 /*****************************************************************************/
 /* UI loops */
diff --git a/tests/vb2_ui_action_tests.c b/tests/vb2_ui_action_tests.c
index a664c19..b32bafe 100644
--- a/tests/vb2_ui_action_tests.c
+++ b/tests/vb2_ui_action_tests.c
@@ -44,7 +44,6 @@
 static int mock_calls_until_shutdown;
 
 static struct vb2_ui_context mock_ui_context;
-static struct vb2_screen_state *mock_state;
 
 static struct display_call mock_displayed[64];
 static int mock_displayed_count;
@@ -70,9 +69,9 @@
 	return VB2_REQUEST_UI_CONTINUE;
 }
 
-static vb2_error_t mock_action_change_screen(struct vb2_ui_context *ui)
+static vb2_error_t mock_action_screen_change(struct vb2_ui_context *ui)
 {
-	return vb2_ui_change_screen(ui, MOCK_SCREEN_BASE);
+	return vb2_ui_screen_change(ui, MOCK_SCREEN_BASE);
 }
 
 static vb2_error_t mock_action_base(struct vb2_ui_context *ui)
@@ -307,8 +306,11 @@
 	/* Mock ui_context based on mock screens */
 	memset(&mock_ui_context, 0, sizeof(mock_ui_context));
 	mock_ui_context.ctx = ctx;
-	mock_ui_context.state.screen = &mock_screen_temp;
-	mock_state = &mock_ui_context.state;
+
+	if (!mock_ui_context.state)
+		mock_ui_context.state = malloc(sizeof(*mock_ui_context.state));
+	memset(mock_ui_context.state, 0, sizeof(*mock_ui_context.state));
+	mock_ui_context.state->screen = &mock_screen_temp;
 
 	/* For vb2ex_display_ui */
 	memset(mock_displayed, 0, sizeof(mock_displayed));
@@ -448,52 +450,57 @@
 
 	/* Valid action */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 2;
+	mock_ui_context.state->screen = &mock_screen_menu;
+	mock_ui_context.state->selected_item = 2;
 	mock_ui_context.key = VB_KEY_UP;
 	TEST_EQ(vb2_ui_menu_prev(&mock_ui_context), VB2_REQUEST_UI_CONTINUE,
 		"valid action");
-	screen_state_eq(mock_state, MOCK_SCREEN_MENU, 1, MOCK_IGNORE);
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 1,
+			MOCK_IGNORE);
 
 	/* Valid action with mask */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 2;
-	mock_state->disabled_item_mask = 0x0a;  /* 0b01010 */
+	mock_ui_context.state->screen = &mock_screen_menu;
+	mock_ui_context.state->selected_item = 2;
+	mock_ui_context.state->disabled_item_mask = 0x0a;  /* 0b01010 */
 	mock_ui_context.key = VB_KEY_UP;
 	TEST_EQ(vb2_ui_menu_prev(&mock_ui_context), VB2_REQUEST_UI_CONTINUE,
 		"valid action with mask");
-	screen_state_eq(mock_state, MOCK_SCREEN_MENU, 0, MOCK_IGNORE);
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 0,
+			MOCK_IGNORE);
 
 	/* Invalid action (blocked) */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 0;
+	mock_ui_context.state->screen = &mock_screen_menu;
+	mock_ui_context.state->selected_item = 0;
 	mock_ui_context.key = VB_KEY_UP;
 	TEST_EQ(vb2_ui_menu_prev(&mock_ui_context), VB2_REQUEST_UI_CONTINUE,
 		"invalid action (blocked)");
-	screen_state_eq(mock_state, MOCK_SCREEN_MENU, 0, MOCK_IGNORE);
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 0,
+			MOCK_IGNORE);
 
 	/* Invalid action (blocked by mask) */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 2;
-	mock_state->disabled_item_mask = 0x0b;  /* 0b01011 */
+	mock_ui_context.state->screen = &mock_screen_menu;
+	mock_ui_context.state->selected_item = 2;
+	mock_ui_context.state->disabled_item_mask = 0x0b;  /* 0b01011 */
 	mock_ui_context.key = VB_KEY_UP;
 	TEST_EQ(vb2_ui_menu_prev(&mock_ui_context), VB2_REQUEST_UI_CONTINUE,
 		"invalid action (blocked by mask)");
-	screen_state_eq(mock_state, MOCK_SCREEN_MENU, 2, MOCK_IGNORE);
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 2,
+			MOCK_IGNORE);
 
 	/* Ignore volume-up when not DETACHABLE */
 	if (!DETACHABLE) {
 		reset_common_data();
-		mock_state->screen = &mock_screen_menu;
-		mock_state->selected_item = 2;
+		mock_ui_context.state->screen = &mock_screen_menu;
+		mock_ui_context.state->selected_item = 2;
 		mock_ui_context.key = VB_BUTTON_VOL_UP_SHORT_PRESS;
 		TEST_EQ(vb2_ui_menu_prev(&mock_ui_context),
 			VB2_REQUEST_UI_CONTINUE,
 			"ignore volume-up when not DETACHABLE");
-		screen_state_eq(mock_state, MOCK_SCREEN_MENU, 2, MOCK_IGNORE);
+		screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 2,
+				MOCK_IGNORE);
 	}
 
 	VB2_DEBUG("...done.\n");
@@ -505,52 +512,57 @@
 
 	/* Valid action */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 2;
+	mock_ui_context.state->screen = &mock_screen_menu;
+	mock_ui_context.state->selected_item = 2;
 	mock_ui_context.key = VB_KEY_DOWN;
 	TEST_EQ(vb2_ui_menu_next(&mock_ui_context), VB2_REQUEST_UI_CONTINUE,
 		"valid action");
-	screen_state_eq(mock_state, MOCK_SCREEN_MENU, 3, MOCK_IGNORE);
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 3,
+			MOCK_IGNORE);
 
 	/* Valid action with mask */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 2;
-	mock_state->disabled_item_mask = 0x0a;  /* 0b01010 */
+	mock_ui_context.state->screen = &mock_screen_menu;
+	mock_ui_context.state->selected_item = 2;
+	mock_ui_context.state->disabled_item_mask = 0x0a;  /* 0b01010 */
 	mock_ui_context.key = VB_KEY_DOWN;
 	TEST_EQ(vb2_ui_menu_next(&mock_ui_context), VB2_REQUEST_UI_CONTINUE,
 		"valid action with mask");
-	screen_state_eq(mock_state, MOCK_SCREEN_MENU, 4, MOCK_IGNORE);
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 4,
+			MOCK_IGNORE);
 
 	/* Invalid action (blocked) */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 4;
+	mock_ui_context.state->screen = &mock_screen_menu;
+	mock_ui_context.state->selected_item = 4;
 	mock_ui_context.key = VB_KEY_DOWN;
 	TEST_EQ(vb2_ui_menu_next(&mock_ui_context), VB2_REQUEST_UI_CONTINUE,
 		"invalid action (blocked)");
-	screen_state_eq(mock_state, MOCK_SCREEN_MENU, 4, MOCK_IGNORE);
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 4,
+			MOCK_IGNORE);
 
 	/* Invalid action (blocked by mask) */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 2;
-	mock_state->disabled_item_mask = 0x1a;  /* 0b11010 */
+	mock_ui_context.state->screen = &mock_screen_menu;
+	mock_ui_context.state->selected_item = 2;
+	mock_ui_context.state->disabled_item_mask = 0x1a;  /* 0b11010 */
 	mock_ui_context.key = VB_KEY_DOWN;
 	TEST_EQ(vb2_ui_menu_next(&mock_ui_context), VB2_REQUEST_UI_CONTINUE,
 		"invalid action (blocked by mask)");
-	screen_state_eq(mock_state, MOCK_SCREEN_MENU, 2, MOCK_IGNORE);
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 2,
+			MOCK_IGNORE);
 
 	/* Ignore volume-down when not DETACHABLE */
 	if (!DETACHABLE) {
 		reset_common_data();
-		mock_state->screen = &mock_screen_menu;
-		mock_state->selected_item = 2;
+		mock_ui_context.state->screen = &mock_screen_menu;
+		mock_ui_context.state->selected_item = 2;
 		mock_ui_context.key = VB_BUTTON_VOL_DOWN_SHORT_PRESS;
 		TEST_EQ(vb2_ui_menu_next(&mock_ui_context),
 			VB2_REQUEST_UI_CONTINUE,
 			"ignore volume-down when not DETACHABLE");
-		screen_state_eq(mock_state, MOCK_SCREEN_MENU, 2, MOCK_IGNORE);
+		screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 2,
+				MOCK_IGNORE);
 	}
 
 	VB2_DEBUG("...done.\n");
@@ -562,26 +574,28 @@
 
 	/* select action with no item screen */
 	reset_common_data();
-	mock_state->screen = &mock_screen_base;
+	mock_ui_context.state->screen = &mock_screen_base;
 	mock_ui_context.key = VB_KEY_ENTER;
 	TEST_EQ(vb2_ui_menu_select(&mock_ui_context),
 		VB2_REQUEST_UI_CONTINUE,
 		"vb2_ui_menu_select with no item screen");
-	screen_state_eq(mock_state, MOCK_SCREEN_BASE, 0, MOCK_IGNORE);
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_BASE, 0,
+			MOCK_IGNORE);
 
 	/* Try to select an item with a target (item 2) */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 2;
+	mock_ui_context.state->screen = &mock_screen_menu;
+	mock_ui_context.state->selected_item = 2;
 	mock_ui_context.key = VB_KEY_ENTER;
 	TEST_EQ(vb2_ui_menu_select(&mock_ui_context),
 		VB2_REQUEST_UI_CONTINUE, "select an item with a target");
-	screen_state_eq(mock_state, MOCK_SCREEN_TARGET2, 0, MOCK_IGNORE);
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_TARGET2, 0,
+			MOCK_IGNORE);
 
 	/* Try to select an item with an action (item 3) */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 3;
+	mock_ui_context.state->screen = &mock_screen_menu;
+	mock_ui_context.state->selected_item = 3;
 	mock_ui_context.key = VB_KEY_ENTER;
 	TEST_EQ(vb2_ui_menu_select(&mock_ui_context),
 		VB2_SUCCESS, "select an item with an action");
@@ -589,24 +603,26 @@
 
 	/* Try to select an item with neither targets nor actions (item 4) */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 4;
+	mock_ui_context.state->screen = &mock_screen_menu;
+	mock_ui_context.state->selected_item = 4;
 	mock_ui_context.key = VB_KEY_ENTER;
 	TEST_EQ(vb2_ui_menu_select(&mock_ui_context),
 		VB2_REQUEST_UI_CONTINUE,
 		"select an item with neither targets nor actions");
-	screen_state_eq(mock_state, MOCK_SCREEN_MENU, 4, MOCK_IGNORE);
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 4,
+			MOCK_IGNORE);
 
 	/* Ignore power button short press when not DETACHABLE */
 	if (!DETACHABLE) {
 		reset_common_data();
-		mock_state->screen = &mock_screen_menu;
-		mock_state->selected_item = 1;
+		mock_ui_context.state->screen = &mock_screen_menu;
+		mock_ui_context.state->selected_item = 1;
 		mock_ui_context.key = VB_BUTTON_POWER_SHORT_PRESS;
 		TEST_EQ(vb2_ui_menu_select(&mock_ui_context),
 			VB2_REQUEST_UI_CONTINUE,
 			"ignore power button short press when not DETACHABLE");
-		screen_state_eq(mock_state, MOCK_SCREEN_MENU, 1, MOCK_IGNORE);
+		screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, 1,
+				MOCK_IGNORE);
 	}
 
 	VB2_DEBUG("...done.\n");
@@ -628,7 +644,7 @@
 	set_mock_vbtlk(VB2_ERROR_LK_NO_DISK_FOUND, VB_DISK_FLAG_REMOVABLE);
 	TEST_EQ(manual_recovery_action(&mock_ui_context),
 		VB2_REQUEST_UI_CONTINUE, "NO_DISK_FOUND");
-	screen_state_eq(mock_state, VB2_SCREEN_RECOVERY_SELECT,
+	screen_state_eq(mock_ui_context.state, VB2_SCREEN_RECOVERY_SELECT,
 			MOCK_IGNORE, MOCK_IGNORE);
 
 	/* NO_DISK_FOUND -> INVALID_KERNEL -> SUCCESS */
@@ -643,7 +659,7 @@
 	set_mock_vbtlk(VB2_SUCCESS, VB_DISK_FLAG_REMOVABLE);
 	TEST_EQ(manual_recovery_action(&mock_ui_context),
 		VB2_SUCCESS, "SUCCESS");
-	screen_state_eq(mock_state, VB2_SCREEN_RECOVERY_INVALID,
+	screen_state_eq(mock_ui_context.state, VB2_SCREEN_RECOVERY_INVALID,
 			MOCK_IGNORE, MOCK_IGNORE);
 
 	/* INVALID_KERNEL */
@@ -652,7 +668,7 @@
 		       VB_DISK_FLAG_REMOVABLE);
 	TEST_EQ(manual_recovery_action(&mock_ui_context),
 		VB2_REQUEST_UI_CONTINUE, "INVALID_KERNEL");
-	screen_state_eq(mock_state, VB2_SCREEN_RECOVERY_INVALID,
+	screen_state_eq(mock_ui_context.state, VB2_SCREEN_RECOVERY_INVALID,
 			MOCK_IGNORE, MOCK_IGNORE);
 
 	/* INVALID_KERNEL -> NO_DISK_FOUND -> SUCCESS */
@@ -667,7 +683,7 @@
 	set_mock_vbtlk(VB2_SUCCESS, VB_DISK_FLAG_REMOVABLE);
 	TEST_EQ(manual_recovery_action(&mock_ui_context),
 		VB2_SUCCESS, "SUCCESS");
-	screen_state_eq(mock_state, VB2_SCREEN_RECOVERY_SELECT,
+	screen_state_eq(mock_ui_context.state, VB2_SCREEN_RECOVERY_SELECT,
 			MOCK_IGNORE, MOCK_IGNORE);
 
 	VB2_DEBUG("...done.\n");
@@ -717,11 +733,11 @@
 
 	/* Global action can change screen */
 	reset_common_data();
-	TEST_EQ(ui_loop(ctx, VB2_SCREEN_BLANK, mock_action_change_screen),
+	TEST_EQ(ui_loop(ctx, VB2_SCREEN_BLANK, mock_action_screen_change),
 		VB2_REQUEST_SHUTDOWN, "global action can change screen");
 	DISPLAYED_PASS();
-	DISPLAYED_EQ("change to mock_screen_base", MOCK_IGNORE, MOCK_IGNORE,
-		     MOCK_IGNORE, MOCK_IGNORE);
+	DISPLAYED_EQ("change to mock_screen_base", MOCK_SCREEN_BASE,
+		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	DISPLAYED_NO_EXTRA();
 
 	/*
diff --git a/tests/vb2_ui_tests.c b/tests/vb2_ui_tests.c
index a56e0a4..47c7046 100644
--- a/tests/vb2_ui_tests.c
+++ b/tests/vb2_ui_tests.c
@@ -42,7 +42,7 @@
 static struct vb2_gbb_header gbb;
 
 static struct vb2_ui_context mock_ui_context;
-static struct vb2_screen_state *mock_state;
+static struct vb2_screen_state mock_state;
 
 static struct display_call mock_displayed[64];
 static int mock_displayed_count;
@@ -248,7 +248,7 @@
 	/* Mock ui_context based on real screens */
 	memset(&mock_ui_context, 0, sizeof(mock_ui_context));
 	mock_ui_context.ctx = ctx;
-	mock_state = &mock_ui_context.state;
+	mock_ui_context.state = &mock_state;
 
 	/* For vb2ex_display_ui */
 	memset(mock_displayed, 0, sizeof(mock_displayed));
@@ -653,6 +653,9 @@
 		"if dev mode is disabled, goes to to_norm screen repeatedly");
 	DISPLAYED_EQ("to_norm", VB2_SCREEN_DEVELOPER_TO_NORM,
 		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
+	DISPLAYED_PASS();
+	DISPLAYED_EQ("to_norm", VB2_SCREEN_DEVELOPER_TO_NORM,
+		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	DISPLAYED_NO_EXTRA();
 
 	/* Select to_norm in dev menu and confirm */
@@ -1040,10 +1043,11 @@
 	add_mock_keypress(VB_KEY_ENTER);
 	/* #1: Return to secure mode */
 	add_mock_keypress(VB_KEY_ESC);
-	add_mock_keypress(VB_KEY_UP);
+	add_mock_keypress(VB_KEY_DOWN);
 	add_mock_keypress(VB_KEY_ENTER);
 	/* #2: Boot internal */
 	add_mock_keypress(VB_KEY_ESC);
+	add_mock_keypress(VB_KEY_DOWN);
 	add_mock_keypress(VB_KEY_ENTER);
 	TEST_EQ(vb2_developer_menu(ctx), VB2_SUCCESS,
 		"dev mode screen");
@@ -1061,6 +1065,7 @@
 	DISPLAYED_EQ("#1: return to secure mode", VB2_SCREEN_DEVELOPER_TO_NORM,
 		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	/* #2: Boot internal */
+	DISPLAYED_PASS();
 	DISPLAYED_EQ("dev mode", VB2_SCREEN_DEVELOPER_MODE,
 		     MOCK_IGNORE, 2, MOCK_IGNORE);
 	VB2_DEBUG("#2: boot internal (no extra screen)\n");
@@ -1088,7 +1093,7 @@
 	/* End of menu */
 	add_mock_keypress(VB_KEY_ESC);
 	add_mock_keypress(VB_KEY_DOWN);
-	add_mock_keypress(VB_KEY_DOWN);
+	add_mock_keypress(VB_KEY_DOWN);  /* Blocked */
 	TEST_EQ(vb2_developer_menu(ctx), VB2_REQUEST_SHUTDOWN,
 		"dev mode screen");
 	/* #4: Advanced options */
@@ -1100,9 +1105,8 @@
 		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	/* End of menu */
 	DISPLAYED_PASS();
-	DISPLAYED_PASS();
 	DISPLAYED_EQ("end of menu", VB2_SCREEN_DEVELOPER_MODE,
-		     MOCK_IGNORE, 4, MOCK_IGNORE);
+		     MOCK_IGNORE, 5, MOCK_IGNORE);
 	DISPLAYED_NO_EXTRA();
 
 	/* Advanced options screen */
@@ -1117,12 +1121,8 @@
 	/* #2: Back */
 	add_mock_keypress(VB_KEY_ESC);
 	add_mock_keypress(VB_KEY_DOWN);
-	add_mock_keypress(VB_KEY_DOWN);
-	add_mock_keypress(VB_KEY_ENTER);
 	add_mock_keypress(VB_KEY_ENTER);
 	/* End of menu */
-	add_mock_keypress(VB_KEY_DOWN);
-	add_mock_keypress(VB_KEY_DOWN);
 	add_mock_keypress(VB_KEY_ENTER);
 	extend_calls_until_shutdown();
 	TEST_EQ(vb2_developer_menu(ctx), VB2_REQUEST_SHUTDOWN,
@@ -1130,8 +1130,8 @@
 	DISPLAYED_PASS();
 	DISPLAYED_PASS();
 	DISPLAYED_PASS();
-	/* #0: Language menu */
 	DISPLAYED_PASS();
+	/* #0: Language menu */
 	DISPLAYED_EQ("advanced options", VB2_SCREEN_ADVANCED_OPTIONS,
 		     MOCK_IGNORE, 0, 0x2);
 	DISPLAYED_EQ("#0: language menu", VB2_SCREEN_LANGUAGE_SELECT,
@@ -1139,15 +1139,11 @@
 	/* #1: (Disabled) */
 	/* #2: Back */
 	DISPLAYED_PASS();
-	DISPLAYED_PASS();
-	DISPLAYED_PASS();
 	DISPLAYED_EQ("advanced options", VB2_SCREEN_ADVANCED_OPTIONS,
 		     MOCK_IGNORE, 2, 0x2);
 	DISPLAYED_EQ("#2: back", VB2_SCREEN_DEVELOPER_MODE,
 		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	/* End of menu */
-	DISPLAYED_PASS();
-	DISPLAYED_PASS();
 	DISPLAYED_EQ("end of menu", VB2_SCREEN_ADVANCED_OPTIONS,
 		     MOCK_IGNORE, 2, MOCK_IGNORE);
 	DISPLAYED_NO_EXTRA();
@@ -1164,6 +1160,7 @@
 	add_mock_keypress(VB_KEY_ENTER);
 	/* #1: Advanced options */
 	add_mock_keypress(VB_KEY_ESC);
+	add_mock_keypress(VB_KEY_DOWN);
 	add_mock_keypress(VB_KEY_ENTER);
 	/* End of menu */
 	add_mock_keypress(VB_KEY_ESC);
@@ -1177,6 +1174,7 @@
 	DISPLAYED_EQ("#0: language menu", VB2_SCREEN_LANGUAGE_SELECT,
 		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	/* #1: Advanced options */
+	DISPLAYED_PASS();
 	DISPLAYED_EQ("broken screen", VB2_SCREEN_RECOVERY_BROKEN,
 		     MOCK_IGNORE, 1, 0x0);
 	DISPLAYED_EQ("#1: advanced options", VB2_SCREEN_ADVANCED_OPTIONS,
@@ -1195,7 +1193,7 @@
 	/* #1: (Disabled) */
 	/* #2: Back */
 	add_mock_keypress(VB_KEY_ESC);
-	add_mock_keypress(VB_KEY_ENTER);
+	add_mock_keypress(VB_KEY_DOWN);
 	add_mock_keypress(VB_KEY_ENTER);
 	/* End of menu */
 	add_mock_keypress(VB_KEY_ENTER);
@@ -1233,6 +1231,7 @@
 	add_mock_keypress(VB_KEY_ENTER);
 	/* #1: Phone recovery */
 	add_mock_keypress(VB_KEY_ESC);
+	add_mock_keypress(VB_KEY_DOWN);
 	add_mock_keypress(VB_KEY_ENTER);
 	/* #2: External disk recovery */
 	add_mock_keypress(VB_KEY_ESC);
@@ -1241,12 +1240,11 @@
 	/* #3: Advanced options */
 	add_mock_keypress(VB_KEY_ESC);
 	add_mock_keypress(VB_KEY_DOWN);
-	add_mock_keypress(VB_KEY_DOWN);
 	add_mock_keypress(VB_KEY_ENTER);
 	/* End of menu */
 	add_mock_keypress(VB_KEY_ESC);
 	add_mock_keypress(VB_KEY_DOWN);
-	add_mock_keypress(VB_KEY_DOWN);
+	add_mock_keypress(VB_KEY_DOWN);  /* Blocked */
 	extend_calls_until_shutdown();
 	TEST_EQ(vb2_manual_recovery_menu(ctx), VB2_REQUEST_SHUTDOWN,
 		"recovery select screen");
@@ -1257,6 +1255,7 @@
 	DISPLAYED_EQ("#0: language menu", VB2_SCREEN_LANGUAGE_SELECT,
 		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	/* #1: Phone recovery */
+	DISPLAYED_PASS();
 	DISPLAYED_EQ("recovery select", VB2_SCREEN_RECOVERY_SELECT,
 		     MOCK_IGNORE, 1, 0x0);
 	DISPLAYED_EQ("#1: phone recovery", VB2_SCREEN_RECOVERY_PHONE_STEP1,
@@ -1269,16 +1268,14 @@
 		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	/* #3: Advanced options */
 	DISPLAYED_PASS();
-	DISPLAYED_PASS();
 	DISPLAYED_EQ("recovery select", VB2_SCREEN_RECOVERY_SELECT,
 		     MOCK_IGNORE, 3, 0x0);
 	DISPLAYED_EQ("#3: advanced options", VB2_SCREEN_ADVANCED_OPTIONS,
 		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	/* End of menu */
 	DISPLAYED_PASS();
-	DISPLAYED_PASS();
 	DISPLAYED_EQ("end of menu", VB2_SCREEN_RECOVERY_SELECT,
-		     MOCK_IGNORE, 3, MOCK_IGNORE);
+		     MOCK_IGNORE, 4, MOCK_IGNORE);
 	DISPLAYED_NO_EXTRA();
 
 	/* Advanced options screen */
@@ -1292,19 +1289,12 @@
 	/* #1: Enable dev mode */
 	add_mock_keypress(VB_KEY_ESC);
 	add_mock_keypress(VB_KEY_DOWN);
-	add_mock_keypress(VB_KEY_DOWN);
-	add_mock_keypress(VB_KEY_ENTER);
 	add_mock_keypress(VB_KEY_ENTER);
 	/* #2: Back */
 	add_mock_keypress(VB_KEY_ESC);
 	add_mock_keypress(VB_KEY_DOWN);
-	add_mock_keypress(VB_KEY_DOWN);
-	add_mock_keypress(VB_KEY_ENTER);
-	add_mock_keypress(VB_KEY_DOWN);
 	add_mock_keypress(VB_KEY_ENTER);
 	/* End of menu */
-	add_mock_keypress(VB_KEY_DOWN);
-	add_mock_keypress(VB_KEY_DOWN);
 	add_mock_keypress(VB_KEY_ENTER);
 	add_mock_keypress(VB_KEY_DOWN);
 	extend_calls_until_shutdown();
@@ -1321,25 +1311,18 @@
 		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	/* #1: Enable dev mode */
 	DISPLAYED_PASS();
-	DISPLAYED_PASS();
-	DISPLAYED_PASS();
 	DISPLAYED_EQ("advanced options", VB2_SCREEN_ADVANCED_OPTIONS,
 		     MOCK_IGNORE, 1, 0x0);
 	DISPLAYED_EQ("#1: enable dev mode", VB2_SCREEN_RECOVERY_TO_DEV,
 		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	/* #2: Back */
 	DISPLAYED_PASS();
-	DISPLAYED_PASS();
-	DISPLAYED_PASS();
-	DISPLAYED_PASS();
 	DISPLAYED_EQ("advanced options", VB2_SCREEN_ADVANCED_OPTIONS,
 		     MOCK_IGNORE, 2, 0x0);
 	DISPLAYED_EQ("#2: back", VB2_SCREEN_RECOVERY_SELECT,
 		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
 	/* End of menu */
 	DISPLAYED_PASS();
-	DISPLAYED_PASS();
-	DISPLAYED_PASS();
 	DISPLAYED_EQ("end of menu", VB2_SCREEN_ADVANCED_OPTIONS,
 		     MOCK_IGNORE, 2, 0x0);
 	DISPLAYED_NO_EXTRA();
diff --git a/tests/vb2_ui_utility_tests.c b/tests/vb2_ui_utility_tests.c
index 533aa51..dbdebd3 100644
--- a/tests/vb2_ui_utility_tests.c
+++ b/tests/vb2_ui_utility_tests.c
@@ -33,7 +33,6 @@
 static int mock_shutdown_request;
 
 static struct vb2_ui_context mock_ui_context;
-static struct vb2_screen_state *mock_state;
 
 /* Mock actions */
 static uint32_t mock_action_called;
@@ -70,7 +69,7 @@
 		.text = "option 4",
 	},
 };
-const struct vb2_screen_info mock_screen_menu = {
+struct vb2_screen_info mock_screen_menu = {
 	.id = MOCK_SCREEN_MENU,
 	.name = "mock_screen_menu: screen with 5 options",
 	.menu = {
@@ -78,7 +77,7 @@
 		.items = mock_screen_menu_items,
 	},
 };
-const struct vb2_screen_info mock_screen_root = {
+struct vb2_screen_info mock_screen_root = {
 	.id = MOCK_SCREEN_ROOT,
 	.name = "mock_screen_root",
 };
@@ -121,10 +120,19 @@
 	/* Mock ui_context based on mock screens */
 	memset(&mock_ui_context, 0, sizeof(mock_ui_context));
 	mock_ui_context.power_button = VB2_POWER_BUTTON_HELD_SINCE_BOOT;
-	mock_state = &mock_ui_context.state;
 
 	/* For mock actions */
 	mock_action_called = 0;
+
+	/* Reset init and action functions */
+	mock_screen_blank.init = NULL;
+	mock_screen_blank.action = NULL;
+	mock_screen_base.init = NULL;
+	mock_screen_base.action = NULL;
+	mock_screen_menu.init = NULL;
+	mock_screen_menu.action = NULL;
+	mock_screen_root.init = NULL;
+	mock_screen_root.action = NULL;
 }
 
 /* Mock functions */
@@ -260,57 +268,56 @@
 	VB2_DEBUG("...done.\n");
 }
 
-static void vb2_ui_change_root_tests(void)
+static void screen_stack_tests(void)
 {
-	VB2_DEBUG("Testing vb2_ui_change_root...\n");
-
-	/* Back to root screen */
-	reset_common_data();
-	mock_ui_context.root_screen = &mock_screen_root;
-	mock_ui_context.key = VB_KEY_ESC;
-	TEST_EQ(vb2_ui_change_root(&mock_ui_context), VB2_REQUEST_UI_CONTINUE,
-		"back to root screen");
-	screen_state_eq(mock_state, MOCK_SCREEN_ROOT, MOCK_IGNORE, MOCK_IGNORE);
-
-	VB2_DEBUG("...done.\n");
-}
-
-static void change_screen_tests(void)
-{
-	VB2_DEBUG("Testing change_screen...\n");
-
-	/* Changing screen will clear screen state */
-	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	mock_state->selected_item = 2;
-	mock_state->disabled_item_mask = 0x10;
-	TEST_EQ(vb2_ui_change_screen(&mock_ui_context, MOCK_SCREEN_BASE),
-		VB2_REQUEST_UI_CONTINUE,
-		"change_screen will clear screen state");
-	screen_state_eq(mock_state, MOCK_SCREEN_BASE, 0, 0);
+	VB2_DEBUG("Testing screen stack functionality...\n");
 
 	/* Change to screen which does not exist */
 	reset_common_data();
-	mock_state->screen = &mock_screen_menu;
-	TEST_EQ(vb2_ui_change_screen(&mock_ui_context, MOCK_NO_SCREEN),
+	TEST_EQ(vb2_ui_screen_change(&mock_ui_context, MOCK_NO_SCREEN),
 		VB2_REQUEST_UI_CONTINUE,
 		"change to screen which does not exist");
-	screen_state_eq(mock_state, MOCK_SCREEN_MENU, MOCK_IGNORE, MOCK_IGNORE);
+	TEST_PTR_EQ(mock_ui_context.state, NULL, "  stack is empty");
 
-	/* Change to screen with init */
+	/* Screen back with empty stack */
+	reset_common_data();
+	TEST_EQ(vb2_ui_screen_back(&mock_ui_context),
+		VB2_REQUEST_UI_CONTINUE,
+		"screen back with empty stack");
+	TEST_PTR_EQ(mock_ui_context.state, NULL, "  stack is empty");
+
+	/* Back to previous screen, restoring the state */
 	reset_common_data();
 	mock_screen_base.init = mock_action_base;
-	TEST_EQ(vb2_ui_change_screen(&mock_ui_context, MOCK_SCREEN_BASE),
-		VB2_SUCCESS, "change to screen with init");
+	vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_ROOT);
+	vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_BASE);
+	mock_ui_context.state->selected_item = 2;
+	mock_ui_context.state->disabled_item_mask = 0x10;
+	vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_MENU);
+	TEST_EQ(vb2_ui_screen_back(&mock_ui_context), VB2_REQUEST_UI_CONTINUE,
+		"back to previous screen");
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_BASE, 2, 0x10);
+	TEST_EQ(mock_action_called, 1, "  action called once");
+
+	/* Change to target screen already in stack, restoring the state */
+	reset_common_data();
+	mock_screen_base.init = mock_action_base;
+	vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_ROOT);
+	vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_BASE);
+	mock_ui_context.state->selected_item = 2;
+	mock_ui_context.state->disabled_item_mask = 0x10;
+	vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_MENU);
+	TEST_EQ(vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_BASE),
+		VB2_REQUEST_UI_CONTINUE, "change to target in stack");
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_BASE, 2, 0x10);
 	TEST_EQ(mock_action_called, 1, "  action called once");
 
 	/* Change to screen without init; using default init() */
 	reset_common_data();
-	mock_state->screen = &mock_screen_base;
-	TEST_EQ(vb2_ui_change_screen(&mock_ui_context, MOCK_SCREEN_MENU),
+	TEST_EQ(vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_MENU),
 		VB2_REQUEST_UI_CONTINUE,
 		"change to screen with language selection");
-	screen_state_eq(mock_state, MOCK_SCREEN_MENU,
+	screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU,
 			1,	/* Since index 0 is the language selection */
 			0);
 
@@ -350,8 +357,7 @@
 int main(void)
 {
 	check_shutdown_request_tests();
-	vb2_ui_change_root_tests();
-	change_screen_tests();
+	screen_stack_tests();
 	get_language_menu_tests();
 
 	return gTestSuccess ? 0 : 255;