1// Copyright 2014 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/metrics/signin_status_metrics_provider.h"
6
7#include <string>
8#include <vector>
9
10#include "base/bind.h"
11#include "base/message_loop/message_loop.h"
12#include "base/metrics/histogram.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/profiles/profile_info_cache.h"
16#include "chrome/browser/profiles/profile_manager.h"
17#include "chrome/browser/ui/browser.h"
18#include "chrome/browser/ui/browser_list.h"
19#include "components/signin/core/browser/signin_manager.h"
20
21#if !defined(OS_ANDROID)
22#include "chrome/browser/ui/browser_finder.h"
23#endif
24
25namespace {
26
27// The event of calling function ComputeCurrentSigninStatus and the errors
28// occurred during the function execution.
29enum ComputeSigninStatus {
30  ENTERED_COMPUTE_SIGNIN_STATUS,
31  ERROR_NO_PROFILE_FOUND,
32  NO_BROWSER_OPENED,
33  USER_SIGNIN_WHEN_STATUS_UNKNOWN,
34  USER_SIGNOUT_WHEN_STATUS_UNKNOWN,
35  TRY_TO_OVERRIDE_ERROR_STATUS,
36  COMPUTE_SIGNIN_STATUS_MAX,
37};
38
39void RecordComputeSigninStatusHistogram(ComputeSigninStatus status) {
40  UMA_HISTOGRAM_ENUMERATION("UMA.ComputeCurrentSigninStatus", status,
41                            COMPUTE_SIGNIN_STATUS_MAX);
42}
43
44}  // namespace
45
46SigninStatusMetricsProvider::SigninStatusMetricsProvider(bool is_test)
47    : signin_status_(UNKNOWN_SIGNIN_STATUS),
48      scoped_observer_(this),
49      is_test_(is_test),
50      weak_ptr_factory_(this) {
51  if (is_test_)
52    return;
53
54  // Postpone the initialization until all threads are created.
55  base::MessageLoop::current()->PostTask(
56      FROM_HERE,
57      base::Bind(&SigninStatusMetricsProvider::Initialize,
58                 weak_ptr_factory_.GetWeakPtr()));
59}
60
61SigninStatusMetricsProvider::~SigninStatusMetricsProvider() {
62  if (is_test_)
63    return;
64
65#if !defined(OS_ANDROID)
66  BrowserList::RemoveObserver(this);
67#endif
68
69  SigninManagerFactory* factory = SigninManagerFactory::GetInstance();
70  if (factory)
71    factory->RemoveObserver(this);
72}
73
74void SigninStatusMetricsProvider::ProvideGeneralMetrics(
75    metrics::ChromeUserMetricsExtension* uma_proto) {
76  UMA_HISTOGRAM_ENUMERATION(
77      "UMA.ProfileSignInStatus", signin_status_, SIGNIN_STATUS_MAX);
78  // After a histogram value is recorded, a new UMA session will be started, so
79  // we need to re-check the current sign-in status regardless of the previous
80  // recorded |signin_status_| value.
81  signin_status_ = UNKNOWN_SIGNIN_STATUS;
82  ComputeCurrentSigninStatus();
83}
84
85// static
86SigninStatusMetricsProvider* SigninStatusMetricsProvider::CreateInstance() {
87  return new SigninStatusMetricsProvider(false);
88}
89
90void SigninStatusMetricsProvider::OnBrowserAdded(Browser* browser) {
91  if (signin_status_ == MIXED_SIGNIN_STATUS)
92    return;
93
94  SigninManager* manager = SigninManagerFactory::GetForProfile(
95      browser->profile());
96
97  // Nothing will change if the opened browser is in incognito mode.
98  if (!manager)
99    return;
100
101  const bool signed_in = manager->IsAuthenticated();
102  UpdateStatusWhenBrowserAdded(signed_in);
103}
104
105void SigninStatusMetricsProvider::SigninManagerCreated(
106    SigninManagerBase* manager) {
107  // Whenever a new profile is created, a new SigninManagerBase will be created
108  // for it. This ensures that all sign-in or sign-out actions of all opened
109  // profiles are being monitored.
110  scoped_observer_.Add(manager);
111
112  // If the status is unknown, it means this is the first created
113  // SigninManagerBase and the corresponding profile should be the only opened
114  // profile.
115  if (signin_status_ == UNKNOWN_SIGNIN_STATUS) {
116    size_t signed_in_count =
117        manager->IsAuthenticated() ? 1 : 0;
118    UpdateInitialSigninStatus(1, signed_in_count);
119  }
120}
121
122void SigninStatusMetricsProvider::SigninManagerShutdown(
123    SigninManagerBase* manager) {
124  if (scoped_observer_.IsObserving(manager))
125    scoped_observer_.Remove(manager);
126}
127
128void SigninStatusMetricsProvider::GoogleSigninSucceeded(
129    const std::string& account_id,
130    const std::string& username,
131    const std::string& password) {
132  if (signin_status_ == ALL_PROFILES_NOT_SIGNED_IN) {
133    SetSigninStatus(MIXED_SIGNIN_STATUS);
134  } else if (signin_status_ == UNKNOWN_SIGNIN_STATUS) {
135    // There should have at least one browser opened if the user can sign in, so
136    // signin_status_ value should not be unknown.
137    SetSigninStatus(ERROR_GETTING_SIGNIN_STATUS);
138    RecordComputeSigninStatusHistogram(USER_SIGNIN_WHEN_STATUS_UNKNOWN);
139  }
140}
141
142void SigninStatusMetricsProvider::GoogleSignedOut(const std::string& account_id,
143                                                  const std::string& username) {
144  if (signin_status_ == ALL_PROFILES_SIGNED_IN) {
145    SetSigninStatus(MIXED_SIGNIN_STATUS);
146  } else if (signin_status_ == UNKNOWN_SIGNIN_STATUS) {
147    // There should have at least one browser opened if the user can sign out,
148    // so signin_status_ value should not be unknown.
149    SetSigninStatus(ERROR_GETTING_SIGNIN_STATUS);
150    RecordComputeSigninStatusHistogram(USER_SIGNOUT_WHEN_STATUS_UNKNOWN);
151  }
152}
153
154void SigninStatusMetricsProvider::Initialize() {
155  // Add observers.
156#if !defined(OS_ANDROID)
157  // On Android, there is always only one profile in any situation, opening new
158  // windows (which is possible with only some Android devices) will not change
159  // the opened profiles signin status.
160  BrowserList::AddObserver(this);
161#endif
162  SigninManagerFactory::GetInstance()->AddObserver(this);
163
164  // Start observing all already-created SigninManagers.
165  ProfileManager* profile_manager = g_browser_process->profile_manager();
166  std::vector<Profile*> profiles = profile_manager->GetLoadedProfiles();
167  for (size_t i = 0; i < profiles.size(); ++i) {
168    SigninManager* manager = SigninManagerFactory::GetForProfileIfExists(
169        profiles[i]);
170    if (manager) {
171      DCHECK(!scoped_observer_.IsObserving(manager));
172      scoped_observer_.Add(manager);
173    }
174  }
175
176  // It is possible that when this object is created, no SigninManager is
177  // created yet, for example, when Chrome is opened for the first time after
178  // installation on desktop, or when Chrome on Android is loaded into memory.
179  if (profiles.empty()) {
180    SetSigninStatus(UNKNOWN_SIGNIN_STATUS);
181  } else {
182    ComputeCurrentSigninStatus();
183  }
184}
185
186void SigninStatusMetricsProvider::UpdateInitialSigninStatus(
187    size_t total_count,
188    size_t signed_in_profiles_count) {
189  // total_count is known to be bigger than 0.
190  if (signed_in_profiles_count == 0) {
191    SetSigninStatus(ALL_PROFILES_NOT_SIGNED_IN);
192  } else if (total_count == signed_in_profiles_count) {
193    SetSigninStatus(ALL_PROFILES_SIGNED_IN);
194  } else {
195    SetSigninStatus(MIXED_SIGNIN_STATUS);
196  }
197}
198
199void SigninStatusMetricsProvider::UpdateStatusWhenBrowserAdded(bool signed_in) {
200#if !defined(OS_ANDROID)
201  if ((signin_status_ == ALL_PROFILES_NOT_SIGNED_IN && signed_in) ||
202      (signin_status_ == ALL_PROFILES_SIGNED_IN && !signed_in)) {
203    SetSigninStatus(MIXED_SIGNIN_STATUS);
204  } else if (signin_status_ == UNKNOWN_SIGNIN_STATUS) {
205    // If when function ProvideGeneralMetrics() is called, Chrome is
206    // running in the background with no browser window opened, |signin_status_|
207    // will be reset to |UNKNOWN_SIGNIN_STATUS|. Then this newly added browser
208    // is the only opened browser/profile and its signin status represents
209    // the whole status.
210    SetSigninStatus(signed_in ? ALL_PROFILES_SIGNED_IN :
211                                ALL_PROFILES_NOT_SIGNED_IN);
212  }
213#endif
214}
215
216void SigninStatusMetricsProvider::ComputeCurrentSigninStatus() {
217  ProfileManager* profile_manager = g_browser_process->profile_manager();
218  std::vector<Profile*> profile_list = profile_manager->GetLoadedProfiles();
219
220  size_t opened_profiles_count = 0;
221  size_t signed_in_profiles_count = 0;
222
223  for (size_t i = 0; i < profile_list.size(); ++i) {
224#if !defined(OS_ANDROID)
225    if (chrome::GetTotalBrowserCountForProfile(profile_list[i]) == 0) {
226      // The profile is loaded, but there's no opened browser for this profile.
227      continue;
228    }
229#endif
230    opened_profiles_count++;
231    SigninManager* manager = SigninManagerFactory::GetForProfile(
232        profile_list[i]->GetOriginalProfile());
233    if (manager && manager->IsAuthenticated())
234      signed_in_profiles_count++;
235  }
236
237  RecordComputeSigninStatusHistogram(ENTERED_COMPUTE_SIGNIN_STATUS);
238  if (profile_list.empty()) {
239    // This should not happen. If it does, record it in histogram.
240    RecordComputeSigninStatusHistogram(ERROR_NO_PROFILE_FOUND);
241    SetSigninStatus(ERROR_GETTING_SIGNIN_STATUS);
242  } else if (opened_profiles_count == 0) {
243    // The code indicates that Chrome is running in the background but no
244    // browser window is opened.
245    RecordComputeSigninStatusHistogram(NO_BROWSER_OPENED);
246    SetSigninStatus(UNKNOWN_SIGNIN_STATUS);
247  } else {
248    UpdateInitialSigninStatus(opened_profiles_count, signed_in_profiles_count);
249  }
250}
251
252void SigninStatusMetricsProvider::SetSigninStatus(
253    SigninStatusMetricsProvider::ProfilesSigninStatus new_status) {
254  if (signin_status_ == ERROR_GETTING_SIGNIN_STATUS) {
255    RecordComputeSigninStatusHistogram(TRY_TO_OVERRIDE_ERROR_STATUS);
256    return;
257  }
258  signin_status_ = new_status;
259}
260
261SigninStatusMetricsProvider::ProfilesSigninStatus
262SigninStatusMetricsProvider::GetSigninStatusForTesting() {
263  return signin_status_;
264}
265