Commit 63ca8fef authored by Andre Le's avatar Andre Le Committed by Commit Bot

[CrOS PhoneHub] Hook up task continuation UI to real phone data.

- Connect task continuation view to phone model to hook up data.
- Fix continue browsing view to display data.
- A small fix in multidevice-internals to display favicon in debug UI.

Screenshot:
https://screenshot.googleplex.com/AXzJduFaBPTaLmQ

BUG=1106937,1126208

Change-Id: I2dde0aeb25183f010427644f0eb54582056ad9b3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2414889Reviewed-by: default avatarTim Song <tengs@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Commit-Queue: Andre Le <leandre@chromium.org>
Cr-Commit-Position: refs/heads/master@{#809071}
parent 6572b37a
......@@ -2096,6 +2096,7 @@ test("ash_unittests") {
"system/phonehub/phone_hub_notification_controller_unittest.cc",
"system/phonehub/phone_hub_tray_unittest.cc",
"system/phonehub/phone_status_view_unittest.cc",
"system/phonehub/task_continuation_view_unittest.cc",
"system/power/backlights_forced_off_setter_unittest.cc",
"system/power/peripheral_battery_notifier_unittest.cc",
"system/power/power_button_controller_unittest.cc",
......
......@@ -3,9 +3,16 @@
// found in the LICENSE file.
#include "ash/system/phonehub/continue_browsing_chip.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/style/ash_color_provider.h"
#include "ui/base/l10n/l10n_util.h"
#include "ash/system/phonehub/phone_hub_tray.h"
#include "ash/system/status_area_widget.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
......@@ -14,30 +21,59 @@ namespace ash {
namespace {
constexpr gfx::Insets kContinueBrowsingChipPadding(8, 8);
constexpr int kContinueBrowsingChipSpacing = 5;
constexpr int kContinueBrowsingChipFaviconSpacing = 5;
constexpr gfx::Size kContinueBrowsingChipFaviconSize(50, 50);
constexpr int kTaskContinuationChipRadius = 10;
constexpr int kTitleMaxLines = 2;
constexpr gfx::Size kTitleViewSize(100, 40);
} // namespace
ContinueBrowsingChip::ContinueBrowsingChip() : views::Button(this) {
ContinueBrowsingChip::ContinueBrowsingChip(
const chromeos::phonehub::BrowserTabsModel::BrowserTabMetadata& metadata)
: views::Button(this), url_(metadata.url) {
auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, kContinueBrowsingChipPadding));
views::BoxLayout::Orientation::kVertical, kContinueBrowsingChipPadding,
kContinueBrowsingChipSpacing));
layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
// TODO(leandre): Update chip with phone data and updated design.
auto* header_label = AddChildView(std::make_unique<views::Label>(
l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_TASK_CONTINUATION_TITLE)));
header_label->SetAutoColorReadabilityEnabled(false);
header_label->SetSubpixelRenderingEnabled(false);
header_label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
auto* header_view = AddChildView(std::make_unique<views::View>());
auto* header_layout =
header_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
kContinueBrowsingChipFaviconSpacing));
header_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
header_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
auto* favicon =
header_view->AddChildView(std::make_unique<views::ImageView>());
favicon->SetImageSize(kContinueBrowsingChipFaviconSize);
favicon->SetImage(metadata.favicon.AsImageSkia());
auto* title_label =
header_view->AddChildView(std::make_unique<views::Label>(metadata.title));
title_label->SetAutoColorReadabilityEnabled(false);
title_label->SetSubpixelRenderingEnabled(false);
title_label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary));
title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
title_label->SetMultiLine(true);
title_label->SetMaxLines(kTitleMaxLines);
title_label->SetSize(kTitleViewSize);
title_label->SetFontList(
title_label->font_list().DeriveWithWeight(gfx::Font::Weight::BOLD));
auto* sub_label = AddChildView(std::make_unique<views::Label>(
l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_TASK_CONTINUATION_TITLE)));
sub_label->SetAutoColorReadabilityEnabled(false);
sub_label->SetSubpixelRenderingEnabled(false);
sub_label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorSecondary));
auto* url_label = AddChildView(
std::make_unique<views::Label>(base::UTF8ToUTF16(metadata.url.host())));
url_label->SetAutoColorReadabilityEnabled(false);
url_label->SetSubpixelRenderingEnabled(false);
url_label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary));
}
void ContinueBrowsingChip::OnPaintBackground(gfx::Canvas* canvas) {
......@@ -52,7 +88,13 @@ void ContinueBrowsingChip::OnPaintBackground(gfx::Canvas* canvas) {
void ContinueBrowsingChip::ButtonPressed(views::Button* sender,
const ui::Event& event) {
// TODO(leandre): Open browser when button pressed.
PA_LOG(INFO) << "Opening browser tab: " << url_;
NewWindowDelegate::GetInstance()->NewTabWithUrl(
url_, /*from_user_interaction=*/true);
Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->phone_hub_tray()
->CloseBubble();
}
ContinueBrowsingChip::~ContinueBrowsingChip() = default;
......
......@@ -6,6 +6,7 @@
#define ASH_SYSTEM_PHONEHUB_CONTINUE_BROWSING_CHIP_H_
#include "ash/ash_export.h"
#include "chromeos/components/phonehub/browser_tabs_model.h"
#include "ui/gfx/canvas.h"
#include "ui/views/controls/button/button.h"
......@@ -16,7 +17,9 @@ namespace ash {
class ASH_EXPORT ContinueBrowsingChip : public views::Button,
public views::ButtonListener {
public:
ContinueBrowsingChip();
explicit ContinueBrowsingChip(
const chromeos::phonehub::BrowserTabsModel::BrowserTabMetadata& metadata);
~ContinueBrowsingChip() override;
ContinueBrowsingChip(ContinueBrowsingChip&) = delete;
ContinueBrowsingChip operator=(ContinueBrowsingChip&) = delete;
......@@ -25,6 +28,9 @@ class ASH_EXPORT ContinueBrowsingChip : public views::Button,
void OnPaintBackground(gfx::Canvas* canvas) override;
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
const char* GetClassName() const override;
private:
GURL url_;
};
} // namespace ash
......
......@@ -71,8 +71,10 @@ class PhoneHubView : public views ::View {
AddSeparator();
setup_layered_view(
bubble_view_->AddChildView(std::make_unique<TaskContinuationView>()));
if (phone_model) {
setup_layered_view(bubble_view->AddChildView(
std::make_unique<TaskContinuationView>(phone_model)));
}
}
~PhoneHubView() override = default;
......
......@@ -11,16 +11,17 @@
#include "ui/gfx/geometry/insets.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view_model.h"
namespace ash {
using BrowserTabsModel = chromeos::phonehub::BrowserTabsModel;
namespace {
constexpr int kTaskContinuationHeaderSpacing = 8;
constexpr gfx::Insets kTaskContinuationViewPadding(12, 4);
constexpr gfx::Insets kPhoneHubSubHeaderPadding(4, 32);
constexpr gfx::Size kTaskContinuationChipSize(170, 50);
constexpr gfx::Size kTaskContinuationChipSize(170, 80);
constexpr int kTaskContinuationChipsInRow = 2;
constexpr int kTaskContinuationChipSpacing = 8;
constexpr int kTaskContinuationChipVerticalPadding = 5;
......@@ -46,77 +47,13 @@ class HeaderView : public views::View {
const char* GetClassName() const override { return "HeaderView"; }
};
class ChipsView : public views::View {
public:
ChipsView() {
// TODO(leandre): Add task chip to bubble using real data from phone.
AddTaskChip(new ContinueBrowsingChip());
AddTaskChip(new ContinueBrowsingChip());
AddTaskChip(new ContinueBrowsingChip());
AddTaskChip(new ContinueBrowsingChip());
}
~ChipsView() override = default;
ChipsView(ChipsView&) = delete;
ChipsView operator=(ChipsView&) = delete;
// views::View:
gfx::Size CalculatePreferredSize() const override {
int width =
kTaskContinuationChipSize.width() * kTaskContinuationChipsInRow +
kTaskContinuationChipSpacing;
int rows_num = std::ceil(
(double)(task_chips_.view_size() / kTaskContinuationChipsInRow));
int height = (kTaskContinuationChipSize.height() +
kTaskContinuationChipVerticalPadding) *
std::max(0, rows_num - 1) +
kTaskContinuationChipSize.height();
return gfx::Size(width, height);
}
void Layout() override {
views::View::Layout();
CalculateIdealBounds();
for (int i = 0; i < task_chips_.view_size(); ++i) {
auto* button = task_chips_.view_at(i);
button->SetBoundsRect(task_chips_.ideal_bounds(i));
}
}
const char* GetClassName() const override { return "ChipsView"; }
private:
gfx::Point GetButtonPosition(int index) {
int row = index / kTaskContinuationChipsInRow;
int column = index % kTaskContinuationChipsInRow;
int x = (kTaskContinuationChipSize.width() + kTaskContinuationChipSpacing) *
column;
int y = (kTaskContinuationChipSize.height() +
kTaskContinuationChipVerticalPadding) *
row;
return gfx::Point(x, y);
}
void CalculateIdealBounds() {
for (int i = 0; i < task_chips_.view_size(); ++i) {
gfx::Rect tile_bounds =
gfx::Rect(GetButtonPosition(i), kTaskContinuationChipSize);
task_chips_.set_ideal_bounds(i, tile_bounds);
}
}
void AddTaskChip(views::View* task_chip) {
int view_size = task_chips_.view_size();
task_chips_.Add(task_chip, view_size);
AddChildView(task_chip);
}
views::ViewModelT<views::View> task_chips_;
};
} // namespace
TaskContinuationView::TaskContinuationView() {
TaskContinuationView::TaskContinuationView(
chromeos::phonehub::PhoneModel* phone_model)
: phone_model_(phone_model) {
phone_model_->AddObserver(this);
auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, kTaskContinuationViewPadding,
kTaskContinuationHeaderSpacing));
......@@ -124,13 +61,105 @@ TaskContinuationView::TaskContinuationView() {
views::BoxLayout::CrossAxisAlignment::kStart);
AddChildView(std::make_unique<HeaderView>());
AddChildView(std::make_unique<ChipsView>());
chips_view_ = AddChildView(std::make_unique<TaskChipsView>());
Update();
}
TaskContinuationView::~TaskContinuationView() {
phone_model_->RemoveObserver(this);
}
TaskContinuationView::~TaskContinuationView() = default;
void TaskContinuationView::OnModelChanged() {
Update();
}
const char* TaskContinuationView::GetClassName() const {
return "TaskContinuationView";
}
TaskContinuationView::TaskChipsView::TaskChipsView() = default;
TaskContinuationView::TaskChipsView::~TaskChipsView() = default;
void TaskContinuationView::TaskChipsView::AddTaskChip(views::View* task_chip) {
int view_size = task_chips_.view_size();
task_chips_.Add(task_chip, view_size);
AddChildView(task_chip);
}
// views::View:
gfx::Size TaskContinuationView::TaskChipsView::CalculatePreferredSize() const {
int width = kTaskContinuationChipSize.width() * kTaskContinuationChipsInRow +
kTaskContinuationChipSpacing;
int rows_num =
std::ceil((double)task_chips_.view_size() / kTaskContinuationChipsInRow);
int height = (kTaskContinuationChipSize.height() +
kTaskContinuationChipVerticalPadding) *
std::max(0, rows_num - 1) +
kTaskContinuationChipSize.height();
return gfx::Size(width, height);
}
void TaskContinuationView::TaskChipsView::Layout() {
views::View::Layout();
CalculateIdealBounds();
for (int i = 0; i < task_chips_.view_size(); ++i) {
auto* button = task_chips_.view_at(i);
button->SetBoundsRect(task_chips_.ideal_bounds(i));
}
}
const char* TaskContinuationView::TaskChipsView::GetClassName() const {
return "TaskChipsView";
}
void TaskContinuationView::TaskChipsView::Reset() {
task_chips_.Clear();
RemoveAllChildViews(true /* delete_children */);
}
gfx::Point TaskContinuationView::TaskChipsView::GetButtonPosition(int index) {
int row = index / kTaskContinuationChipsInRow;
int column = index % kTaskContinuationChipsInRow;
int x = (kTaskContinuationChipSize.width() + kTaskContinuationChipSpacing) *
column;
int y = (kTaskContinuationChipSize.height() +
kTaskContinuationChipVerticalPadding) *
row;
return gfx::Point(x, y);
}
void TaskContinuationView::TaskChipsView::CalculateIdealBounds() {
for (int i = 0; i < task_chips_.view_size(); ++i) {
gfx::Rect tile_bounds =
gfx::Rect(GetButtonPosition(i), kTaskContinuationChipSize);
task_chips_.set_ideal_bounds(i, tile_bounds);
}
}
void TaskContinuationView::Update() {
if (!phone_model_->browser_tabs_model()) {
SetVisible(false);
return;
}
const BrowserTabsModel& browser_tabs =
phone_model_->browser_tabs_model().value();
if (!browser_tabs.is_tab_sync_enabled() ||
browser_tabs.most_recent_tabs().empty()) {
SetVisible(false);
return;
}
chips_view_->Reset();
for (const BrowserTabsModel::BrowserTabMetadata& metadata :
browser_tabs.most_recent_tabs()) {
chips_view_->AddTaskChip(new ContinueBrowsingChip(metadata));
}
SetVisible(true);
}
} // namespace ash
......@@ -6,21 +6,61 @@
#define ASH_SYSTEM_PHONEHUB_TASK_CONTINUATION_VIEW_H_
#include "ash/ash_export.h"
#include "chromeos/components/phonehub/phone_model.h"
#include "ui/views/view.h"
#include "ui/views/view_model.h"
namespace ash {
// A view in Phone Hub bubble that allows user to pick up unfinished task left
// off from their phone, currently only support web browsing.
class ASH_EXPORT TaskContinuationView : public views::View {
class ASH_EXPORT TaskContinuationView
: public views::View,
public chromeos::phonehub::PhoneModel::Observer {
public:
TaskContinuationView();
explicit TaskContinuationView(chromeos::phonehub::PhoneModel* phone_model);
~TaskContinuationView() override;
TaskContinuationView(TaskContinuationView&) = delete;
TaskContinuationView operator=(TaskContinuationView&) = delete;
// chromeos::phonehub::PhoneHubModel::Observer:
void OnModelChanged() override;
// views::View:
const char* GetClassName() const override;
private:
FRIEND_TEST_ALL_PREFIXES(TaskContinuationViewTest, TaskChipsView);
class TaskChipsView : public views::View {
public:
TaskChipsView();
~TaskChipsView() override;
TaskChipsView(TaskChipsView&) = delete;
TaskChipsView operator=(TaskChipsView&) = delete;
void AddTaskChip(views::View* task_chip);
// views::View:
gfx::Size CalculatePreferredSize() const override;
void Layout() override;
const char* GetClassName() const override;
// Clear all existing tasks in the view and in |task_chips_|.
void Reset();
private:
gfx::Point GetButtonPosition(int index);
void CalculateIdealBounds();
views::ViewModelT<views::View> task_chips_;
};
// Update the chips to display current phone status.
void Update();
chromeos::phonehub::PhoneModel* phone_model_ = nullptr;
TaskChipsView* chips_view_ = nullptr;
};
} // namespace ash
......
// Copyright 2020 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/system/phonehub/task_continuation_view.h"
#include "ash/public/cpp/test/test_new_window_delegate.h"
#include "ash/system/phonehub/continue_browsing_chip.h"
#include "ash/test/ash_test_base.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/components/phonehub/mutable_phone_model.h"
#include "chromeos/components/phonehub/phone_model_test_util.h"
#include "chromeos/constants/chromeos_features.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace ash {
using BrowserTabsModel = chromeos::phonehub::BrowserTabsModel;
namespace {
class MockNewWindowDelegate : public testing::NiceMock<TestNewWindowDelegate> {
public:
// TestNewWindowDelegate:
MOCK_METHOD(void,
NewTabWithUrl,
(const GURL& url, bool from_user_interaction),
(override));
MOCK_METHOD(void, OpenFeedbackPage, (bool from_assistant), (override));
};
class DummyEvent : public ui::Event {
public:
DummyEvent() : Event(ui::ET_UNKNOWN, base::TimeTicks(), 0) {}
};
} // namespace
class TaskContinuationViewTest : public AshTestBase {
public:
TaskContinuationViewTest() = default;
~TaskContinuationViewTest() override = default;
// AshTestBase:
void SetUp() override {
feature_list_.InitAndEnableFeature(chromeos::features::kPhoneHub);
AshTestBase::SetUp();
task_continuation_view_ =
std::make_unique<TaskContinuationView>(&phone_model_);
}
void TearDown() override {
task_continuation_view_.reset();
AshTestBase::TearDown();
}
protected:
TaskContinuationView* task_view() { return task_continuation_view_.get(); }
chromeos::phonehub::MutablePhoneModel* phone_model() { return &phone_model_; }
MockNewWindowDelegate& new_window_delegate() { return new_window_delegate_; }
private:
std::unique_ptr<TaskContinuationView> task_continuation_view_;
chromeos::phonehub::MutablePhoneModel phone_model_;
base::test::ScopedFeatureList feature_list_;
MockNewWindowDelegate new_window_delegate_;
};
TEST_F(TaskContinuationViewTest, TaskViewVisibility) {
phone_model()->SetBrowserTabsModel(
BrowserTabsModel(false /* is_tab_sync_enabled */));
// The view should not be shown when tab sync is not enabled.
EXPECT_FALSE(task_view()->GetVisible());
phone_model()->SetBrowserTabsModel(
BrowserTabsModel(true /* is_tab_sync_enabled */));
// The view should not be shown when tab sync is enabled but no browser tabs
// open.
EXPECT_FALSE(task_view()->GetVisible());
BrowserTabsModel::BrowserTabMetadata metadata =
chromeos::phonehub::CreateFakeBrowserTabMetadata();
std::vector<BrowserTabsModel::BrowserTabMetadata> tabs = {metadata};
phone_model()->SetBrowserTabsModel(BrowserTabsModel(true, tabs));
// The view should be shown when there is one browser tab.
EXPECT_TRUE(task_view()->GetVisible());
tabs.push_back(metadata);
phone_model()->SetBrowserTabsModel(BrowserTabsModel(true, tabs));
// The view should be shown when there are two or more browser tabs.
EXPECT_TRUE(task_view()->GetVisible());
}
TEST_F(TaskContinuationViewTest, TaskChipsView) {
BrowserTabsModel::BrowserTabMetadata metadata =
chromeos::phonehub::CreateFakeBrowserTabMetadata();
std::vector<BrowserTabsModel::BrowserTabMetadata> tabs = {metadata};
phone_model()->SetBrowserTabsModel(BrowserTabsModel(true, tabs));
// The chips view should contains 1 tab.
size_t expected_tabs = 1;
EXPECT_EQ(expected_tabs, task_view()->chips_view_->children().size());
tabs.push_back(metadata);
phone_model()->SetBrowserTabsModel(BrowserTabsModel(true, tabs));
// The chips view should contains 2 tab.
expected_tabs = 2;
EXPECT_EQ(expected_tabs, task_view()->chips_view_->children().size());
tabs.push_back(metadata);
phone_model()->SetBrowserTabsModel(BrowserTabsModel(true, tabs));
// The chips view should contains 3 tab.
expected_tabs = 3;
EXPECT_EQ(expected_tabs, task_view()->chips_view_->children().size());
tabs.push_back(metadata);
phone_model()->SetBrowserTabsModel(BrowserTabsModel(true, tabs));
// The chips view should contains 4 tab.
expected_tabs = 4;
EXPECT_EQ(expected_tabs, task_view()->chips_view_->children().size());
for (auto* child : task_view()->chips_view_->children()) {
ContinueBrowsingChip* chip = static_cast<ContinueBrowsingChip*>(child);
// NewTabWithUrl is expected to call after button pressed simulation.
EXPECT_CALL(new_window_delegate(), NewTabWithUrl)
.WillOnce([](const GURL& url, bool from_user_interaction) {
EXPECT_EQ(GURL("https://www.example.com/tab1"), url);
EXPECT_TRUE(from_user_interaction);
});
// Simulate clicking button using dummy event.
chip->ButtonPressed(nullptr, DummyEvent());
}
}
} // namespace ash
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment