blob: af15745d29ba5272c3d12f4aad80dd32f56ada89 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/notifications/notification_channels_provider_android.h"
#include <algorithm>
#include <utility>
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/default_clock.h"
#include "base/values.h"
#include "chrome/common/pref_names.h"
#include "components/content_settings/core/browser/content_settings_pref_provider.h"
#include "components/content_settings/core/browser/content_settings_rule.h"
#include "components/content_settings/core/browser/content_settings_utils.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_constraints.h"
#include "components/content_settings/core/common/content_settings_metadata.h"
#include "components/content_settings/core/common/content_settings_partition_key.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "url/gurl.h"
#include "url/url_constants.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/browser/notifications/jni_headers/NotificationSettingsBridge_jni.h"
using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef;
namespace {
constexpr char kPendingChannelId[] = "pending";
// Returns the callback to run when notification channel state changes.
base::RepeatingCallback<void(const NotificationChannel& channel)>&
GetChannelStateChangedCallback() {
static base::NoDestructor<
base::RepeatingCallback<void(const NotificationChannel& channel)>>
channel_state_changed_callback;
return *channel_state_changed_callback;
}
NotificationChannel CreatePendingChannel(const std::string& origin_string,
ContentSetting content_setting,
base::Time timestamp) {
NotificationChannelStatus status = NotificationChannelStatus::UNAVAILABLE;
switch (content_setting) {
case CONTENT_SETTING_ALLOW:
status = NotificationChannelStatus::ENABLED;
break;
case CONTENT_SETTING_BLOCK:
status = NotificationChannelStatus::BLOCKED;
break;
case CONTENT_SETTING_DEFAULT:
break;
default:
NOTREACHED();
}
return NotificationChannel(kPendingChannelId, origin_string, timestamp,
status);
}
std::string GetOriginStringFromPattern(const ContentSettingsPattern& pattern) {
url::Origin origin = url::Origin::Create(GURL(pattern.ToString()));
DCHECK(!origin.opaque());
return origin.Serialize();
}
class NotificationChannelsBridgeImpl
: public NotificationChannelsProviderAndroid::NotificationChannelsBridge {
public:
NotificationChannelsBridgeImpl(NotificationChannelsProviderAndroid*
notification_channel_provider_android)
: notification_channel_provider_android_(
notification_channel_provider_android) {
if (GetChannelStateChangedCallback().is_null()) {
GetChannelStateChangedCallback() = base::BindRepeating(
&NotificationChannelsBridgeImpl::OnChannelStateChanged,
weak_factory_.GetWeakPtr());
} else {
LOG(WARNING) << "Notification channels are used by two profiles. Changes"
"will only affect the first profile.";
}
}
~NotificationChannelsBridgeImpl() override {
GetChannelStateChangedCallback().Reset();
}
NotificationChannel CreateChannel(const std::string& origin,
const base::Time& timestamp,
bool enabled) override {
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobject> jchannel =
Java_NotificationSettingsBridge_createChannel(
env, origin, timestamp.ToInternalValue(), enabled);
return NotificationChannel(
Java_SiteChannel_getId(env, jchannel),
Java_SiteChannel_getOrigin(env, jchannel),
base::Time::FromInternalValue(
Java_SiteChannel_getTimestamp(env, jchannel)),
static_cast<NotificationChannelStatus>(
Java_SiteChannel_getStatus(env, jchannel)));
}
void DeleteChannel(const std::string& origin) override {
JNIEnv* env = AttachCurrentThread();
Java_NotificationSettingsBridge_deleteChannel(env, origin);
}
void GetChannels(NotificationChannelsProviderAndroid::GetChannelsCallback
callback) override {
JNIEnv* env = AttachCurrentThread();
NotificationChannelsProviderAndroid::GetChannelsCallback cb =
base::BindOnce(&NotificationChannelsBridgeImpl::OnGetChannelsDone,
weak_factory_.GetWeakPtr(), std::move(callback));
intptr_t callback_id = reinterpret_cast<intptr_t>(
new NotificationChannelsProviderAndroid::GetChannelsCallback(
std::move(cb)));
Java_NotificationSettingsBridge_getSiteChannels(env, callback_id);
}
private:
void OnGetChannelsDone(
NotificationChannelsProviderAndroid::GetChannelsCallback callback,
const std::vector<NotificationChannel>& channels) {
std::move(callback).Run(channels);
}
void OnChannelStateChanged(const NotificationChannel& channel) {
notification_channel_provider_android_->OnChannelStateChanged(channel);
}
raw_ptr<NotificationChannelsProviderAndroid>
notification_channel_provider_android_;
base::WeakPtrFactory<NotificationChannelsBridgeImpl> weak_factory_{this};
};
ContentSetting ChannelStatusToContentSetting(NotificationChannelStatus status) {
switch (status) {
case NotificationChannelStatus::ENABLED:
return CONTENT_SETTING_ALLOW;
case NotificationChannelStatus::BLOCKED:
return CONTENT_SETTING_BLOCK;
case NotificationChannelStatus::UNAVAILABLE:
NOTREACHED();
}
return CONTENT_SETTING_DEFAULT;
}
class ChannelsRuleIterator : public content_settings::RuleIterator {
public:
explicit ChannelsRuleIterator(std::vector<NotificationChannel> channels)
: channels_(std::move(channels)) {}
ChannelsRuleIterator(const ChannelsRuleIterator&) = delete;
ChannelsRuleIterator& operator=(const ChannelsRuleIterator&) = delete;
~ChannelsRuleIterator() override = default;
bool HasNext() const override { return index_ < channels_.size(); }
std::unique_ptr<content_settings::Rule> Next() override {
DCHECK(HasNext());
auto& channel = channels_[index_];
DCHECK_NE(channels_[index_].status, NotificationChannelStatus::UNAVAILABLE);
content_settings::RuleMetaData metadata;
metadata.set_last_modified(channel.timestamp);
std::unique_ptr<content_settings::Rule> rule =
std::make_unique<content_settings::Rule>(
ContentSettingsPattern::FromURLNoWildcard(GURL(channel.origin)),
ContentSettingsPattern::Wildcard(),
base::Value(ChannelStatusToContentSetting(channel.status)),
std::move(metadata));
index_++;
return rule;
}
private:
std::vector<NotificationChannel> channels_;
size_t index_ = 0;
};
} // anonymous namespace
static void JNI_NotificationSettingsBridge_OnGetSiteChannelsDone(
JNIEnv* env,
jlong callback_id,
const JavaParamRef<jobjectArray>& j_channels) {
std::vector<NotificationChannel> channels;
for (auto jchannel : j_channels.ReadElements<jobject>()) {
channels.emplace_back(Java_SiteChannel_getId(env, jchannel),
Java_SiteChannel_getOrigin(env, jchannel),
base::Time::FromInternalValue(
Java_SiteChannel_getTimestamp(env, jchannel)),
static_cast<NotificationChannelStatus>(
Java_SiteChannel_getStatus(env, jchannel)));
}
// Convert java long long int to c++ pointer, take ownership.
std::unique_ptr<NotificationChannelsProviderAndroid::GetChannelsCallback> cb(
reinterpret_cast<
NotificationChannelsProviderAndroid::GetChannelsCallback*>(
callback_id));
std::move(*cb).Run(std::move(channels));
}
static void JNI_NotificationSettingsBridge_OnChannelStateChanged(
JNIEnv* env,
const JavaParamRef<jstring>& j_channel_id,
const JavaParamRef<jstring>& j_origin,
jboolean blocked) {
if (GetChannelStateChangedCallback().is_null()) {
return;
}
NotificationChannelStatus status = blocked
? NotificationChannelStatus::BLOCKED
: NotificationChannelStatus::ENABLED;
GetChannelStateChangedCallback().Run(NotificationChannel(
base::android::ConvertJavaStringToUTF8(env, j_channel_id),
base::android::ConvertJavaStringToUTF8(env, j_origin), base::Time::Now(),
status));
}
// static
void NotificationChannelsProviderAndroid::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(prefs::kClearedBlockedSiteNotificationChannels,
false /* default_value */);
registry->RegisterBooleanPref(prefs::kMigratedToSiteNotificationChannels,
false);
}
NotificationChannel::NotificationChannel(const std::string& id,
const std::string& origin,
const base::Time& timestamp,
NotificationChannelStatus status)
: id(id), origin(origin), timestamp(timestamp), status(status) {}
NotificationChannelsProviderAndroid::NotificationChannelsProviderAndroid(
PrefService* pref_service)
: NotificationChannelsProviderAndroid(
pref_service,
std::make_unique<NotificationChannelsBridgeImpl>(this)) {}
NotificationChannelsProviderAndroid::NotificationChannelsProviderAndroid(
PrefService* pref_service,
std::unique_ptr<NotificationChannelsBridge> bridge)
: bridge_(std::move(bridge)),
clock_(base::DefaultClock::GetInstance()),
pref_service_(pref_service) {}
NotificationChannelsProviderAndroid::~NotificationChannelsProviderAndroid() =
default;
void NotificationChannelsProviderAndroid::Initialize(
content_settings::ProviderInterface* pref_provider,
TemplateURLService* template_url_service) {
MigrateToChannelsIfNecessary(pref_provider);
// Clear blocked channels *after* migrating in case the pref provider
// contained any erroneously-created channels that need deleting.
ClearBlockedChannelsIfNecessary(template_url_service);
GetCachedChannelsIfNecessary(base::DoNothing());
}
void NotificationChannelsProviderAndroid::MigrateToChannelsIfNecessary(
content_settings::ProviderInterface* pref_provider) {
if (!pref_service_ ||
pref_service_->GetBoolean(prefs::kMigratedToSiteNotificationChannels)) {
return;
}
// If there are no existing rules, no need to migrate.
std::unique_ptr<content_settings::RuleIterator> it(
pref_provider->GetRuleIterator(
ContentSettingsType::NOTIFICATIONS, false /* off_the_record */,
content_settings::PartitionKey::WipGetDefault()));
if (!it || !it->HasNext()) {
return;
}
GetCachedChannelsIfNecessary(base::BindOnce(
&NotificationChannelsProviderAndroid::MigrateToChannelsIfNecessaryImpl,
weak_factory_.GetWeakPtr(), base::Unretained(pref_provider)));
}
void NotificationChannelsProviderAndroid::MigrateToChannelsIfNecessaryImpl(
content_settings::ProviderInterface* pref_provider) {
std::vector<std::pair<ContentSettingsPattern, ContentSettingsPattern>>
patterns;
// Collect the existing rules and create channels for them.
{
std::unique_ptr<content_settings::RuleIterator> it(
pref_provider->GetRuleIterator(
ContentSettingsType::NOTIFICATIONS, false /* off_the_record */,
content_settings::PartitionKey::WipGetDefault()));
while (it && it->HasNext()) {
std::unique_ptr<content_settings::Rule> rule = it->Next();
CreateChannelForRule(*rule);
patterns.emplace_back(std::move(rule->primary_pattern),
std::move(rule->secondary_pattern));
}
}
for (const auto& pattern : patterns) {
pref_provider->SetWebsiteSetting(
pattern.first, pattern.second, ContentSettingsType::NOTIFICATIONS,
base::Value(), {}, content_settings::PartitionKey::WipGetDefault());
}
if (pref_service_) {
pref_service_->SetBoolean(prefs::kMigratedToSiteNotificationChannels, true);
}
}
void NotificationChannelsProviderAndroid::ClearBlockedChannelsIfNecessary(
TemplateURLService* template_url_service) {
if (!pref_service_ || pref_service_->GetBoolean(
prefs::kClearedBlockedSiteNotificationChannels)) {
return;
}
url::Origin default_search_engine_origin;
if (template_url_service) {
default_search_engine_origin =
template_url_service->GetDefaultSearchProviderOrigin();
}
ScheduleGetChannels(
/*skip_get_if_cached_channels_are_available=*/false,
base::BindOnce(&NotificationChannelsProviderAndroid::
ClearBlockedChannelsIfNecessaryImpl,
weak_factory_.GetWeakPtr(), default_search_engine_origin));
}
void NotificationChannelsProviderAndroid::ClearBlockedChannelsIfNecessaryImpl(
const url::Origin& default_search_engine_origin,
const std::vector<NotificationChannel>& channels) {
for (const NotificationChannel& channel : channels) {
if (channel.status != NotificationChannelStatus::BLOCKED)
continue;
if (default_search_engine_origin.IsSameOriginWith(GURL(channel.origin))) {
// Do not clear the DSE permission, as it should always be ALLOW or BLOCK.
continue;
}
bridge_->DeleteChannel(channel.id);
}
// Reset the cache.
cached_channels_.reset();
if (pref_service_) {
pref_service_->SetBoolean(prefs::kClearedBlockedSiteNotificationChannels,
true);
}
}
void NotificationChannelsProviderAndroid::OnChannelStateChanged(
const NotificationChannel& channel) {
if (!cached_channels_) {
cached_channels_ = std::map<std::string, NotificationChannel>();
}
auto iter = cached_channels_->find(channel.origin);
if (iter != cached_channels_->end()) {
if (iter->second == channel) {
return;
}
iter->second.status = channel.status;
} else {
cached_channels_->emplace(channel.origin, channel);
}
// If there is a pending channel of the same origin, GetRuleIterator() should
// return the pending channel before UpdateChannelForWebsiteImpl() is called.
// As a result, there is no need to notify the observers at this moment.
// However, there is always a a race condition between
// UpdateChannelForWebsiteImpl() and this call if both happens at almost the
// same time. To solve this, schedule a task to update all the cached channels
// at the end of all the pending tasks.
if (pending_channels_.find(channel.origin) == pending_channels_.end()) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&NotificationChannelsProviderAndroid::NotifyObservers,
weak_factory_.GetWeakPtr(),
ContentSettingsPattern::Wildcard(),
ContentSettingsPattern::Wildcard(),
ContentSettingsType::NOTIFICATIONS,
/*partition_key=*/nullptr));
}
EnsureUpdatedSettings(base::DoNothing());
}
std::unique_ptr<content_settings::RuleIterator>
NotificationChannelsProviderAndroid::GetRuleIterator(
ContentSettingsType content_type,
bool off_the_record,
const content_settings::PartitionKey& partition_key) const {
if (content_type != ContentSettingsType::NOTIFICATIONS || off_the_record) {
return nullptr;
}
// This const_cast is not ideal but tolerated because it allows us to
// notify observers as soon as we detect changes to channels.
auto* provider = const_cast<NotificationChannelsProviderAndroid*>(this);
provider->RecordCachedChannelStatus();
// Combine cached_channels_ with pending_channels_ and remove duplicated
// origins.
std::map<std::string, NotificationChannel> origin_channel_map;
std::set<std::string> origins_pending_default_settings;
for (const auto& pending_channel : pending_channels_) {
if (pending_channel.second.status ==
NotificationChannelStatus::UNAVAILABLE) {
origins_pending_default_settings.emplace(pending_channel.first);
} else {
origin_channel_map.emplace(pending_channel.first, pending_channel.second);
}
}
if (cached_channels_) {
for (const auto& cached_channel : *cached_channels_) {
if (origins_pending_default_settings.find(cached_channel.first) ==
origins_pending_default_settings.end()) {
origin_channel_map.emplace(cached_channel.first, cached_channel.second);
}
}
}
// Since Android P, Chrome listens to blocked state changes for all
// notification channels, thus the returned RuleIterator is up-to-date.
std::vector<NotificationChannel> channels;
for (const auto& channel : origin_channel_map) {
channels.push_back(channel.second);
}
return channels.empty()
? nullptr
: std::make_unique<ChannelsRuleIterator>(std::move(channels));
}
void NotificationChannelsProviderAndroid::EnsureUpdatedSettings(
base::OnceClosure callback) {
ScheduleGetChannels(
/*skip_get_if_cached_channels_are_available=*/false,
base::BindOnce(
&NotificationChannelsProviderAndroid::UpdateCachedChannelsImpl,
weak_factory_.GetWeakPtr(),
/*only_initialize_null_cached_channels=*/false, std::move(callback)));
}
bool NotificationChannelsProviderAndroid::SetWebsiteSetting(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type,
base::Value&& value,
const content_settings::ContentSettingConstraints& constraints,
const content_settings::PartitionKey& partition_key) {
if (content_type != ContentSettingsType::NOTIFICATIONS) {
return false;
}
// This provider only handles settings for specific origins.
if (primary_pattern == ContentSettingsPattern::Wildcard() &&
secondary_pattern == ContentSettingsPattern::Wildcard()) {
return false;
}
// These constraints are not supported for notifications on Android.
DCHECK_EQ(constraints.expiration(), base::Time());
DCHECK_EQ(constraints.session_model(),
content_settings::mojom::SessionModel::DURABLE);
DCHECK_EQ(constraints.track_last_visit_for_autoexpiration(), false);
ContentSetting setting = content_settings::ValueToContentSetting(value);
if (setting != CONTENT_SETTING_DEFAULT && setting != CONTENT_SETTING_ALLOW &&
setting != CONTENT_SETTING_BLOCK) {
return false;
}
std::string origin_string = GetOriginStringFromPattern(primary_pattern);
// Create a new pending channel for future GetRuleIterator() call. The
// new channel should override the previous pending channel for the same
// origin.
NotificationChannel channel =
CreatePendingChannel(origin_string, setting, clock_->Now());
bool rule_changed = !IsSameAsCachedRule(channel);
pending_channels_.insert_or_assign(origin_string, channel);
GetCachedChannelsIfNecessary(base::BindOnce(
&NotificationChannelsProviderAndroid::UpdateChannelForWebsiteImpl,
weak_factory_.GetWeakPtr(), primary_pattern, secondary_pattern,
content_type, setting, constraints.Clone(), channel));
if (rule_changed) {
NotifyObservers(primary_pattern, secondary_pattern, content_type,
/*partition_key=*/nullptr);
}
if (setting == CONTENT_SETTING_DEFAULT) {
return false;
}
value = base::Value();
return true;
}
bool NotificationChannelsProviderAndroid::IsSameAsCachedRule(
const NotificationChannel& channel) {
if (auto iter = pending_channels_.find(channel.origin);
iter != pending_channels_.end()) {
return (channel == iter->second);
} else if (cached_channels_.has_value()) {
if (auto iter2 = cached_channels_->find(channel.origin);
iter2 != cached_channels_->end()) {
return (channel == iter2->second);
}
}
return false;
}
void NotificationChannelsProviderAndroid::UpdateChannelForWebsiteImpl(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type,
ContentSetting content_setting,
const content_settings::ContentSettingConstraints& constraints,
const NotificationChannel& pending_channel) {
switch (content_setting) {
case CONTENT_SETTING_ALLOW:
CreateChannelIfRequired(primary_pattern, secondary_pattern,
NotificationChannelStatus::ENABLED);
break;
case CONTENT_SETTING_BLOCK:
CreateChannelIfRequired(primary_pattern, secondary_pattern,
NotificationChannelStatus::BLOCKED);
break;
case CONTENT_SETTING_DEFAULT: {
auto channel_to_delete = cached_channels_->find(pending_channel.origin);
if (channel_to_delete != cached_channels_->end()) {
bridge_->DeleteChannel(channel_to_delete->second.id);
cached_channels_->erase(channel_to_delete);
// The observers should have already been notified when creating the
// pending channel, thus there is no need to notify them here.
}
break;
}
default:
// We rely on notification settings being one of ALLOW/BLOCK/DEFAULT.
NOTREACHED();
}
// If this is the last pending update for this origin, we no longer need to
// keep it in `pending_channels_` and can move it to `cached_channels_`.
auto iter = pending_channels_.find(pending_channel.origin);
DCHECK(iter != pending_channels_.end());
if (iter->second == pending_channel &&
iter->second.timestamp == pending_channel.timestamp) {
pending_channels_.erase(pending_channel.origin);
// The notification channel in `cached_channels_` may have a wrong status
// since the actual android notification channel is not recreated.
// Therefore, it needs to be updated with the status from the
// pending channel.
auto channel_iter = cached_channels_->find(pending_channel.origin);
if (channel_iter != cached_channels_->end()) {
channel_iter->second.status = pending_channel.status;
}
}
}
void NotificationChannelsProviderAndroid::ClearAllContentSettingsRules(
ContentSettingsType content_type,
const content_settings::PartitionKey& partition_key) {
if (content_type != ContentSettingsType::NOTIFICATIONS) {
return;
}
// Create default pending channels for all existing rules.
bool rule_changed = false;
std::set<std::string> origins;
for (const auto& iter : pending_channels_) {
origins.insert(iter.first);
}
if (cached_channels_) {
for (const auto& iter : *cached_channels_) {
origins.insert(iter.first);
}
}
base::Time timestamp = clock_->Now();
for (const std::string& origin : origins) {
NotificationChannel channel =
CreatePendingChannel(origin, CONTENT_SETTING_DEFAULT, timestamp);
rule_changed = rule_changed || !IsSameAsCachedRule(channel);
pending_channels_.insert_or_assign(origin, channel);
}
if (rule_changed) {
NotifyObservers(ContentSettingsPattern::Wildcard(),
ContentSettingsPattern::Wildcard(), content_type,
/*partition_key=*/nullptr);
}
ScheduleGetChannels(
/*skip_get_if_cached_channels_are_available=*/false,
base::BindOnce(&NotificationChannelsProviderAndroid::ClearAllChannelsImpl,
weak_factory_.GetWeakPtr(), content_type, timestamp));
}
void NotificationChannelsProviderAndroid::ClearAllChannelsImpl(
ContentSettingsType content_type,
base::Time channel_timestamp_at_invocation,
const std::vector<NotificationChannel>& channels) {
for (auto channel : channels) {
bridge_->DeleteChannel(channel.id);
}
if (cached_channels_) {
cached_channels_->clear();
}
// Clean up pending channels that hasn't changed since the
// ClearAllContentSettingsRules() calls.
for (auto iter = pending_channels_.begin();
iter != pending_channels_.end();) {
if (iter->second.status == NotificationChannelStatus::UNAVAILABLE &&
iter->second.timestamp == channel_timestamp_at_invocation) {
iter = pending_channels_.erase(iter);
} else {
++iter;
}
}
}
void NotificationChannelsProviderAndroid::ShutdownOnUIThread() {
pref_service_ = nullptr;
RemoveAllObservers();
}
bool NotificationChannelsProviderAndroid::UpdateLastUsedTime(
const GURL& primary_url,
const GURL& secondary_url,
ContentSettingsType content_type,
const base::Time time,
const content_settings::PartitionKey& partition_key) {
// Last used tracking is not implemented for this type.
return false;
}
bool NotificationChannelsProviderAndroid::ResetLastVisitTime(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type,
const content_settings::PartitionKey& partition_key) {
// Last visited tracking is not implemented for this type.
return false;
}
bool NotificationChannelsProviderAndroid::UpdateLastVisitTime(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type,
const content_settings::PartitionKey& partition_key) {
// Last visited tracking is not implemented for this type.
return false;
}
std::optional<base::TimeDelta>
NotificationChannelsProviderAndroid::RenewContentSetting(
const GURL& primary_url,
const GURL& secondary_url,
ContentSettingsType content_type,
std::optional<ContentSetting> setting_to_match,
const content_settings::PartitionKey& partition_key) {
// Setting renewal is not implemented for this type.
return std::nullopt;
}
void NotificationChannelsProviderAndroid::SetClockForTesting(
const base::Clock* clock) {
clock_ = clock;
}
// GetCachedChannelsIfNecessary() must be called prior to calling this method.
void NotificationChannelsProviderAndroid::CreateChannelIfRequired(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
NotificationChannelStatus new_channel_status) {
const std::string origin_string = GetOriginStringFromPattern(primary_pattern);
auto channel_entry = cached_channels_->find(origin_string);
if (channel_entry == cached_channels_->end()) {
base::Time timestamp = clock_->Now();
NotificationChannel channel = bridge_->CreateChannel(
origin_string, timestamp,
new_channel_status == NotificationChannelStatus::ENABLED);
cached_channels_->emplace(origin_string, std::move(channel));
// The observers should have been notified when the pending channel was
// created, so there is no need to notify them.
}
}
// GetCachedChannelsIfNecessary() must be called prior to calling this method.
void NotificationChannelsProviderAndroid::CreateChannelForRule(
const content_settings::Rule& rule) {
ContentSetting content_setting =
content_settings::ValueToContentSetting(rule.value);
switch (content_setting) {
case CONTENT_SETTING_ALLOW:
CreateChannelIfRequired(rule.primary_pattern, rule.secondary_pattern,
NotificationChannelStatus::ENABLED);
break;
case CONTENT_SETTING_BLOCK:
CreateChannelIfRequired(rule.primary_pattern, rule.secondary_pattern,
NotificationChannelStatus::BLOCKED);
break;
default:
// We assume notification preferences are either ALLOW/BLOCK.
NOTREACHED();
}
}
// This method must be called prior to accessing |cached_channels_|.
void NotificationChannelsProviderAndroid::GetCachedChannelsIfNecessary(
base::OnceClosure on_channels_initialized_cb) {
ScheduleGetChannels(
/*skip_get_if_cached_channels_are_available=*/true,
base::BindOnce(
&NotificationChannelsProviderAndroid::UpdateCachedChannelsImpl,
weak_factory_.GetWeakPtr(),
/*only_initialize_null_cached_channels=*/true,
std::move(on_channels_initialized_cb)));
}
void NotificationChannelsProviderAndroid::UpdateCachedChannelsImpl(
bool only_initialize_null_cached_channels,
base::OnceClosure on_channel_updated_cb,
const std::vector<NotificationChannel>& channels) {
std::map<std::string, NotificationChannel> updated_channels_map;
for (const auto& channel : channels) {
updated_channels_map.emplace(channel.origin, channel);
}
if (only_initialize_null_cached_channels) {
if (!cached_channels_) {
cached_channels_ = std::move(updated_channels_map);
}
} else {
if (!cached_channels_ || updated_channels_map != cached_channels_.value()) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&NotificationChannelsProviderAndroid::NotifyObservers,
weak_factory_.GetWeakPtr(),
ContentSettingsPattern::Wildcard(),
ContentSettingsPattern::Wildcard(),
ContentSettingsType::NOTIFICATIONS,
/*partition_key=*/nullptr));
cached_channels_ = std::move(updated_channels_map);
}
}
std::move(on_channel_updated_cb).Run();
}
void NotificationChannelsProviderAndroid::ScheduleGetChannels(
bool skip_get_if_cached_channels_are_available,
GetChannelsCallback get_channels_cb) {
pending_operations_.push(base::BindOnce(
&NotificationChannelsProviderAndroid::GetChannelsImpl,
weak_factory_.GetWeakPtr(), skip_get_if_cached_channels_are_available,
std::move(get_channels_cb)));
ProcessPendingOperations();
}
void NotificationChannelsProviderAndroid::GetChannelsImpl(
bool skip_get_if_cached_channels_are_available,
GetChannelsCallback get_channels_cb,
base::OnceClosure on_task_completed_cb) {
if (skip_get_if_cached_channels_are_available && cached_channels_) {
OnGetChannelsDone(std::move(get_channels_cb),
std::move(on_task_completed_cb),
std::vector<NotificationChannel>());
return;
}
bridge_->GetChannels(
base::BindOnce(&NotificationChannelsProviderAndroid::OnGetChannelsDone,
weak_factory_.GetWeakPtr(), std::move(get_channels_cb),
std::move(on_task_completed_cb)));
}
void NotificationChannelsProviderAndroid::OnGetChannelsDone(
GetChannelsCallback get_channels_cb,
base::OnceClosure on_task_completed_cb,
const std::vector<NotificationChannel>& channels) {
std::move(get_channels_cb).Run(channels);
std::move(on_task_completed_cb).Run();
}
void NotificationChannelsProviderAndroid::ProcessPendingOperations() {
if (is_processing_pending_operations_ || pending_operations_.empty()) {
return;
}
is_processing_pending_operations_ = true;
PendingCallback callback = std::move(pending_operations_.front());
pending_operations_.pop();
std::move(callback).Run(base::BindOnce(
&NotificationChannelsProviderAndroid::OnCurrentOperationFinished,
weak_factory_.GetWeakPtr()));
}
void NotificationChannelsProviderAndroid::OnCurrentOperationFinished() {
DCHECK(is_processing_pending_operations_);
is_processing_pending_operations_ = false;
ProcessPendingOperations();
}
void NotificationChannelsProviderAndroid::RecordCachedChannelStatus() {
if (!has_get_rule_iterator_called_) {
base::UmaHistogramBoolean(
"Notifications.Android.CachedChannelsStatusOnFirstGetRuleIterator",
!!cached_channels_);
has_get_rule_iterator_called_ = true;
}
}