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