1// Copyright 2013 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 "chrome/browser/chromeos/first_run/first_run_controller.h"
6
7#include "ash/shell.h"
8#include "base/logging.h"
9#include "base/message_loop/message_loop.h"
10#include "base/metrics/histogram.h"
11#include "chrome/browser/chromeos/first_run/first_run_view.h"
12#include "chrome/browser/chromeos/first_run/metrics.h"
13#include "chrome/browser/chromeos/first_run/steps/app_list_step.h"
14#include "chrome/browser/chromeos/first_run/steps/help_step.h"
15#include "chrome/browser/chromeos/first_run/steps/tray_step.h"
16#include "chrome/browser/chromeos/profiles/profile_helper.h"
17#include "chrome/browser/ui/chrome_pages.h"
18#include "components/user_manager/user_manager.h"
19#include "ui/views/widget/widget.h"
20
21namespace {
22
23size_t NONE_STEP_INDEX = std::numeric_limits<size_t>::max();
24
25// Instance of currently running controller, or NULL if controller is not
26// running now.
27chromeos::FirstRunController* g_instance;
28
29void RecordCompletion(chromeos::first_run::TutorialCompletion type) {
30  UMA_HISTOGRAM_ENUMERATION("CrosFirstRun.TutorialCompletion",
31                            type,
32                            chromeos::first_run::TUTORIAL_COMPLETION_SIZE);
33}
34
35}  // namespace
36
37namespace chromeos {
38
39FirstRunController::~FirstRunController() {}
40
41// static
42void FirstRunController::Start() {
43#if !defined(USE_ATHENA)
44  // crbug.com/413914
45  if (g_instance) {
46    LOG(WARNING) << "First-run tutorial is running already.";
47    return;
48  }
49  g_instance = new FirstRunController();
50  g_instance->Init();
51#endif
52}
53
54// static
55void FirstRunController::Stop() {
56#if !defined(USE_ATHENA)
57  if (!g_instance) {
58    LOG(WARNING) << "First-run tutorial is not running.";
59    return;
60  }
61  g_instance->Finalize();
62  base::MessageLoop::current()->DeleteSoon(FROM_HERE, g_instance);
63  g_instance = NULL;
64#endif
65}
66
67FirstRunController* FirstRunController::GetInstanceForTest() {
68  return g_instance;
69}
70
71FirstRunController::FirstRunController()
72    : actor_(NULL),
73      current_step_index_(NONE_STEP_INDEX),
74      user_profile_(NULL) {
75}
76
77void FirstRunController::Init() {
78  start_time_ = base::Time::Now();
79  user_manager::UserManager* user_manager = user_manager::UserManager::Get();
80  user_profile_ = ProfileHelper::Get()->GetProfileByUserUnsafe(
81      user_manager->GetActiveUser());
82
83  shell_helper_.reset(ash::Shell::GetInstance()->CreateFirstRunHelper());
84  shell_helper_->AddObserver(this);
85
86  FirstRunView* view = new FirstRunView();
87  view->Init(user_profile_);
88  shell_helper_->GetOverlayWidget()->SetContentsView(view);
89  actor_ = view->GetActor();
90  actor_->set_delegate(this);
91  shell_helper_->GetOverlayWidget()->Show();
92  view->RequestFocus();
93  web_contents_for_tests_ = view->GetWebContents();
94
95  if (actor_->IsInitialized())
96    OnActorInitialized();
97}
98
99void FirstRunController::Finalize() {
100  int furthest_step = current_step_index_ == NONE_STEP_INDEX
101                          ? steps_.size() - 1
102                          : current_step_index_;
103  UMA_HISTOGRAM_ENUMERATION("CrosFirstRun.FurthestStep",
104                            furthest_step,
105                            steps_.size());
106  UMA_HISTOGRAM_MEDIUM_TIMES("CrosFirstRun.TimeSpent",
107                             base::Time::Now() - start_time_);
108  if (GetCurrentStep())
109    GetCurrentStep()->OnBeforeHide();
110  steps_.clear();
111  if (actor_)
112    actor_->set_delegate(NULL);
113  actor_ = NULL;
114  shell_helper_->RemoveObserver(this);
115  shell_helper_.reset();
116}
117
118void FirstRunController::OnActorInitialized() {
119  RegisterSteps();
120  ShowNextStep();
121}
122
123void FirstRunController::OnNextButtonClicked(const std::string& step_name) {
124  DCHECK(GetCurrentStep() && GetCurrentStep()->name() == step_name);
125  GetCurrentStep()->OnBeforeHide();
126  actor_->HideCurrentStep();
127}
128
129void FirstRunController::OnHelpButtonClicked() {
130  RecordCompletion(first_run::TUTORIAL_COMPLETED_WITH_KEEP_EXPLORING);
131  on_actor_finalized_ = base::Bind(chrome::ShowHelpForProfile,
132                                   user_profile_,
133                                   chrome::HOST_DESKTOP_TYPE_ASH,
134                                   chrome::HELP_SOURCE_MENU);
135  actor_->Finalize();
136}
137
138void FirstRunController::OnStepHidden(const std::string& step_name) {
139  DCHECK(GetCurrentStep() && GetCurrentStep()->name() == step_name);
140  GetCurrentStep()->OnAfterHide();
141  if (!actor_->IsFinalizing())
142    ShowNextStep();
143}
144
145void FirstRunController::OnStepShown(const std::string& step_name) {
146  DCHECK(GetCurrentStep() && GetCurrentStep()->name() == step_name);
147}
148
149void FirstRunController::OnActorFinalized() {
150  if (!on_actor_finalized_.is_null())
151    on_actor_finalized_.Run();
152  Stop();
153}
154
155void FirstRunController::OnActorDestroyed() {
156  // Normally this shouldn't happen because we are implicitly controlling
157  // actor's lifetime.
158  NOTREACHED() <<
159    "FirstRunActor destroyed before FirstRunController::Finalize.";
160}
161
162void FirstRunController::OnCancelled() {
163  RecordCompletion(first_run::TUTORIAL_NOT_FINISHED);
164  Stop();
165}
166
167void FirstRunController::RegisterSteps() {
168  steps_.push_back(make_linked_ptr(
169      new first_run::AppListStep(shell_helper_.get(), actor_)));
170  steps_.push_back(make_linked_ptr(
171      new first_run::TrayStep(shell_helper_.get(), actor_)));
172  steps_.push_back(make_linked_ptr(
173      new first_run::HelpStep(shell_helper_.get(), actor_)));
174}
175
176void FirstRunController::ShowNextStep() {
177  AdvanceStep();
178  if (!GetCurrentStep()) {
179    actor_->Finalize();
180    RecordCompletion(first_run::TUTORIAL_COMPLETED_WITH_GOT_IT);
181    return;
182  }
183  GetCurrentStep()->Show();
184}
185
186void FirstRunController::AdvanceStep() {
187  if (current_step_index_ == NONE_STEP_INDEX)
188    current_step_index_ = 0;
189  else
190    ++current_step_index_;
191  if (current_step_index_ >= steps_.size())
192    current_step_index_ = NONE_STEP_INDEX;
193}
194
195first_run::Step* FirstRunController::GetCurrentStep() const {
196  return current_step_index_ != NONE_STEP_INDEX ?
197      steps_[current_step_index_].get() : NULL;
198}
199
200}  // namespace chromeos
201
202