blob: db2bce72ffbe36fdb13082963455ca04ff4a57de [file] [log] [blame]
// Copyright 2019 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_base.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 "components/gcm_driver/instance_id/instance_id_driver.h"
#include "components/invalidation/impl/fcm_network_handler.h"
#include "components/invalidation/impl/invalidation_prefs.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"
namespace invalidation {
namespace {
const char kApplicationName[] = "com.google.chrome.fcm.invalidations";
// Sender ID coming from the Firebase console.
const char kInvalidationGCMSenderId[] = "8181035976";
// 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
FCMInvalidationServiceBase::FCMInvalidationServiceBase(
FCMNetworkHandlerCallback fcm_network_handler_callback,
PerUserTopicRegistrationManagerCallback
per_user_topic_registration_manager_callback,
instance_id::InstanceIDDriver* instance_id_driver,
PrefService* pref_service,
const std::string& sender_id)
: sender_id_(sender_id.empty() ? kInvalidationGCMSenderId : sender_id),
invalidator_registrar_(pref_service,
sender_id_,
sender_id_ == kInvalidationGCMSenderId),
fcm_network_handler_callback_(std::move(fcm_network_handler_callback)),
per_user_topic_registration_manager_callback_(
std::move(per_user_topic_registration_manager_callback)),
instance_id_driver_(instance_id_driver),
pref_service_(pref_service),
update_was_requested_(false) {}
FCMInvalidationServiceBase::~FCMInvalidationServiceBase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
invalidator_registrar_.UpdateInvalidatorState(
syncer::INVALIDATOR_SHUTTING_DOWN);
if (IsStarted())
StopInvalidator();
}
// static
void FCMInvalidationServiceBase::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterStringPref(
invalidation::prefs::kFCMInvalidationClientIDCacheDeprecated,
/*default_value=*/std::string());
registry->RegisterDictionaryPref(
invalidation::prefs::kInvalidationClientIDCache);
}
void FCMInvalidationServiceBase::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 FCMInvalidationServiceBase::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_.GetHandlerNameToTopicsMap());
return true;
}
void FCMInvalidationServiceBase::UnregisterInvalidationHandler(
syncer::InvalidationHandler* handler) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << "Unregistering";
invalidator_registrar_.UnregisterHandler(handler);
logger_.OnUnregistration(handler->GetOwnerName());
}
syncer::InvalidatorState FCMInvalidationServiceBase::GetInvalidatorState()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (invalidation_listener_) {
DVLOG(2) << "GetInvalidatorState returning "
<< invalidator_registrar_.GetInvalidatorState();
return invalidator_registrar_.GetInvalidatorState();
}
DVLOG(2) << "Invalidator currently stopped";
return syncer::STOPPED;
}
std::string FCMInvalidationServiceBase::GetInvalidatorClientId() const {
return client_id_;
}
InvalidationLogger* FCMInvalidationServiceBase::GetInvalidationLogger() {
return &logger_;
}
void FCMInvalidationServiceBase::RequestDetailedStatus(
base::RepeatingCallback<void(const base::DictionaryValue&)> return_callback)
const {
return_callback.Run(CollectDebugData());
invalidator_registrar_.RequestDetailedStatus(return_callback);
if (IsStarted()) {
invalidation_listener_->RequestDetailedStatus(return_callback);
}
}
void FCMInvalidationServiceBase::OnInvalidate(
const syncer::TopicInvalidationMap& invalidation_map) {
invalidator_registrar_.DispatchInvalidationsToHandlers(invalidation_map);
logger_.OnInvalidation(
ConvertTopicInvalidationMapToObjectIdInvalidationMap(invalidation_map));
}
void FCMInvalidationServiceBase::OnInvalidatorStateChange(
syncer::InvalidatorState state) {
ReportInvalidatorState(state);
invalidator_registrar_.UpdateInvalidatorState(state);
logger_.OnStateChange(state);
}
void FCMInvalidationServiceBase::InitForTest(
std::unique_ptr<syncer::FCMInvalidationListener> invalidation_listener) {
// Here we perform the equivalent of Init() and StartInvalidator(), but with
// some minor changes to account for the fact that we're injecting the
// invalidation_listener.
// StartInvalidator initializes the invalidation_listener and starts it.
invalidation_listener_ = std::move(invalidation_listener);
invalidation_listener_->StartForTest(this);
DoUpdateRegisteredIdsIfNeeded();
}
base::DictionaryValue FCMInvalidationServiceBase::CollectDebugData() const {
base::DictionaryValue status;
status.SetString(
"InvalidationService.IID-requested",
base::TimeFormatShortDateAndTime(diagnostic_info_.instance_id_requested));
status.SetString(
"InvalidationService.IID-received",
base::TimeFormatShortDateAndTime(diagnostic_info_.instance_id_received));
status.SetString(
"InvalidationService.Service-stopped",
base::TimeFormatShortDateAndTime(diagnostic_info_.service_was_stopped));
status.SetString(
"InvalidationService.Service-started",
base::TimeFormatShortDateAndTime(diagnostic_info_.service_was_started));
return status;
}
void FCMInvalidationServiceBase::ReportInvalidatorState(
syncer::InvalidatorState state) {
UMA_HISTOGRAM_ENUMERATION("Invalidations.StatusChanged", state);
}
bool FCMInvalidationServiceBase::IsStarted() const {
return invalidation_listener_ != nullptr;
}
void FCMInvalidationServiceBase::StartInvalidator() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!invalidation_listener_);
diagnostic_info_.service_was_started = base::Time::Now();
auto network =
fcm_network_handler_callback_.Run(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 InvalidationListener first, because it registers the
// handler for the incoming messages, which is crucial on Android, because on
// the startup cached messages might exists.
invalidation_listener_ =
std::make_unique<syncer::FCMInvalidationListener>(std::move(network));
auto registration_manager = per_user_topic_registration_manager_callback_.Run(
sender_id_, /*migrate_prefs=*/sender_id_ == kInvalidationGCMSenderId);
invalidation_listener_->Start(this, std::move(registration_manager));
PopulateClientID();
DoUpdateRegisteredIdsIfNeeded();
}
void FCMInvalidationServiceBase::StopInvalidator() {
DCHECK(invalidation_listener_);
diagnostic_info_.service_was_stopped = base::Time::Now();
// TODO(melandory): reset the network.
invalidation_listener_.reset();
}
void FCMInvalidationServiceBase::StopInvalidatorPermanently() {
StopInvalidator();
if (!client_id_.empty())
ResetClientID();
}
void FCMInvalidationServiceBase::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(&FCMInvalidationServiceBase::OnInstanceIdReceived,
base::Unretained(this)));
}
void FCMInvalidationServiceBase::ResetClientID() {
instance_id::InstanceID* instance_id =
instance_id_driver_->GetInstanceID(GetApplicationName());
instance_id->DeleteID(
base::Bind(&FCMInvalidationServiceBase::OnDeleteIDCompleted,
base::Unretained(this)));
DictionaryPrefUpdate update(pref_service_, prefs::kInvalidationClientIDCache);
update->RemoveKey(sender_id_);
}
void FCMInvalidationServiceBase::OnInstanceIdReceived(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_.UpdateInvalidatorInstanceId(id);
}
}
void FCMInvalidationServiceBase::OnDeleteIDCompleted(
instance_id::InstanceID::Result result) {
UMA_HISTOGRAM_ENUMERATION("FCMInvalidations.ResetClientIDStatus", result,
instance_id::InstanceID::Result::LAST_RESULT + 1);
}
void FCMInvalidationServiceBase::DoUpdateRegisteredIdsIfNeeded() {
if (!invalidation_listener_ || !update_was_requested_)
return;
auto subscribed_topics = invalidator_registrar_.GetAllSubscribedTopics();
invalidation_listener_->UpdateRegisteredTopics(subscribed_topics);
update_was_requested_ = false;
}
const std::string FCMInvalidationServiceBase::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_});
}
} // namespace invalidation