blob: 78e04a86758dcbc51aab3b927ca5a2042f70ec7f [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/invalidation/impl/fcm_invalidation_service.h"
#include <memory>
#include "base/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "build/build_config.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"
#include "components/invalidation/impl/fcm_invalidator.h"
#include "components/invalidation/impl/fcm_network_handler.h"
#include "components/invalidation/impl/invalidation_prefs.h"
#include "components/invalidation/impl/invalidation_service_util.h"
#include "components/invalidation/impl/invalidation_switches.h"
#include "components/invalidation/impl/invalidator.h"
#include "components/invalidation/public/invalidation_util.h"
#include "components/invalidation/public/invalidator_state.h"
#include "components/invalidation/public/object_id_invalidation_map.h"
#include "components/invalidation/public/topic_invalidation_map.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "google_apis/gaia/gaia_constants.h"
namespace invalidation {
namespace {
const char kApplicationName[] = "com.google.chrome.fcm.invalidations";
// Sender ID coming from the Firebase console.
const char kInvalidationGCMSenderId[] = "8181035976";
void ReportInvalidatorState(syncer::InvalidatorState state) {
UMA_HISTOGRAM_ENUMERATION("Invalidations.StatusChanged", state);
}
// Added in M76.
void MigratePrefs(PrefService* prefs, const std::string& sender_id) {
if (!prefs->HasPrefPath(prefs::kFCMInvalidationClientIDCacheDeprecated)) {
return;
}
DictionaryPrefUpdate update(prefs, prefs::kInvalidationClientIDCache);
update->SetString(
sender_id,
prefs->GetString(prefs::kFCMInvalidationClientIDCacheDeprecated));
prefs->ClearPref(prefs::kFCMInvalidationClientIDCacheDeprecated);
}
} // namespace
FCMInvalidationService::FCMInvalidationService(
IdentityProvider* identity_provider,
gcm::GCMDriver* gcm_driver,
instance_id::InstanceIDDriver* instance_id_driver,
PrefService* pref_service,
const syncer::ParseJSONCallback& parse_json,
network::mojom::URLLoaderFactory* loader_factory,
const std::string& sender_id)
: sender_id_(sender_id.empty() ? kInvalidationGCMSenderId : sender_id),
invalidator_registrar_(pref_service,
sender_id_,
sender_id_ == kInvalidationGCMSenderId),
gcm_driver_(gcm_driver),
instance_id_driver_(instance_id_driver),
identity_provider_(identity_provider),
pref_service_(pref_service),
parse_json_(parse_json),
loader_factory_(loader_factory),
update_was_requested_(false) {}
FCMInvalidationService::~FCMInvalidationService() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
invalidator_registrar_.UpdateInvalidatorState(
syncer::INVALIDATOR_SHUTTING_DOWN);
identity_provider_->RemoveObserver(this);
if (IsStarted())
StopInvalidator();
}
void FCMInvalidationService::Init() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (IsReadyToStart()) {
StartInvalidator();
} else {
if (identity_provider_->GetActiveAccountId().empty()) {
ReportInvalidatorState(syncer::NOT_STARTED_NO_ACTIVE_ACCOUNT);
} else {
ReportInvalidatorState(syncer::NOT_STARTED_NO_REFRESH_TOKEN);
}
}
identity_provider_->AddObserver(this);
}
// static
void FCMInvalidationService::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterStringPref(
invalidation::prefs::kFCMInvalidationClientIDCacheDeprecated,
/*default_value=*/std::string());
registry->RegisterDictionaryPref(
invalidation::prefs::kInvalidationClientIDCache);
}
void FCMInvalidationService::InitForTest(syncer::Invalidator* invalidator) {
// Here we perform the equivalent of Init() and StartInvalidator(), but with
// some minor changes to account for the fact that we're injecting the
// invalidator.
invalidator_.reset(invalidator);
invalidator_->RegisterHandler(this);
DoUpdateRegisteredIdsIfNeeded();
}
void FCMInvalidationService::RegisterInvalidationHandler(
syncer::InvalidationHandler* handler) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << "Registering an invalidation handler";
invalidator_registrar_.RegisterHandler(handler);
// Populate the id for newly registered handlers.
handler->OnInvalidatorClientIdChange(client_id_);
logger_.OnRegistration(handler->GetOwnerName());
}
bool FCMInvalidationService::UpdateRegisteredInvalidationIds(
syncer::InvalidationHandler* handler,
const syncer::ObjectIdSet& ids) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
update_was_requested_ = true;
DVLOG(2) << "Registering ids: " << ids.size();
syncer::Topics topics = ConvertIdsToTopics(ids, handler);
if (!invalidator_registrar_.UpdateRegisteredTopics(handler, topics))
return false;
DoUpdateRegisteredIdsIfNeeded();
logger_.OnUpdateTopics(invalidator_registrar_.GetSanitizedHandlersIdsMap());
return true;
}
void FCMInvalidationService::UnregisterInvalidationHandler(
syncer::InvalidationHandler* handler) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << "Unregistering";
invalidator_registrar_.UnregisterHandler(handler);
logger_.OnUnregistration(handler->GetOwnerName());
}
syncer::InvalidatorState FCMInvalidationService::GetInvalidatorState() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (invalidator_) {
DVLOG(2) << "GetInvalidatorState returning "
<< invalidator_->GetInvalidatorState();
return invalidator_->GetInvalidatorState();
}
DVLOG(2) << "Invalidator currently stopped";
return syncer::STOPPED;
}
std::string FCMInvalidationService::GetInvalidatorClientId() const {
return client_id_;
}
InvalidationLogger* FCMInvalidationService::GetInvalidationLogger() {
return &logger_;
}
void FCMInvalidationService::RequestDetailedStatus(
base::RepeatingCallback<void(const base::DictionaryValue&)> return_callback)
const {
return_callback.Run(diagnostic_info_.CollectDebugData());
invalidator_registrar_.RequestDetailedStatus(return_callback);
if (identity_provider_) {
identity_provider_->RequestDetailedStatus(return_callback);
}
if (IsStarted()) {
invalidator_->RequestDetailedStatus(return_callback);
}
}
void FCMInvalidationService::OnActiveAccountLogin() {
diagnostic_info_.active_account_login = base::Time::Now();
diagnostic_info_.was_already_started_on_login = IsStarted();
diagnostic_info_.was_ready_to_start_on_login = IsReadyToStart();
diagnostic_info_.active_account_id = identity_provider_->GetActiveAccountId();
if (IsStarted()) {
return;
}
if (IsReadyToStart()) {
StartInvalidator();
} else {
ReportInvalidatorState(syncer::NOT_STARTED_NO_REFRESH_TOKEN);
}
}
void FCMInvalidationService::OnActiveAccountRefreshTokenUpdated() {
diagnostic_info_.active_account_token_updated = base::Time::Now();
if (!IsStarted() && IsReadyToStart())
StartInvalidator();
}
void FCMInvalidationService::OnActiveAccountLogout() {
diagnostic_info_.active_account_logged_out = base::Time::Now();
diagnostic_info_.active_account_id = std::string();
if (IsStarted()) {
StopInvalidator();
if (!client_id_.empty())
ResetClientID();
}
}
void FCMInvalidationService::OnInvalidatorStateChange(
syncer::InvalidatorState state) {
ReportInvalidatorState(state);
invalidator_registrar_.UpdateInvalidatorState(state);
logger_.OnStateChange(state);
}
void FCMInvalidationService::OnIncomingInvalidation(
const syncer::ObjectIdInvalidationMap& invalidation_map) {
invalidator_registrar_.DispatchInvalidationsToHandlers(
ConvertObjectIdInvalidationMapToTopicInvalidationMap(invalidation_map));
logger_.OnInvalidation(invalidation_map);
}
std::string FCMInvalidationService::GetOwnerName() const {
if (sender_id_ == kInvalidationGCMSenderId) {
return "FCM";
}
return "FCM" + sender_id_;
}
bool FCMInvalidationService::IsReadyToStart() {
bool valid_account_info_available =
identity_provider_->IsActiveAccountWithRefreshToken();
#if defined(OS_ANDROID)
// IsReadyToStart checks if account is available (active account logged in
// and token is available). As currently observed, FCMInvalidationService
// isn't always notified on Android when token is available.
if (base::FeatureList::IsEnabled(
invalidation::switches::
kFCMInvalidationsStartOnceActiveAccountAvailable)) {
valid_account_info_available =
!identity_provider_->GetActiveAccountId().empty();
}
#endif
if (!valid_account_info_available) {
DVLOG(2) << "Not starting FCMInvalidationService: "
<< "active account is not available";
return false;
}
return true;
}
bool FCMInvalidationService::IsStarted() const {
return invalidator_ != nullptr;
}
void FCMInvalidationService::StartInvalidator() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!invalidator_);
DCHECK(IsReadyToStart());
diagnostic_info_.service_was_started = base::Time::Now();
auto network = std::make_unique<syncer::FCMNetworkHandler>(
gcm_driver_, instance_id_driver_, sender_id_, GetApplicationName());
// The order of calls is important. Do not change.
// We should start listening before requesting the id, because
// valid id is only generated, once there is an app handler
// for the app. StartListening registers the app handler.
// We should create invalidator first, because it registers the handler
// for the incoming messages, which is crutial on Android, because on the
// startup cached messages might exists.
invalidator_ = std::make_unique<syncer::FCMInvalidator>(
std::move(network), identity_provider_, pref_service_, loader_factory_,
parse_json_, sender_id_, sender_id_ == kInvalidationGCMSenderId);
PopulateClientID();
invalidator_->RegisterHandler(this);
DoUpdateRegisteredIdsIfNeeded();
}
void FCMInvalidationService::StopInvalidator() {
DCHECK(invalidator_);
diagnostic_info_.service_was_stopped = base::Time::Now();
// TODO(melandory): reset the network.
invalidator_->UnregisterHandler(this);
invalidator_.reset();
}
void FCMInvalidationService::PopulateClientID() {
diagnostic_info_.instance_id_requested = base::Time::Now();
if (sender_id_ == kInvalidationGCMSenderId) {
MigratePrefs(pref_service_, sender_id_);
}
const std::string* client_id_pref =
pref_service_->GetDictionary(prefs::kInvalidationClientIDCache)
->FindStringKey(sender_id_);
client_id_ = client_id_pref ? *client_id_pref : "";
instance_id::InstanceID* instance_id =
instance_id_driver_->GetInstanceID(GetApplicationName());
instance_id->GetID(base::Bind(&FCMInvalidationService::OnInstanceIdRecieved,
base::Unretained(this)));
}
void FCMInvalidationService::ResetClientID() {
instance_id::InstanceID* instance_id =
instance_id_driver_->GetInstanceID(GetApplicationName());
instance_id->DeleteID(base::Bind(&FCMInvalidationService::OnDeleteIDCompleted,
base::Unretained(this)));
DictionaryPrefUpdate update(pref_service_, prefs::kInvalidationClientIDCache);
update->RemoveKey(sender_id_);
}
void FCMInvalidationService::OnInstanceIdRecieved(const std::string& id) {
diagnostic_info_.instance_id_received = base::Time::Now();
if (client_id_ != id) {
client_id_ = id;
DictionaryPrefUpdate update(pref_service_,
prefs::kInvalidationClientIDCache);
update->SetStringKey(sender_id_, id);
invalidator_registrar_.UpdateInvalidatorId(id);
}
}
void FCMInvalidationService::OnDeleteIDCompleted(
instance_id::InstanceID::Result) {
// TODO(meandory): report metric in case of unsucesfull deletion.
}
void FCMInvalidationService::DoUpdateRegisteredIdsIfNeeded() {
if (!invalidator_ || !update_was_requested_)
return;
auto registered_ids = invalidator_registrar_.GetAllRegisteredIds();
CHECK(invalidator_->UpdateRegisteredIds(this, registered_ids));
update_was_requested_ = false;
}
const std::string FCMInvalidationService::GetApplicationName() {
// If using the default |sender_id_|, use the bare |kApplicationName|, so the
// old app name is maintained.
if (sender_id_ == kInvalidationGCMSenderId) {
return kApplicationName;
}
return base::StrCat({kApplicationName, "-", sender_id_});
}
FCMInvalidationService::Diagnostics::Diagnostics() {}
base::DictionaryValue FCMInvalidationService::Diagnostics::CollectDebugData()
const {
base::DictionaryValue status;
status.SetString("InvalidationService.Active-account-login",
base::TimeFormatShortDateAndTime(active_account_login));
status.SetString(
"InvalidationService.Active-account-token-updated",
base::TimeFormatShortDateAndTime(active_account_token_updated));
status.SetString("InvalidationService.Active-account-logged-out",
base::TimeFormatShortDateAndTime(active_account_logged_out));
status.SetString("InvalidationService.IID-requested",
base::TimeFormatShortDateAndTime(instance_id_requested));
status.SetString("InvalidationService.IID-received",
base::TimeFormatShortDateAndTime(instance_id_received));
status.SetString("InvalidationService.Service-stopped",
base::TimeFormatShortDateAndTime(service_was_stopped));
status.SetString("InvalidationService.Service-started",
base::TimeFormatShortDateAndTime(service_was_started));
status.SetBoolean("InvalidationService.Started-on-active-account-login",
was_already_started_on_login);
status.SetBoolean(
"InvalidationService.Ready-to-start-on-active-account-login",
was_ready_to_start_on_login);
status.SetString("InvalidationService.Active-account-id", active_account_id);
return status;
}
} // namespace invalidation