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