Skip to content

Commit

Permalink
Introduce security curtain screen, compliant with UX requirements
Browse files Browse the repository at this point in the history
This replaces placeholder UI that was previously added
(but never released to any customers).

UX requirement: http://shortn/_tWLEP1bbdp
Implementation:
   Light mode: https://screenshot.googleplex.com/7UwiiWHr3HEVNzU.png
   Dark mode: https://screenshot.googleplex.com/BFWm4YsNSpwmkvN.png

Note that the implementation is not 100% according to the UX
requirements because to fully comply I have to wait until the
OOBE code is migrated to their new look-and-feel, which is
only scheduled for M116.
I opened b/271099991 to ensure we do not forget to update the UX at
that time.

Low-Coverage-Reason: Will add browser pixel tests to ensure the curtain looks correct, but support for that is not there yet.

Bug: b/266550061
Change-Id: I1a30de86140b7d796a55399e7573b8260d8560bc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4254387
Reviewed-by: Demetrios Papadopoulos <dpapad@chromium.org>
Reviewed-by: Renato Silva <rrsilva@google.com>
Commit-Queue: Jeroen Dhollander <jeroendh@google.com>
Cr-Commit-Position: refs/heads/main@{#1120420}
  • Loading branch information
Jeroen Dhollander authored and Chromium LUCI CQ committed Mar 22, 2023
1 parent 2f32cad commit b93b120
Show file tree
Hide file tree
Showing 23 changed files with 315 additions and 183 deletions.
8 changes: 0 additions & 8 deletions ash/ash_strings.grd
Expand Up @@ -6443,14 +6443,6 @@ New install
Please unlock to view notifications
</message>

<!-- security curtain -->
<message name="IDS_ASH_CURTAIN_TITLE" desc="Title text displayed to inform the user a remote admin has taken control of the ChromeOs device.">
Your administrator is controlling your device
</message>
<message name="IDS_ASH_CURTAIN_DESCRIPTION" desc="Detailed description displayed to inform the user a remote admin has taken control of the ChromeOs device.">
Your administrator has logged in to look into an issue. You can continue to use the device after the administrator gives the control back to you.
</message>

<!-- pagination view -->
<message name="IDS_ASH_PAGINATION_LEFT_ARROW_TOOLTIP" desc="The tooltip text used for pagination left arrow button." >
Previous page
Expand Down
1 change: 0 additions & 1 deletion ash/ash_strings_grd/IDS_ASH_CURTAIN_DESCRIPTION.png.sha1

This file was deleted.

1 change: 0 additions & 1 deletion ash/ash_strings_grd/IDS_ASH_CURTAIN_TITLE.png.sha1

This file was deleted.

214 changes: 47 additions & 167 deletions ash/curtain/remote_maintenance_curtain_view.cc
Expand Up @@ -6,195 +6,75 @@

#include <memory>

#include "ash/public/cpp/shelf_config.h"
#include "ash/public/cpp/style/color_provider.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/assistant/ui/base/stack_layout.h"
#include "ash/public/cpp/ash_web_view.h"
#include "ash/public/cpp/ash_web_view_factory.h"
#include "ash/wallpaper/wallpaper_constants.h"
#include "ash/wallpaper/wallpaper_view.h"
#include "base/check_deref.h"
#include "chromeos/ui/vector_icons/vector_icons.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_constants.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/views/background.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/metadata/view_factory_internal.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"

namespace ash::curtain {

namespace {

using views::Builder;
using views::FlexLayoutView;
using views::ImageView;
using views::kFlexBehaviorKey;
using views::kMarginsKey;
using views::LayoutOrientation;

constexpr gfx::Size kEnterpriseIconSize(40, 40);
constexpr gfx::Size kLockImageSize(300, 300);

constexpr gfx::Insets kEnterpriseIconMargin = gfx::Insets::VH(20, 5);
constexpr gfx::Insets kLockImageMargin = gfx::Insets::VH(20, 20);
constexpr gfx::Insets kLeftSideMargins = gfx::Insets::VH(200, 100);
constexpr gfx::Insets kRightSideMargins = gfx::Insets::VH(100, 100);

gfx::ImageSkia EnterpriseIcon(const ColorProvider& color_provider) {
return gfx::CreateVectorIcon(
chromeos::kEnterpriseIcon,
color_provider.GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorPrimary));
}
constexpr char kRemoteManagementCurtainUrl[] = "chrome://security-curtain/";

gfx::ImageSkia LockImage(const ColorProvider& color_provider) {
return gfx::CreateVectorIcon(
kSystemTrayCapsLockIcon,
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorProminent));
gfx::Size CalculateCurtainViewSize(const gfx::Size& size) {
// TODO(b/271099991): Use correct margins once Oobe code has migrated to the
// new UX style so we can use their code.
const int horizontal_margin = size.width() / 10;
const int vertical_margin = size.height() / 10;
return gfx::Size(size.width() - 2 * horizontal_margin,
size.height() - 2 * vertical_margin);
}

std::u16string TitleText() {
return l10n_util::GetStringUTF16(IDS_ASH_CURTAIN_TITLE);
}
} // namespace

std::u16string MessageText() {
return l10n_util::GetStringUTF16(IDS_ASH_CURTAIN_DESCRIPTION);
RemoteMaintenanceCurtainView::RemoteMaintenanceCurtainView() {
Initialize();
}

// A container that - when added as a child of a `FlexContainer` - will
// automatically resize to take an equal share of the available space.
class ResizingFlexContainer : public views::FlexLayoutView {
public:
ResizingFlexContainer() {
// Tell our parent flex container that we want to be resized depending
// on the available space.
SetProperty(
kFlexBehaviorKey,
views::FlexSpecification{views::MinimumFlexSizeRule::kScaleToMinimum,
views::MaximumFlexSizeRule::kUnbounded});
}
ResizingFlexContainer(const ResizingFlexContainer&) = delete;
ResizingFlexContainer& operator=(const ResizingFlexContainer&) = delete;
~ResizingFlexContainer() override = default;

// `views::FlexLayoutView` implementation:

gfx::Size CalculatePreferredSize() const override {
// The parent Flex container will first grant each child as much space
// as their preferred size, and then distributes all remaining space
// equally among all children. So to ensure all children get exactly the
// same space, we make them all report the same (small) preferred size.
return gfx::Size(1, 1);
}
};

BEGIN_VIEW_BUILDER(, ResizingFlexContainer, views::FlexLayoutView)
END_VIEW_BUILDER
RemoteMaintenanceCurtainView::~RemoteMaintenanceCurtainView() = default;

} // namespace
void RemoteMaintenanceCurtainView::OnBoundsChanged(const gfx::Rect&) {
UpdateChildrenSize(size());
}

} // namespace ash::curtain
void RemoteMaintenanceCurtainView::UpdateChildrenSize(
const gfx::Size& new_size) {
wallpaper_view_->SetPreferredSize(new_size);
curtain_view_->SetPreferredSize(CalculateCurtainViewSize(new_size));
}

