0

Reland "[CrOS] Introduce Kiosk app instruction bubble for Kiosk SKU"

This is a reland of commit 47d0b95168

Revert CL is crrev.com/c/3586533.

The test, KioskUpdateTest.UpdateAppWithSharedModuleRemoveAllSecondaryApps,
was flaky 4 time in the past 2 weeks before the initial CL.

Original change's description:
> [CrOS] Introduce Kiosk app instruction bubble for Kiosk SKU
>
> For device with kiosk SKU, a kiosk app instruction bubble would be shown
> if kiosk apps are set up. (go/kiosk-sku-prd)
>
> Also, if the device is with kiosk SKU, addUser and browseAsGuest buttons
> on the shelf would not be visible, nor would the GAIA dialog.
>
> Implementation screenshots
> (https://screenshot.googleplex.com/C6a8BQdzDyojj4H)
>
> Bug: 1307303
> Change-Id: I0d67aefeebf3b4199a877ff27f8422357018f9a3
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3569068
> Reviewed-by: Denis Kuznetsov <antrim@chromium.org>
> Reviewed-by: Mike Wasserman <msw@chromium.org>
> Commit-Queue: Sherri Lin <sherrilin@google.com>
> Cr-Commit-Position: refs/heads/main@{#992765}

Bug: 1307303
Change-Id: I007c76406270c07b5f969fd22609c7867264c49a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3592994
Reviewed-by: Denis Kuznetsov <antrim@chromium.org>
Reviewed-by: Mike Wasserman <msw@chromium.org>
Commit-Queue: Sherri Lin <sherrilin@google.com>
Cr-Commit-Position: refs/heads/main@{#994847}
This commit is contained in:
Sherri Lin
2022-04-21 19:36:00 +00:00
committed by Chromium LUCI CQ
parent de1abc38f6
commit c81b120168
9 changed files with 293 additions and 3 deletions

@ -847,6 +847,8 @@ component("ash") {
"shelf/hotseat_widget.h",
"shelf/in_app_to_home_nudge_controller.cc",
"shelf/in_app_to_home_nudge_controller.h",
"shelf/kiosk_app_instruction_bubble.cc",
"shelf/kiosk_app_instruction_bubble.h",
"shelf/launcher_nudge_controller.cc",
"shelf/launcher_nudge_controller.h",
"shelf/login_shelf_gesture_controller.cc",

@ -4188,6 +4188,9 @@ Here are some things you can try to get started.
<message name="IDS_SHELF_ITEM_PAUSED_APP" desc="Accessibility announcement notifying users that the currently focused shelf app is paused.">
Paused
</message>
<message name="IDS_SHELF_KIOSK_APP_INSTRUCTION" desc="Instructions to show users where to start using kiosk apps.">
Open an app to get started
</message>
<message name="IDS_APP_ACCESSIBILITY_BLOCKED_INSTALLED_APP_ANNOUNCEMENT" desc="Accessibility text to specify a search result is a blocked installed app.">
<ph name="APP_NAME">$1<ex>Files</ex></ph>, Installed App, Blocked
</message>

@ -0,0 +1 @@
e87550a9515726ae613912ad832b9de0ad3dabb0

@ -39,6 +39,7 @@
#include "ash/public/cpp/smartlock_state.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/login_shelf_view.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
@ -937,6 +938,11 @@ void LockContentsView::OnUsersChanged(const std::vector<LoginUserInfo>& users) {
main_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
// TODO(crbug.com/1307303): Determine if this is a kiosk license device.
bool kiosk_license_mode = false;
if (kiosk_license_mode)
return;
// If there are no users, show GAIA signin if login.
if (users.empty() && screen_type_ == LockScreen::ScreenType::kLogin) {
// Create a UI that will be shown on camera usage timeout after the

@ -0,0 +1,102 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/shelf/kiosk_app_instruction_bubble.h"
#include "ash/constants/ash_features.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/tray/tray_utils.h"
#include "base/callback_helpers.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/layout_provider.h"
namespace ash {
namespace {
// The preferred width of the kiosk app instruction bubble.
constexpr int kBubblePreferredWidth = 150;
views::BubbleBorder::Arrow GetArrow(ShelfAlignment alignment) {
switch (alignment) {
case ShelfAlignment::kBottom:
case ShelfAlignment::kBottomLocked:
return views::BubbleBorder::BOTTOM_LEFT;
case ShelfAlignment::kLeft:
return views::BubbleBorder::LEFT_TOP;
case ShelfAlignment::kRight:
return views::BubbleBorder::RIGHT_TOP;
}
return views::BubbleBorder::Arrow::NONE;
}
gfx::Insets GetBubbleInsets() {
gfx::Insets insets = GetTrayBubbleInsets();
insets.set_bottom(views::LayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_RELATED_LABEL_HORIZONTAL));
return insets;
}
} // namespace
KioskAppInstructionBubble::KioskAppInstructionBubble(views::View* anchor,
ShelfAlignment alignment,
SkColor background_color)
: ShelfBubble(anchor, alignment, background_color) {
set_close_on_deactivate(false);
const int bubble_margin = views::LayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_CONTROL);
set_margins(gfx::Insets(bubble_margin));
SetButtons(ui::DIALOG_BUTTON_NONE);
SetArrow(GetArrow(alignment));
SetLayoutManager(std::make_unique<views::FillLayout>());
// Set up the title view.
title_ = AddChildView(std::make_unique<views::Label>());
TrayPopupUtils::SetLabelFontList(title_,
TrayPopupUtils::FontStyle::kSmallTitle);
title_->SetText(l10n_util::GetStringUTF16(IDS_SHELF_KIOSK_APP_INSTRUCTION));
title_->SetMultiLine(true);
CreateBubble();
auto bubble_border =
std::make_unique<views::BubbleBorder>(arrow(), GetShadow(), color());
bubble_border->set_insets(GetBubbleInsets());
bubble_border->set_use_theme_background_color(true);
bubble_border->SetCornerRadius(
views::LayoutProvider::Get()->GetCornerRadiusMetric(
views::Emphasis::kHigh));
GetBubbleFrameView()->SetBubbleBorder(std::move(bubble_border));
}
KioskAppInstructionBubble::~KioskAppInstructionBubble() = default;
void KioskAppInstructionBubble::OnThemeChanged() {
views::View::OnThemeChanged();
SkColor label_color = AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary);
title_->SetEnabledColor(label_color);
}
gfx::Size KioskAppInstructionBubble::CalculatePreferredSize() const {
return gfx::Size(kBubblePreferredWidth,
GetHeightForWidth(kBubblePreferredWidth));
}
bool KioskAppInstructionBubble::ShouldCloseOnPressDown() {
return false;
}
bool KioskAppInstructionBubble::ShouldCloseOnMouseExit() {
return false;
}
} // namespace ash

@ -0,0 +1,47 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_SHELF_KIOSK_APP_INSTRUCTION_BUBBLE_H_
#define ASH_SHELF_KIOSK_APP_INSTRUCTION_BUBBLE_H_
#include "ash/ash_export.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/shelf/shelf_bubble.h"
#include "third_party/skia/include/core/SkColor.h"
namespace views {
class Label;
class View;
} // namespace views
namespace ash {
// A shelf bubble instructing kiosk users to interact with the kiosk app menu
// button.
class ASH_EXPORT KioskAppInstructionBubble : public ShelfBubble {
public:
KioskAppInstructionBubble(views::View* anchor,
ShelfAlignment alignment,
SkColor background_color);
KioskAppInstructionBubble(const KioskAppInstructionBubble&) = delete;
KioskAppInstructionBubble& operator=(const KioskAppInstructionBubble&) =
delete;
~KioskAppInstructionBubble() override;
private:
// views::View:
void OnThemeChanged() override;
gfx::Size CalculatePreferredSize() const override;
// ShelfBubble:
bool ShouldCloseOnPressDown() override;
bool ShouldCloseOnMouseExit() override;
views::Label* title_ = nullptr;
};
} // namespace ash
#endif // ASH_SHELF_KIOSK_APP_INSTRUCTION_BUBBLE_H_

@ -23,6 +23,7 @@
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/kiosk_app_instruction_bubble.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_shutdown_confirmation_bubble.h"
#include "ash/shelf/shelf_widget.h"
@ -716,11 +717,23 @@ void LoginShelfView::InstallTestUiUpdateDelegate(
test_ui_update_delegate_ = std::move(delegate);
}
void LoginShelfView::OnKioskMenuShown(
const base::RepeatingClosure& on_kiosk_menu_shown) {
if (kiosk_instruction_bubble_)
kiosk_instruction_bubble_->GetWidget()->Hide();
on_kiosk_menu_shown.Run();
}
void LoginShelfView::SetKioskApps(
const std::vector<KioskAppMenuEntry>& kiosk_apps,
const base::RepeatingCallback<void(const KioskAppMenuEntry&)>& launch_app,
const base::RepeatingCallback<void()>& on_show_menu) {
kiosk_apps_button_->SetApps(kiosk_apps, launch_app, on_show_menu);
const auto show_kiosk_menu_callback =
base::BindRepeating(&LoginShelfView::OnKioskMenuShown,
weak_ptr_factory_.GetWeakPtr(), on_show_menu);
kiosk_apps_button_->SetApps(kiosk_apps, launch_app, show_kiosk_menu_callback);
UpdateUi();
}
@ -797,6 +810,10 @@ void LoginShelfView::SetButtonOpacity(float target_opacity) {
ShelfConfig::Get()->DimAnimationTween());
}
void LoginShelfView::SetKioskLicenseModeForTesting(bool is_kiosk_license_mode) {
kiosk_license_mode_ = is_kiosk_license_mode;
}
std::unique_ptr<ScopedGuestButtonBlocker>
LoginShelfView::GetScopedGuestButtonBlocker() {
return std::make_unique<LoginShelfView::ScopedGuestButtonBlockerImpl>(
@ -836,6 +853,11 @@ void LoginShelfView::HandleLocaleChange() {
}
}
KioskAppInstructionBubble*
LoginShelfView::GetKioskInstructionBubbleForTesting() {
return kiosk_instruction_bubble_;
}
ShelfShutdownConfirmationBubble*
LoginShelfView::GetShutdownConfirmationBubbleForTesting() {
return test_shutdown_confirmation_bubble_;
@ -901,9 +923,26 @@ void LoginShelfView::UpdateUi() {
// Show add user button when it's in login screen and Oobe UI dialog is not
// visible. The button should not appear if the device is not connected to a
// network.
GetViewByID(kAddUser)->SetVisible(!dialog_visible && is_login_primary);
GetViewByID(kAddUser)->SetVisible(!kiosk_license_mode_ && !dialog_visible &&
is_login_primary);
kiosk_apps_button_->SetVisible(kiosk_apps_button_->HasApps() &&
ShouldShowAppsButton());
if (kiosk_license_mode_) {
// Create the bubble once the login shelf view is available for anchoring.
if (!kiosk_instruction_bubble_ && GetWidget()) {
Shelf* shelf = Shelf::ForWindow(GetWidget()->GetNativeWindow());
kiosk_instruction_bubble_ = new KioskAppInstructionBubble(
GetViewByID(kApps), shelf->alignment(),
shelf->shelf_widget()->GetShelfBackgroundColor());
}
if (kiosk_instruction_bubble_) {
// Show kiosk instructions if the kiosk app button is visible.
if (kiosk_apps_button_->GetVisible())
kiosk_instruction_bubble_->GetWidget()->Show();
else
kiosk_instruction_bubble_->GetWidget()->Hide();
}
}
GetViewByID(kOsInstall)->SetVisible(ShouldShowOsInstallButton());
@ -1001,6 +1040,7 @@ bool LoginShelfView::ShouldShowShutdownButton() const {
// only signin option), the guest button should be shown if allowed by policy
// and OOBE.
// 6. There are no scoped guest buttons blockers active.
// 7. The device is not in kiosk license mode.
bool LoginShelfView::ShouldShowGuestButton() const {
if (!allow_guest_)
return false;
@ -1020,6 +1060,9 @@ bool LoginShelfView::ShouldShowGuestButton() const {
if (session_state != SessionState::LOGIN_PRIMARY)
return false;
if (kiosk_license_mode_)
return false;
return true;
}

@ -16,6 +16,7 @@
#include "ash/public/cpp/kiosk_app_menu.h"
#include "ash/public/cpp/login_types.h"
#include "ash/public/cpp/scoped_guest_button_blocker.h"
#include "ash/shelf/kiosk_app_instruction_bubble.h"
#include "ash/shelf/shelf_shutdown_confirmation_bubble.h"
#include "ash/shutdown_controller_impl.h"
#include "ash/tray_action/tray_action.h"
@ -116,6 +117,9 @@ class ASH_EXPORT LoginShelfView : public views::View,
// Sets and animates the opacity of login shelf buttons.
void SetButtonOpacity(float target_opacity);
// Test API. Set device to have kiosk license.
void SetKioskLicenseModeForTesting(bool is_kiosk_license_mode);
// views::View:
const char* GetClassName() const override;
void OnFocus() override;
@ -159,7 +163,10 @@ class ASH_EXPORT LoginShelfView : public views::View,
// strings.
void HandleLocaleChange();
// Returns true if the shutdown confirmation is visible
// Returns the Kiosk instruction bubble.
KioskAppInstructionBubble* GetKioskInstructionBubbleForTesting();
// Returns the shutdown confirmation bubble.
ShelfShutdownConfirmationBubble* GetShutdownConfirmationBubbleForTesting();
private:
@ -207,10 +214,15 @@ class ASH_EXPORT LoginShelfView : public views::View,
// number of dropped calls exceeds 'kMaxDroppedCallsWhenDisplaysOff'
void CallIfDisplayIsOn(const base::RepeatingClosure& closure);
// Helper function which calls on_kiosk_menu_shown when kiosk menu is shown.
void OnKioskMenuShown(const base::RepeatingClosure& on_kiosk_menu_shown);
OobeDialogState dialog_state_ = OobeDialogState::HIDDEN;
bool allow_guest_ = true;
bool is_first_signin_step_ = false;
bool show_parent_access_ = false;
// TODO(crbug.com/1307303): Determine if this is a kiosk license device.
bool kiosk_license_mode_ = false;
// When the Gaia screen is active during Login, the guest-login button should
// appear if there are no user views.
bool login_screen_has_users_ = false;
@ -235,6 +247,9 @@ class ASH_EXPORT LoginShelfView : public views::View,
// shelf.
KioskAppsButton* kiosk_apps_button_ = nullptr;
// The kiosk app instruction will be shown if the kiosk app button is visible.
KioskAppInstructionBubble* kiosk_instruction_bubble_ = nullptr;
// This is used in tests to check if the confirmation bubble is visible and to
// click its buttons.
ShelfShutdownConfirmationBubble* test_shutdown_confirmation_bubble_ = nullptr;

@ -1426,11 +1426,82 @@ TEST_P(LoginShelfViewWithShutdownConfirmationTest, ClickRestartButton) {
EXPECT_TRUE(Shell::Get()->lock_state_controller()->ShutdownRequested());
}
class LoginShelfViewWithKioskLicenseTest : public LoginShelfViewTest {
public:
LoginShelfViewWithKioskLicenseTest() = default;
LoginShelfViewWithKioskLicenseTest(
const LoginShelfViewWithKioskLicenseTest&) = delete;
LoginShelfViewWithKioskLicenseTest& operator=(
const LoginShelfViewWithKioskLicenseTest&) = delete;
~LoginShelfViewWithKioskLicenseTest() override = default;
protected:
// Check whether the kiosk instruction bubble is visible.
bool IsKioskInstructionBubbleVisible() {
return login_shelf_view_->GetKioskInstructionBubbleForTesting() &&
login_shelf_view_->GetKioskInstructionBubbleForTesting()
->GetWidget()
->IsVisible();
}
void SetKioskLicenseModeForTesting(bool is_kiosk_license_mode) {
login_shelf_view_->SetKioskLicenseModeForTesting(is_kiosk_license_mode);
}
};
// Checks that kiosk app button and kiosk instruction appears if device is with
// kiosk license.
TEST_P(LoginShelfViewWithKioskLicenseTest, ShouldShowKioskInstructionBubble) {
SetKioskLicenseModeForTesting(true);
NotifySessionStateChanged(SessionState::LOGIN_PRIMARY);
std::vector<KioskAppMenuEntry> kiosk_apps(1);
login_shelf_view_->SetKioskApps(kiosk_apps, {}, {});
EXPECT_TRUE(
login_shelf_view_->GetViewByID(LoginShelfView::kApps)->GetVisible());
EXPECT_TRUE(IsKioskInstructionBubbleVisible());
}
// Checks that kiosk app button appears and kiosk instruction hidden if device
// is not with kiosk license.
TEST_P(LoginShelfViewWithKioskLicenseTest, ShouldHideKioskInstructionBubble) {
SetKioskLicenseModeForTesting(false);
NotifySessionStateChanged(SessionState::LOGIN_PRIMARY);
std::vector<KioskAppMenuEntry> kiosk_apps(1);
login_shelf_view_->SetKioskApps(kiosk_apps, {}, {});
EXPECT_TRUE(
login_shelf_view_->GetViewByID(LoginShelfView::kApps)->GetVisible());
EXPECT_FALSE(IsKioskInstructionBubbleVisible());
}
// Checks that kiosk app button appears and kiosk instruction hidden if device
// is with kiosk license and no kiosk app is set up.
TEST_P(LoginShelfViewWithKioskLicenseTest,
ShouldNotShowKioskInstructionBubble) {
SetKioskLicenseModeForTesting(true);
NotifySessionStateChanged(SessionState::LOGIN_PRIMARY);
std::vector<KioskAppMenuEntry> kiosk_apps(0);
login_shelf_view_->SetKioskApps(kiosk_apps, {}, {});
EXPECT_FALSE(
login_shelf_view_->GetViewByID(LoginShelfView::kApps)->GetVisible());
EXPECT_FALSE(IsKioskInstructionBubbleVisible());
}
INSTANTIATE_TEST_SUITE_P(All, LoginShelfViewTest, testing::Bool());
INSTANTIATE_TEST_SUITE_P(All, OsInstallButtonTest, testing::Bool());
INSTANTIATE_TEST_SUITE_P(All,
LoginShelfViewWithShutdownConfirmationTest,
testing::Bool());
INSTANTIATE_TEST_SUITE_P(All,
LoginShelfViewWithKioskLicenseTest,
testing::Bool());
} // namespace
} // namespace ash