blob: d82665404e57a853dc798f02fa97db71efa6ea83 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/invalidations/fcm_handler.h"
#include <map>
#include <utility>
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "components/gcm_driver/gcm_driver.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"
#include "components/sync/invalidations/fcm_registration_token_observer.h"
#include "components/sync/invalidations/invalidations_listener.h"
namespace syncer {
// Lower bound time between two token validations when listening.
constexpr int kTokenValidationPeriodMinutesDefault = 60 * 24;
constexpr int kInstanceIDTokenTTLSeconds = 14 * 24 * 60 * 60; // 2 weeks.
// Limits the number of last received buffered messages.
constexpr size_t kMaxBufferedLastFcmMessages = 20;
FCMHandler::FCMHandler(gcm::GCMDriver* gcm_driver,
instance_id::InstanceIDDriver* instance_id_driver,
const std::string& sender_id,
const std::string& app_id)
: gcm_driver_(gcm_driver),
instance_id_driver_(instance_id_driver),
sender_id_(sender_id),
app_id_(app_id) {}
FCMHandler::~FCMHandler() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
StopListening();
}
void FCMHandler::StartListening() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!IsListening());
DCHECK(last_received_messages_.empty());
DCHECK(!fcm_registration_token_.has_value());
// Note that AddAppHandler() causes an immediate replay of all received
// messages in background on Android. Those messages will be stored in
// `last_received_messages_` and delivered to listeners once they have been
// added.
gcm_driver_->AddAppHandler(app_id_, this);
StartTokenFetch(/*is_validation=*/false);
}
void FCMHandler::StopListening() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// StopListening() may be called after StartListening() right away and
// DidRetrieveToken() won't be called.
if (IsListening()) {
gcm_driver_->RemoveAppHandler(app_id_);
fcm_registration_token_ = std::nullopt;
token_validation_timer_.Stop();
last_received_messages_.clear();
}
}
void FCMHandler::StopListeningPermanently() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (instance_id_driver_->ExistsInstanceID(app_id_)) {
instance_id_driver_->GetInstanceID(app_id_)->DeleteID(
/*callback=*/base::DoNothing());
}
const bool was_listening = IsListening();
const bool had_token = fcm_registration_token_.has_value();
StopListening();
if (was_listening && had_token) {
// After permanently stopping listening, the token is cleared.
// Observers should be notified of this change.
for (FCMRegistrationTokenObserver& token_observer : token_observers_) {
token_observer.OnFCMRegistrationTokenChanged();
}
}
}
const std::optional<std::string>& FCMHandler::GetFCMRegistrationToken() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return fcm_registration_token_;
}
void FCMHandler::ShutdownHandler() {
// Shutdown() should come before and it removes us from the list of app
// handlers of gcm::GCMDriver so this shouldn't ever been called.
NOTREACHED();
}
void FCMHandler::AddListener(InvalidationsListener* listener) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (listeners_.HasObserver(listener)) {
return;
}
listeners_.AddObserver(listener);
// Immediately replay any buffered messages received before the `listener`
// was added.
for (const std::string& message : last_received_messages_) {
listener->OnInvalidationReceived(message);
}
}
bool FCMHandler::HasListener(InvalidationsListener* listener) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return listeners_.HasObserver(listener);
}
void FCMHandler::RemoveListener(InvalidationsListener* listener) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
listeners_.RemoveObserver(listener);
}
void FCMHandler::OnStoreReset() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The FCM registration token is not stored by FCMHandler.
}
void FCMHandler::AddTokenObserver(FCMRegistrationTokenObserver* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
token_observers_.AddObserver(observer);
}
void FCMHandler::RemoveTokenObserver(FCMRegistrationTokenObserver* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
token_observers_.RemoveObserver(observer);
}
void FCMHandler::OnMessage(const std::string& app_id,
const gcm::IncomingMessage& message) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(app_id, app_id_);
if (last_received_messages_.size() >= kMaxBufferedLastFcmMessages) {
last_received_messages_.pop_front();
}
last_received_messages_.push_back(message.raw_data);
for (InvalidationsListener& listener : listeners_) {
listener.OnInvalidationReceived(message.raw_data);
}
}
void FCMHandler::OnMessagesDeleted(const std::string& app_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(app_id, app_id_);
}
void FCMHandler::OnSendError(const std::string& app_id,
const gcm::GCMClient::SendErrorDetails& details) {
// Should never be called because the invalidation service doesn't send GCM
// messages to the server.
NOTREACHED() << "FCMHandler doesn't send GCM messages.";
}
void FCMHandler::OnSendAcknowledged(const std::string& app_id,
const std::string& message_id) {
// Should never be called because the invalidation service doesn't send GCM
// messages to the server.
NOTREACHED() << "FCMHandler doesn't send GCM messages.";
}
bool FCMHandler::IsListening() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return gcm_driver_->GetAppHandler(app_id_) != nullptr;
}
void FCMHandler::DidRetrieveToken(base::TimeTicks fetch_time_for_metrics,
bool is_validation,
const std::string& subscription_token,
instance_id::InstanceID::Result result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Record histograms for the initial token requests only (called from
// StartListening()).
// TODO(crbug.com/40260679): record similar metrics for validation requests.
if (!is_validation) {
RecordInitialTokenRetrievalMetrics(fetch_time_for_metrics, result);
}
if (!IsListening()) {
// After we requested the token, `StopListening` has been called. Thus,
// ignore the token.
return;
}
if (result != instance_id::InstanceID::SUCCESS) {
DLOG(WARNING) << "Messaging subscription failed: " << result;
} else if (fcm_registration_token_ != subscription_token) {
// Notify observers only if the token has changed.
fcm_registration_token_ = subscription_token;
for (FCMRegistrationTokenObserver& token_observer : token_observers_) {
token_observer.OnFCMRegistrationTokenChanged();
}
}
ScheduleNextTokenValidation();
}
void FCMHandler::RecordInitialTokenRetrievalMetrics(
base::TimeTicks fetch_time_for_metrics,
instance_id::InstanceID::Result result) const {
base::UmaHistogramEnumeration("Sync.FCMInstanceIdTokenRetrievalStatus",
result);
if (result == instance_id::InstanceID::SUCCESS) {
base::UmaHistogramMediumTimes(
"Sync.FcmRegistrationTokenFetchTime",
base::TimeTicks::Now() - fetch_time_for_metrics);
}
}
void FCMHandler::ScheduleNextTokenValidation() {
DCHECK(IsListening());
token_validation_timer_.Start(
FROM_HERE, base::Minutes(kTokenValidationPeriodMinutesDefault),
base::BindOnce(&FCMHandler::StartTokenValidation,
weak_ptr_factory_.GetWeakPtr()));
}
void FCMHandler::StartTokenValidation() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsListening());
StartTokenFetch(/*is_validation=*/true);
}
void FCMHandler::StartTokenFetch(bool is_validation) {
instance_id_driver_->GetInstanceID(app_id_)->GetToken(
sender_id_, instance_id::kGCMScope,
/*time_to_live=*/base::Seconds(kInstanceIDTokenTTLSeconds),
/*flags=*/{instance_id::InstanceID::Flags::kIsLazy},
base::BindOnce(
&FCMHandler::DidRetrieveToken, weak_ptr_factory_.GetWeakPtr(),
/*fetch_time_for_metrics=*/base::TimeTicks::Now(), is_validation));
}
} // namespace syncer