// Allow `ResizingContainer` to be used inside a view builder hierarchy
// (`Builder<ResizingContainer>().SetXYZ()`).
//
// Must be in the global namespace.
DEFINE_VIEW_BUILDER(, ash::curtain::ResizingFlexContainer)
void RemoteMaintenanceCurtainView::Initialize() {
layout_ = SetLayoutManager(std::make_unique<StackLayout>());

namespace ash::curtain {
AddWallpaper();
AddCurtainWebView();
}

RemoteMaintenanceCurtainView::RemoteMaintenanceCurtainView() {
Initialize();
void RemoteMaintenanceCurtainView::AddWallpaper() {
// Add a copy of the wallpaper above the security curtain, since UX wants
// to see a blurred wallpaper but we can't expose the real wallpaper as that
// would require the security curtain to be translucent which would defeat
// its whole purpose.
DCHECK(!wallpaper_view_);
wallpaper_view_ = AddChildView(
std::make_unique<WallpaperView>(wallpaper_constants::kLockLoginBlur));
}

RemoteMaintenanceCurtainView::~RemoteMaintenanceCurtainView() = default;
void RemoteMaintenanceCurtainView::AddCurtainWebView() {
DCHECK(!curtain_view_);
curtain_view_ =
AddChildView(AshWebViewFactory::Get()->Create(AshWebView::InitParams()));
layout_->SetVerticalAlignmentForView(curtain_view_,
StackLayout::VerticalAlignment::kCenter);

void RemoteMaintenanceCurtainView::Initialize() {
const ColorProvider& color_provider = CHECK_DEREF(ColorProvider::Get());
const int shelf_size = CHECK_DEREF(ShelfConfig::Get()).shelf_size();

// A flex rule forcing the view to maintain its fixed size.
const views::FlexSpecification kFixedSize(
views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kPreferred);

Builder<FlexLayoutView>(this)
.SetOrientation(LayoutOrientation::kVertical)
.SetBackground(
views::CreateSolidBackground(color_provider.GetBaseLayerColor(
ColorProvider::BaseLayerType::kOpaque)))
.AddChildren(
// Main content
Builder<ResizingFlexContainer>()
.SetOrientation(LayoutOrientation::kHorizontal)
.AddChildren(
// Left half of the screen
Builder<ResizingFlexContainer>()
.SetProperty(kMarginsKey, kLeftSideMargins)
.SetOrientation(LayoutOrientation::kVertical)
.AddChildren(
// Enterprise icon
Builder<ImageView>()
.SetImage(EnterpriseIcon(color_provider))
.SetImageSize(kEnterpriseIconSize)
.SetProperty(kMarginsKey, kEnterpriseIconMargin)
.SetHorizontalAlignment(
ImageView::Alignment::kLeading),
// Title
Builder<views::Label>()
.SetText(TitleText())
.SetTextStyle(views::style::STYLE_EMPHASIZED)
.SetTextContext(
views::style::CONTEXT_DIALOG_TITLE)
.SetHorizontalAlignment(
gfx::HorizontalAlignment::ALIGN_LEFT)
.SetMultiLine(true)
.SetEnabledColor(
color_provider.GetContentLayerColor(
ColorProvider::ContentLayerType::
kTextColorPrimary)),
// Message
Builder<views::Label>()
.SetText(MessageText())
.SetVerticalAlignment(
gfx::VerticalAlignment::ALIGN_TOP)
.SetHorizontalAlignment(
gfx::HorizontalAlignment::ALIGN_LEFT)
.SetMultiLine(true)
.SetEnabledColor(
color_provider.GetContentLayerColor(
ColorProvider::ContentLayerType::
kTextColorPrimary))),
// Right half of the screen
Builder<ResizingFlexContainer>()
.SetProperty(kMarginsKey, kRightSideMargins)
.AddChildren(
Builder<ImageView>()
.SetImage(LockImage(color_provider))
.SetImageSize(kLockImageSize)
.SetProperty(kMarginsKey, kLockImageMargin)
.SetHorizontalAlignment(
ImageView::Alignment::kCenter))),
// Shelf
Builder<View>()
.SetPreferredSize(gfx::Size(0, shelf_size))
.SetProperty(kFlexBehaviorKey, kFixedSize))
.BuildChildren();
// Load the actual security curtain content.
curtain_view_->Navigate(GURL(kRemoteManagementCurtainUrl));
}

BEGIN_METADATA(RemoteMaintenanceCurtainView, views::FlexLayoutView)
END_METADATA

} // namespace ash::curtain
29 changes: 24 additions & 5 deletions ash/curtain/remote_maintenance_curtain_view.h
Expand Up @@ -6,26 +6,45 @@
#define ASH_CURTAIN_REMOTE_MAINTENANCE_CURTAIN_VIEW_H_

#include "ash/ash_export.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/views/layout/flex_layout_view.h"
#include "base/allocator/partition_allocator/pointers/raw_ptr.h"
#include "ui/views/view.h"

namespace ash {
class AshWebView;
class StackLayout;
} // namespace ash

namespace gfx {
class Rect;
class Size;
} // namespace gfx

namespace ash::curtain {

// The root view shown as the security curtain overlay when the security curtain
// is created by an enterprise admin through the 'start crd session' remote
// command.
class ASH_EXPORT RemoteMaintenanceCurtainView : public views::FlexLayoutView {
class ASH_EXPORT RemoteMaintenanceCurtainView : public views::View {
public:
RemoteMaintenanceCurtainView();
RemoteMaintenanceCurtainView(const RemoteMaintenanceCurtainView&) = delete;
RemoteMaintenanceCurtainView& operator=(const RemoteMaintenanceCurtainView&) =
delete;
~RemoteMaintenanceCurtainView() override;

METADATA_HEADER(RemoteMaintenanceCurtainView);

private:
// `views::View` implementation:
void OnBoundsChanged(const gfx::Rect&) override;

void UpdateChildrenSize(const gfx::Size& new_size);

void Initialize();
void AddWallpaper();
void AddCurtainWebView();

raw_ptr<StackLayout> layout_ = nullptr;
raw_ptr<AshWebView> curtain_view_ = nullptr;
raw_ptr<views::View> wallpaper_view_ = nullptr;
};

} // namespace ash::curtain
Expand Down
9 changes: 9 additions & 0 deletions chrome/app/chromeos_strings.grdp
Expand Up @@ -6648,4 +6648,13 @@ Your <ph name="DEVICE_TYPE">{0}<ex>Chromebook</ex></ph> will be locked now.
<message name="IDS_OFFICE_FALLBACK_INSTRUCTIONS" desc="Office Fallback dialog instructions for the choices (buttons) the user has available to open the office file.">
Choose "Try again", or choose "Open in offline editor" to use limited view and editing options.
</message>

<!-- security curtain -->
<message name="IDS_SECURITY_CURTAIN_TITLE" desc="Title text displayed to inform the user a remote admin has taken control of the ChromeOs device.">
Your administrator is controlling your device
</message>
<message name="IDS_SECURITY_CURTAIN_DESCRIPTION" desc="Detailed description displayed to inform the user a remote admin has taken control of the ChromeOs device.">
Your administrator has logged in to look into an issue. You can continue to use the device after the administrator gives the control back to you.
</message>

</grit-part>
@@ -0,0 +1 @@
344f76c78773402b51bc14353489b58efc19569a
@@ -0,0 +1 @@
344f76c78773402b51bc14353489b58efc19569a
@@ -0,0 +1,27 @@
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//ui/webui/resources/tools/build_webui.gni")

assert(is_chromeos_ash)

build_webui("build") {
grd_prefix = "remote_maintenance_curtain"

static_files = [
"main.html",

# Vector resources
"images/admin_control_dark.svg",
"images/admin_control_light.svg",
]

# Files added here must have a corresponding .html file
web_component_files = [ "curtain_screen.ts" ]

ts_deps = [
"//third_party/polymer/v3_0:library",
"//ui/webui/resources/js:build_ts",
]
}
@@ -0,0 +1 @@
file://ash/curtain/OWNERS
@@ -0,0 +1,21 @@
<!--
Copyright 2023 The Chromium Authors
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<style include="oobe-common-styles oobe-dialog-host-styles"></style>

<oobe-adaptive-dialog id="mainCurtainDialog" role="dialog"
aria-label="$i18n{curtainTitle}">
<iron-icon slot="icon" icon="oobe-32:enterprise"></iron-icon>
<h1 slot="title">$i18n{curtainTitle}</h1>
<div slot="subtitle">$i18n{curtainDescription}</div>
<div slot="content" class="flex layout vertical center center-justified">
<picture>
<source srcset="images/admin_control_dark.svg"
media="(prefers-color-scheme: dark)" class="oobe-illustration">
<img class="illustration" src="images/admin_control_light.svg">
</picture>
</div>
</oobe-adaptive-dialog>

0 comments on commit b93b120

Please sign in to comment.