1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ash/system/phonehub/onboarding_view.h"
6
7 #include <algorithm>
8 #include <memory>
9 #include <vector>
10
11 #include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
12 #include "ash/public/cpp/system_tray_client.h"
13 #include "ash/root_window_controller.h"
14 #include "ash/shell.h"
15 #include "ash/strings/grit/ash_strings.h"
16 #include "ash/style/ash_color_provider.h"
17 #include "ash/system/model/system_tray_model.h"
18 #include "ash/system/phonehub/interstitial_view_button.h"
19 #include "ash/system/phonehub/phone_hub_content_view.h"
20 #include "ash/system/phonehub/phone_hub_interstitial_view.h"
21 #include "ash/system/phonehub/phone_hub_metrics.h"
22 #include "ash/system/phonehub/phone_hub_tray.h"
23 #include "ash/system/phonehub/phone_hub_view_ids.h"
24 #include "ash/system/status_area_widget.h"
25 #include "ash/system/tray/tray_bubble_view.h"
26 #include "ash/system/unified/rounded_label_button.h"
27 #include "base/strings/strcat.h"
28 #include "base/strings/string16.h"
29 #include "chromeos/components/phonehub/onboarding_ui_tracker.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/chromeos/devicetype_utils.h"
33 #include "ui/views/layout/fill_layout.h"
34 #include "ui/views/metadata/metadata_impl_macros.h"
35
36 namespace ash {
37
38 using phone_hub_metrics::InterstitialScreenEvent;
39 using phone_hub_metrics::Screen;
40
41 // OnboardingMainView ---------------------------------------------------------
42 // Main onboarding screen with Phone Hub feature description and two buttons
43 // (Get Started and Dismiss), where user can either choose to grant permission
44 // to enable this feature or dismiss the screen.
45 class OnboardingMainView : public PhoneHubInterstitialView {
46 public:
OnboardingMainView(chromeos::phonehub::OnboardingUiTracker * onboarding_ui_tracker,OnboardingView * parent_view,OnboardingView::OnboardingFlow onboarding_flow)47 OnboardingMainView(
48 chromeos::phonehub::OnboardingUiTracker* onboarding_ui_tracker,
49 OnboardingView* parent_view,
50 OnboardingView::OnboardingFlow onboarding_flow)
51 : PhoneHubInterstitialView(/*show_progress=*/false),
52 onboarding_ui_tracker_(onboarding_ui_tracker),
53 parent_view_(parent_view),
54 onboarding_flow_(onboarding_flow) {
55 SetID(PhoneHubViewID::kOnboardingMainView);
56 InitLayout();
57 }
58
59 // PhoneHubInterstitialView:
GetScreenForMetrics() const60 Screen GetScreenForMetrics() const override {
61 switch (onboarding_flow_) {
62 case OnboardingView::kExistingMultideviceUser:
63 return Screen::kOnboardingExistingMultideviceUser;
64 case OnboardingView::kNewMultideviceUser:
65 return Screen::kOnboardingNewMultideviceUser;
66 }
67 }
68
69 private:
InitLayout()70 void InitLayout() {
71 // TODO(crbug.com/1127996): Replace PNG file with vector icon.
72 gfx::ImageSkia* image =
73 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
74 IDR_PHONE_HUB_ONBOARDING_IMAGE);
75 SetImage(*image);
76 SetTitle(
77 l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_ONBOARDING_DIALOG_TITLE));
78 SetDescription(l10n_util::GetStringFUTF16(
79 IDS_ASH_PHONE_HUB_ONBOARDING_DIALOG_DESCRIPTION,
80 ui::GetChromeOSDeviceName()));
81
82 // Add "Dismiss" and "Get started" buttons.
83 auto dismiss = std::make_unique<InterstitialViewButton>(
84 base::BindRepeating(&OnboardingMainView::DismissButtonPressed,
85 base::Unretained(this)),
86 l10n_util::GetStringUTF16(
87 IDS_ASH_PHONE_HUB_ONBOARDING_DIALOG_DISMISS_BUTTON),
88 /*paint_background=*/false);
89 dismiss->SetEnabledTextColors(AshColorProvider::Get()->GetContentLayerColor(
90 AshColorProvider::ContentLayerType::kTextColorPrimary));
91 dismiss->SetID(PhoneHubViewID::kOnboardingDismissButton);
92 AddButton(std::move(dismiss));
93
94 auto get_started = std::make_unique<InterstitialViewButton>(
95 base::BindRepeating(&OnboardingMainView::GetStartedButtonPressed,
96 base::Unretained(this)),
97 l10n_util::GetStringUTF16(
98 IDS_ASH_PHONE_HUB_ONBOARDING_DIALOG_GET_STARTED_BUTTON),
99 /*paint_background=*/true);
100 get_started->SetID(PhoneHubViewID::kOnboardingGetStartedButton);
101 AddButton(std::move(get_started));
102 }
103
GetStartedButtonPressed()104 void GetStartedButtonPressed() {
105 LogInterstitialScreenEvent(InterstitialScreenEvent::kConfirm);
106 onboarding_ui_tracker_->HandleGetStarted();
107 }
108
DismissButtonPressed()109 void DismissButtonPressed() {
110 LogInterstitialScreenEvent(InterstitialScreenEvent::kDismiss);
111 parent_view_->ShowDismissPrompt();
112 }
113
114 chromeos::phonehub::OnboardingUiTracker* onboarding_ui_tracker_ = nullptr;
115 OnboardingView* parent_view_ = nullptr;
116 const OnboardingView::OnboardingFlow onboarding_flow_;
117 };
118
119 // OnboardingDismissPromptView ------------------------------------------------
120 // A follow-up prompt screen that pops up when the user has chosen to dismiss
121 // the main onboarding screen. It should not be shown again after being
122 // dismissed manually by either clicking the ack button or outside the bubble.
123 class OnboardingDismissPromptView : public PhoneHubInterstitialView {
124 public:
OnboardingDismissPromptView(chromeos::phonehub::OnboardingUiTracker * onboarding_ui_tracker)125 explicit OnboardingDismissPromptView(
126 chromeos::phonehub::OnboardingUiTracker* onboarding_ui_tracker)
127 : PhoneHubInterstitialView(/*show_progress=*/false, /*show_image=*/false),
128 onboarding_ui_tracker_(onboarding_ui_tracker) {
129 SetID(PhoneHubViewID::kOnboardingDismissPromptView);
130 InitLayout();
131 }
132
133 private:
InitLayout()134 void InitLayout() {
135 // Adds title and description.
136 SetTitle(l10n_util::GetStringUTF16(
137 IDS_ASH_PHONE_HUB_ONBOARDING_DISMISS_DIALOG_TITLE));
138 base::string16 part1 = l10n_util::GetStringFUTF16(
139 IDS_ASH_PHONE_HUB_ONBOARDING_DISMISS_DIALOG_DESCRIPTION_PART_1,
140 ui::GetChromeOSDeviceName());
141 base::string16 part2 = l10n_util::GetStringUTF16(
142 IDS_ASH_PHONE_HUB_ONBOARDING_DISMISS_DIALOG_DESCRIPTION_PART_2);
143 // Uses "\n" to create a newline separator between two text paragraphs.
144 SetDescription(base::StrCat({part1, base::ASCIIToUTF16("\n\n"), part2}));
145
146 // Adds "Ok, got it" button.
147 auto ack_button = std::make_unique<InterstitialViewButton>(
148 base::BindRepeating(&OnboardingDismissPromptView::ButtonPressed,
149 base::Unretained(this)),
150 l10n_util::GetStringUTF16(
151 IDS_ASH_PHONE_HUB_ONBOARDING_DISMISS_DIALOG_OK_BUTTON),
152 /*paint_background=*/true);
153 ack_button->SetID(PhoneHubViewID::kOnboardingDismissAckButton);
154 AddButton(std::move(ack_button));
155 }
156
ButtonPressed()157 void ButtonPressed() {
158 LogInterstitialScreenEvent(InterstitialScreenEvent::kConfirm);
159
160 // Close Phone Hub bubble in current display.
161 views::Widget* const widget = GetWidget();
162 // |widget| is null when this function is called before the view is added to
163 // a widget (in unit tests).
164 if (!widget)
165 return;
166 int64_t current_display_id =
167 display::Screen::GetScreen()
168 ->GetDisplayNearestWindow(widget->GetNativeWindow())
169 .id();
170 Shell::GetRootWindowControllerWithDisplayId(current_display_id)
171 ->GetStatusAreaWidget()
172 ->phone_hub_tray()
173 ->CloseBubble();
174 }
175
176 // PhoneHubInterstitialView:
OnBubbleClose()177 void OnBubbleClose() override { onboarding_ui_tracker_->DismissSetupUi(); }
178
GetScreenForMetrics() const179 Screen GetScreenForMetrics() const override {
180 return Screen::kOnboardingDismissPrompt;
181 }
182
183 chromeos::phonehub::OnboardingUiTracker* onboarding_ui_tracker_ = nullptr;
184 };
185
186 // OnboardingView -------------------------------------------------------------
OnboardingView(chromeos::phonehub::OnboardingUiTracker * onboarding_ui_tracker,Delegate * delegate,OnboardingFlow onboarding_flow)187 OnboardingView::OnboardingView(
188 chromeos::phonehub::OnboardingUiTracker* onboarding_ui_tracker,
189 Delegate* delegate,
190 OnboardingFlow onboarding_flow)
191 : onboarding_ui_tracker_(onboarding_ui_tracker), delegate_(delegate) {
192 SetID(PhoneHubViewID::kOnboardingView);
193
194 SetLayoutManager(std::make_unique<views::FillLayout>());
195 main_view_ = AddChildView(std::make_unique<OnboardingMainView>(
196 onboarding_ui_tracker_, this, onboarding_flow));
197
198 LogInterstitialScreenEvent(InterstitialScreenEvent::kShown);
199 }
200
201 OnboardingView::~OnboardingView() = default;
202
OnBubbleClose()203 void OnboardingView::OnBubbleClose() {
204 main_view_->OnBubbleClose();
205 }
206
GetScreenForMetrics() const207 Screen OnboardingView::GetScreenForMetrics() const {
208 return main_view_->GetScreenForMetrics();
209 }
210
ShowDismissPrompt()211 void OnboardingView::ShowDismissPrompt() {
212 DCHECK(main_view_);
213
214 LogInterstitialScreenEvent(InterstitialScreenEvent::kShown);
215
216 RemoveChildView(main_view_);
217 main_view_ = AddChildView(
218 std::make_unique<OnboardingDismissPromptView>(onboarding_ui_tracker_));
219
220 // We don't show status header view on top for the dismiss prompt.
221 DCHECK(delegate_);
222 delegate_->HideStatusHeaderView();
223 }
224
225 BEGIN_METADATA(OnboardingView, views::View)
226 END_METADATA
227
228 } // namespace ash
229