Commit 10dc6063 authored by Anita Woodruff's avatar Anita Woodruff Committed by Commit Bot

[Android Notifications] Use timestamp in notification channel IDs

- Implement GetWebsiteSettingLastModified for the
NotificationChannelsAndroidProvider by storing channel creation
timestamp within the channel IDs.

- This also has the beneficial side effect that each time a channel
is created in response to permission grant, it will have a unique ID,
so that channels deleted when site data is cleared will not be
restored the next time permission is granted/blocked.

- Note that NCAP.GetWebsiteSettingLastModified is currently only
called from tests, but in a future patch it will be pulled up to a
new provider interface and called by the HostContentSettingsMap
where it currently only calls the Pref Provider. 

Bug: 700377
Change-Id: I5363ae2baa7e0c92bb7768361d459cd024622b32
Reviewed-on: https://chromium-review.googlesource.com/567153
Commit-Queue: Anita Woodruff <awdf@chromium.org>
Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#488198}
parent b0635479
......@@ -393,8 +393,8 @@ public class NotificationPlatformBridge {
* 2. NotificationConstants.EXTRA_NOTIFICATION_TAG - set by us on browser UI that should
* launch specific site settings, e.g. the web notifications Site Settings button.
*
* See {@link SiteChannelsManager#toChannelId} and {@link SiteChannelsManager#toSiteOrigin} for
* how we convert origins to and from channel ids.
* See {@link SiteChannelsManager#createChannelId} and {@link SiteChannelsManager#toSiteOrigin}
* for how we convert origins to and from channel ids.
*
* See {@link #makePlatformTag} for details about the format of the EXTRA_NOTIFICATION tag.
*
......@@ -623,10 +623,12 @@ public class NotificationPlatformBridge {
// Don't set a channelId for web apk notifications because the channel won't be
// initialized for the web apk and it will crash on notify - see crbug.com/727178.
// (It's okay to not set a channel on them because web apks don't target O yet.)
// TODO(crbug.com/700377): Channel ID should be retrieved from cache in native and passed
// through to here with other notification parameters.
String channelId = forWebApk
? null
: ChromeFeatureList.isEnabled(ChromeFeatureList.SITE_NOTIFICATION_CHANNELS)
? SiteChannelsManager.toChannelId(origin)
? SiteChannelsManager.getInstance().getChannelIdForOrigin(origin)
: ChannelDefinitions.CHANNEL_ID_SITES;
if (useCustomLayouts(hasImage)) {
return new CustomNotificationBuilder(context, channelId);
......
......@@ -4,8 +4,12 @@
package org.chromium.chrome.browser.notifications;
import android.app.NotificationManager;
import org.chromium.base.BuildInfo;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.notifications.channels.Channel;
import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
import org.chromium.chrome.browser.notifications.channels.SiteChannelsManager;
/**
......@@ -20,19 +24,23 @@ public class NotificationSettingsBridge {
}
/**
* Creates a notification channel for the given origin.
* Creates a notification channel for the given origin, unless a channel for this origin
* already exists.
*
* @param origin The site origin to be used as the channel name.
* @param creationTime A string representing the time of channel creation.
* @param enabled True if the channel should be initially enabled, false if
* it should start off as blocked.
* @return The channel created for this origin.
*/
@CalledByNative
static void createChannel(String origin, boolean enabled) {
SiteChannelsManager.getInstance().createSiteChannel(origin, enabled);
static SiteChannel createChannel(String origin, long creationTime, boolean enabled) {
return SiteChannelsManager.getInstance().createSiteChannel(origin, creationTime, enabled);
}
@CalledByNative
static @NotificationChannelStatus int getChannelStatus(String origin) {
return SiteChannelsManager.getInstance().getChannelStatus(origin);
static @NotificationChannelStatus int getChannelStatus(String channelId) {
return SiteChannelsManager.getInstance().getChannelStatus(channelId);
}
@CalledByNative
......@@ -41,22 +49,32 @@ public class NotificationSettingsBridge {
}
@CalledByNative
static void deleteChannel(String origin) {
SiteChannelsManager.getInstance().deleteSiteChannel(origin);
static void deleteChannel(String channelId) {
SiteChannelsManager.getInstance().deleteSiteChannel(channelId);
}
/**
* Helper type for passing site channel objects across the JNI.
*/
public static class SiteChannel {
private final String mId;
private final String mOrigin;
private final long mTimestamp;
private final @NotificationChannelStatus int mStatus;
public SiteChannel(String origin, @NotificationChannelStatus int status) {
public SiteChannel(String channelId, String origin, long creationTimestamp,
@NotificationChannelStatus int status) {
mId = channelId;
mOrigin = origin;
mTimestamp = creationTimestamp;
mStatus = status;
}
@CalledByNative("SiteChannel")
public long getTimestamp() {
return mTimestamp;
}
@CalledByNative("SiteChannel")
public String getOrigin() {
return mOrigin;
......@@ -66,5 +84,18 @@ public class NotificationSettingsBridge {
public @NotificationChannelStatus int getStatus() {
return mStatus;
}
@CalledByNative("SiteChannel")
public String getId() {
return mId;
}
public Channel toChannel() {
return new Channel(mId, mOrigin,
mStatus == NotificationChannelStatus.BLOCKED
? NotificationManager.IMPORTANCE_NONE
: NotificationManager.IMPORTANCE_DEFAULT,
ChannelDefinitions.CHANNEL_GROUP_ID_SITES);
}
}
}
......@@ -33,8 +33,8 @@ public class ChannelDefinitions {
// TODO(crbug.com/700377): Deprecate the 'sites' channel.
public static final String CHANNEL_ID_SITES = "sites";
public static final String CHANNEL_ID_PREFIX_SITES = "web:";
public static final String CHANNEL_GROUP_ID_SITES = "sites";
static final String CHANNEL_GROUP_ID_GENERAL = "general";
static final String CHANNEL_GROUP_ID_SITES = "sites";
/**
* Version number identifying the current set of channels. This must be incremented whenever
* the set of channels returned by {@link #getStartupChannelIds()} or
......
......@@ -52,10 +52,10 @@ public class ChannelsInitializer {
*/
public void ensureInitialized(String channelId) {
if (channelId.startsWith(ChannelDefinitions.CHANNEL_ID_PREFIX_SITES)) {
// TODO(crbug.com/700377): Initialize site channels via native, not directly as below,
// in order to keep track of when they were created.
SiteChannelsManager.getInstance().createSiteChannel(
SiteChannelsManager.toSiteOrigin(channelId), /*enabled=*/true);
// If we have a valid site channel ID at this point, it is safe to assume a channel
// has already been created, since the only way to obtain a site channel ID is by
// creating a channel.
assert SiteChannelsManager.isValidSiteChannelId(channelId);
return;
}
ChannelDefinitions.PredefinedChannel predefinedChannel =
......
......@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.notifications.channels;
import android.app.NotificationManager;
import android.content.Context;
import android.support.annotation.Nullable;
import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting;
......@@ -22,6 +23,8 @@ import java.util.List;
* Creates/deletes and queries our notification channels for websites.
*/
public class SiteChannelsManager {
private static final String CHANNEL_ID_SEPARATOR = ";";
private final NotificationManagerProxy mNotificationManager;
public static SiteChannelsManager getInstance() {
......@@ -41,34 +44,52 @@ public class SiteChannelsManager {
}
/**
* Creates a channel for the given origin. The channel will appear within the Sites channel
* Creates a channel for the given origin, unless a channel for this origin
* already exists. The channel will appear within the Sites channel
* group, with default importance, or no importance if created as blocked.
* @param origin The site origin, to be used as the channel's user-visible name.
* @param creationTime A string representing the time of channel creation.
* @param enabled Determines whether the channel will be created as enabled or blocked.
* @return The channel created for the given origin.
*/
public void createSiteChannel(String origin, boolean enabled) {
public SiteChannel createSiteChannel(String origin, long creationTime, boolean enabled) {
SiteChannel preexistingChannel = getSiteChannelForOrigin(origin);
if (preexistingChannel != null) {
return preexistingChannel;
}
// Channel group must be created before the channel.
mNotificationManager.createNotificationChannelGroup(
ChannelDefinitions.getChannelGroup(ChannelDefinitions.CHANNEL_GROUP_ID_SITES));
mNotificationManager.createNotificationChannel(new Channel(toChannelId(origin), origin,
enabled ? NotificationManager.IMPORTANCE_DEFAULT
: NotificationManager.IMPORTANCE_NONE,
ChannelDefinitions.CHANNEL_GROUP_ID_SITES));
SiteChannel siteChannel = new SiteChannel(createChannelId(origin, creationTime), origin,
creationTime,
enabled ? NotificationChannelStatus.ENABLED : NotificationChannelStatus.BLOCKED);
mNotificationManager.createNotificationChannel(siteChannel.toChannel());
return siteChannel;
}
private @Nullable SiteChannel getSiteChannelForOrigin(String origin) {
String normalizedOrigin = WebsiteAddress.create(origin).getOrigin();
for (SiteChannel channel : getSiteChannels()) {
if (channel.getOrigin().equals(normalizedOrigin)) {
return channel;
}
}
return null;
}
/**
* Deletes the channel associated with this origin.
* Deletes the channel associated with this channel ID.
*/
public void deleteSiteChannel(String origin) {
mNotificationManager.deleteNotificationChannel(toChannelId(origin));
public void deleteSiteChannel(String channelId) {
mNotificationManager.deleteNotificationChannel(channelId);
}
/**
* Gets the status of the channel associated with this origin.
* Gets the status of the channel associated with this channelId.
* @return ALLOW, BLOCKED, or UNAVAILABLE (if the channel was never created or was deleted).
*/
public @NotificationChannelStatus int getChannelStatus(String origin) {
Channel channel = mNotificationManager.getNotificationChannel(toChannelId(origin));
public @NotificationChannelStatus int getChannelStatus(String channelId) {
Channel channel = mNotificationManager.getNotificationChannel(channelId);
if (channel == null) return NotificationChannelStatus.UNAVAILABLE;
return toChannelStatus(channel.getImportance());
}
......@@ -81,21 +102,34 @@ public class SiteChannelsManager {
List<Channel> channels = mNotificationManager.getNotificationChannels();
List<SiteChannel> siteChannels = new ArrayList<>();
for (Channel channel : channels) {
if (channel.getGroupId() != null
&& channel.getGroupId().equals(ChannelDefinitions.CHANNEL_GROUP_ID_SITES)) {
siteChannels.add(new SiteChannel(
toSiteOrigin(channel.getId()), toChannelStatus(channel.getImportance())));
if (isValidSiteChannelId(channel.getId())) {
siteChannels.add(toSiteChannel(channel));
}
}
return siteChannels.toArray(new SiteChannel[siteChannels.size()]);
}
private static SiteChannel toSiteChannel(Channel channel) {
String originAndTimestamp =
channel.getId().substring(ChannelDefinitions.CHANNEL_ID_PREFIX_SITES.length());
String[] parts = originAndTimestamp.split(CHANNEL_ID_SEPARATOR);
assert parts.length == 2;
return new SiteChannel(channel.getId(), parts[0], Long.parseLong(parts[1]),
toChannelStatus(channel.getImportance()));
}
static boolean isValidSiteChannelId(String channelId) {
return channelId.startsWith(ChannelDefinitions.CHANNEL_ID_PREFIX_SITES)
&& channelId.substring(ChannelDefinitions.CHANNEL_ID_PREFIX_SITES.length())
.contains(CHANNEL_ID_SEPARATOR);
}
/**
* Converts a site's origin to a canonical channel id for that origin.
* Converts a site's origin and creation timestamp to a canonical channel id.
*/
public static String toChannelId(String origin) {
public static String createChannelId(String origin, long creationTime) {
return ChannelDefinitions.CHANNEL_ID_PREFIX_SITES
+ WebsiteAddress.create(origin).getOrigin();
+ WebsiteAddress.create(origin).getOrigin() + CHANNEL_ID_SEPARATOR + creationTime;
}
/**
......@@ -105,7 +139,8 @@ public class SiteChannelsManager {
*/
public static String toSiteOrigin(String channelId) {
assert channelId.startsWith(ChannelDefinitions.CHANNEL_ID_PREFIX_SITES);
return channelId.substring(ChannelDefinitions.CHANNEL_ID_PREFIX_SITES.length());
return channelId.substring(ChannelDefinitions.CHANNEL_ID_PREFIX_SITES.length())
.split(CHANNEL_ID_SEPARATOR)[0];
}
/**
......@@ -119,4 +154,10 @@ public class SiteChannelsManager {
return NotificationChannelStatus.ENABLED;
}
}
public String getChannelIdForOrigin(String origin) {
SiteChannel channel = getSiteChannelForOrigin(origin);
// Fall back to generic Sites channel if a channel for this origin doesn't exist.
return channel == null ? ChannelDefinitions.CHANNEL_ID_SITES : channel.getId();
}
}
......@@ -385,14 +385,13 @@ public class SingleWebsitePreferences extends PreferenceFragment
public boolean onPreferenceClick(Preference preference) {
// There is no guarantee that a channel has been initialized yet for sites
// that were granted permission before the channel-initialization-on-grant
// code was in place. So initialize it here before launching the settings.
// TODO(awdf): Once upgrade code is in place, quickly check this ran instead.
// (Right now this is laggy!)
SiteChannelsManager.getInstance().createSiteChannel(
mSite.getAddress().getOrigin(), value == ContentSetting.ALLOW);
launchOsChannelSettings(preference.getContext(),
SiteChannelsManager.toChannelId(mSite.getAddress().getOrigin()));
// code was in place. However, getChannelIdForOrigin will fall back to the
// generic Sites channel if no specific channel has been created for the given
// origin, so it is safe to open the channel settings for whatever channel ID
// it returns.
String channelId = SiteChannelsManager.getInstance().getChannelIdForOrigin(
mSite.getAddress().getOrigin());
launchOsChannelSettings(preference.getContext(), channelId);
return true;
}
});
......
......@@ -230,12 +230,14 @@ public class ChannelsInitializerTest {
public void testEnsureInitialized_singleOriginSiteChannel() throws Exception {
if (!BuildInfo.isAtLeastO()) return;
String origin = "https://example.com";
mChannelsInitializer.ensureInitialized(SiteChannelsManager.toChannelId(origin));
long creationTime = 621046800000L;
mChannelsInitializer.ensureInitialized(
SiteChannelsManager.createChannelId(origin, creationTime));
assertThat(getChannelsIgnoringMiscellaneous(), hasSize(1));
Channel channel = getChannelsIgnoringMiscellaneous().get(0);
assertThat(channel.getId(), is(SiteChannelsManager.toChannelId(origin)));
assertThat(channel.getId(), is(SiteChannelsManager.createChannelId(origin, creationTime)));
assertThat(channel.getName().toString(), is("https://example.com"));
assertThat(channel.getImportance(), is(NotificationManager.IMPORTANCE_DEFAULT));
assertThat(channel.getGroupId(), is(ChannelDefinitions.CHANNEL_GROUP_ID_SITES));
......
......@@ -67,18 +67,19 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26)
@SmallTest
public void testCreateSiteChannel_enabled() throws Exception {
mSiteChannelsManager.createSiteChannel("https://chromium.org", true);
mSiteChannelsManager.createSiteChannel("https://chromium.org", 62102180000L, true);
assertThat(Arrays.asList(mSiteChannelsManager.getSiteChannels()), hasSize(1));
NotificationSettingsBridge.SiteChannel channel = mSiteChannelsManager.getSiteChannels()[0];
assertThat(channel.getOrigin(), is("https://chromium.org"));
assertThat(channel.getStatus(), matchesChannelStatus(NotificationChannelStatus.ENABLED));
assertThat(channel.getTimestamp(), is(62102180000L));
}
@Test
@MinAndroidSdkLevel(26)
@SmallTest
public void testCreateSiteChannel_disabled() throws Exception {
mSiteChannelsManager.createSiteChannel("https://example.com", false);
mSiteChannelsManager.createSiteChannel("https://example.com", 0L, false);
assertThat(Arrays.asList(mSiteChannelsManager.getSiteChannels()), hasSize(1));
NotificationSettingsBridge.SiteChannel channel = mSiteChannelsManager.getSiteChannels()[0];
assertThat(channel.getOrigin(), is("https://example.com"));
......@@ -89,8 +90,9 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26)
@SmallTest
public void testDeleteSiteChannel_channelExists() throws Exception {
mSiteChannelsManager.createSiteChannel("https://chromium.org", true);
mSiteChannelsManager.deleteSiteChannel("https://chromium.org");
NotificationSettingsBridge.SiteChannel channel =
mSiteChannelsManager.createSiteChannel("https://chromium.org", 0L, true);
mSiteChannelsManager.deleteSiteChannel(channel.getId());
assertThat(Arrays.asList(mSiteChannelsManager.getSiteChannels()), hasSize(0));
}
......@@ -98,7 +100,7 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26)
@SmallTest
public void testDeleteSiteChannel_channelDoesNotExist() throws Exception {
mSiteChannelsManager.createSiteChannel("https://chromium.org", true);
mSiteChannelsManager.createSiteChannel("https://chromium.org", 0L, true);
mSiteChannelsManager.deleteSiteChannel("https://some-other-origin.org");
assertThat(Arrays.asList(mSiteChannelsManager.getSiteChannels()), hasSize(1));
}
......@@ -107,8 +109,9 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26)
@SmallTest
public void testGetChannelStatus_channelCreatedAsEnabled() throws Exception {
mSiteChannelsManager.createSiteChannel("https://chromium.org", true);
assertThat(mSiteChannelsManager.getChannelStatus("https://chromium.org"),
NotificationSettingsBridge.SiteChannel channel =
mSiteChannelsManager.createSiteChannel("https://chromium.org", 0L, true);
assertThat(mSiteChannelsManager.getChannelStatus(channel.getId()),
matchesChannelStatus(NotificationChannelStatus.ENABLED));
}
......@@ -118,11 +121,9 @@ public class SiteChannelsManagerTest {
public void testGetChannelStatus_channelCreatedAsBlocked() throws Exception {
assertThat(mSiteChannelsManager.getChannelStatus("https://foobar.com"),
matchesChannelStatus(NotificationChannelStatus.UNAVAILABLE));
mSiteChannelsManager.createSiteChannel("https://foobar.com", false);
assertThat(mNotificationManagerProxy.getNotificationChannel("web:https://foobar.com")
.getImportance(),
is(NotificationManager.IMPORTANCE_NONE));
assertThat(mSiteChannelsManager.getChannelStatus("https://foobar.com"),
NotificationSettingsBridge.SiteChannel channel =
mSiteChannelsManager.createSiteChannel("https://foobar.com", 0L, false);
assertThat(mSiteChannelsManager.getChannelStatus(channel.getId()),
matchesChannelStatus(NotificationChannelStatus.BLOCKED));
}
......@@ -130,7 +131,7 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26)
@SmallTest
public void testGetChannelStatus_channelNotCreated() throws Exception {
assertThat(mSiteChannelsManager.getChannelStatus("https://chromium.org"),
assertThat(mSiteChannelsManager.getChannelStatus("invalid-channel-id"),
matchesChannelStatus(NotificationChannelStatus.UNAVAILABLE));
}
......@@ -138,9 +139,10 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26)
@SmallTest
public void testGetChannelStatus_channelCreatedThenDeleted() throws Exception {
mSiteChannelsManager.createSiteChannel("https://chromium.org", true);
mSiteChannelsManager.deleteSiteChannel("https://chromium.org");
assertThat(mSiteChannelsManager.getChannelStatus("https://chromium.org"),
NotificationSettingsBridge.SiteChannel channel =
mSiteChannelsManager.createSiteChannel("https://chromium.org", 0L, true);
mSiteChannelsManager.deleteSiteChannel(channel.getId());
assertThat(mSiteChannelsManager.getChannelStatus(channel.getId()),
matchesChannelStatus(NotificationChannelStatus.UNAVAILABLE));
}
......
......@@ -120,7 +120,7 @@ public class NotificationPlatformBridgeUnitTest {
// Returns the expected origin for a channel id associated with a particular origin.
assertEquals("https://example.com",
NotificationPlatformBridge.getOriginFromChannelId(
SiteChannelsManager.toChannelId("https://example.com")));
SiteChannelsManager.createChannelId("https://example.com", 62104680000L)));
// Returns null for a channel id that is not associated with a particular origin.
assertNull(NotificationPlatformBridge.getOriginFromChannelId(
......
......@@ -5,12 +5,14 @@
#ifndef CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_CHANNELS_PROVIDER_ANDROID_H_
#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_CHANNELS_PROVIDER_ANDROID_H_
#include <map>
#include <string>
#include <tuple>
#include <vector>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/time/clock.h"
#include "components/content_settings/core/browser/content_settings_observable_provider.h"
#include "components/content_settings/core/browser/content_settings_observer.h"
#include "components/content_settings/core/browser/content_settings_rule.h"
......@@ -23,15 +25,17 @@
enum NotificationChannelStatus { ENABLED, BLOCKED, UNAVAILABLE };
struct NotificationChannel {
NotificationChannel(std::string origin, NotificationChannelStatus status)
: origin(origin), status(status) {}
NotificationChannel(const std::string& id,
const std::string& origin,
const base::Time& timestamp,
NotificationChannelStatus status);
NotificationChannel(const NotificationChannel& other);
bool operator==(const NotificationChannel& other) const {
return origin == other.origin && status == other.status;
}
bool operator<(const NotificationChannel& other) const {
return std::tie(origin, status) < std::tie(other.origin, other.status);
}
std::string origin;
const std::string id;
const std::string origin;
const base::Time timestamp;
NotificationChannelStatus status = NotificationChannelStatus::UNAVAILABLE;
};
......@@ -47,7 +51,9 @@ class NotificationChannelsProviderAndroid
public:
virtual ~NotificationChannelsBridge() = default;
virtual bool ShouldUseChannelSettings() = 0;
virtual void CreateChannel(const std::string& origin, bool enabled) = 0;
virtual NotificationChannel CreateChannel(const std::string& origin,
const base::Time& timestamp,
bool enabled) = 0;
virtual NotificationChannelStatus GetChannelStatus(
const std::string& origin) = 0;
virtual void DeleteChannel(const std::string& origin) = 0;
......@@ -71,22 +77,48 @@ class NotificationChannelsProviderAndroid
void ClearAllContentSettingsRules(ContentSettingsType content_type) override;
void ShutdownOnUIThread() override;
base::Time GetWebsiteSettingLastModified(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type,
const content_settings::ResourceIdentifier& resource_identifier);
private:
explicit NotificationChannelsProviderAndroid(
std::unique_ptr<NotificationChannelsBridge> bridge);
std::unique_ptr<NotificationChannelsBridge> bridge,
std::unique_ptr<base::Clock> clock);
friend class NotificationChannelsProviderAndroidTest;
std::vector<NotificationChannel> UpdateCachedChannels() const;
void CreateChannelIfRequired(const std::string& origin_string,
NotificationChannelStatus new_channel_status);
void InitCachedChannels();
std::unique_ptr<NotificationChannelsBridge> bridge_;
bool should_use_channels_;
// This cache is updated every time GetRuleIterator is called. It is used to
// check if any channels have changed their status since the previous call,
// in order to notify observers. This is necessary to detect channels getting
// blocked/enabled by the user, in the absence of a callback for this event.
std::vector<NotificationChannel> cached_channels_;
std::unique_ptr<base::Clock> clock_;
// Flag to keep track of whether |cached_channels_| has been initialized yet.
bool initialized_cached_channels_;
// Map of origin - NotificationChannel. Channel status may be out of date.
// This cache is completely refreshed every time GetRuleIterator is called;
// entries are also added and deleted when channels are added and deleted.
// This cache serves three purposes:
//
// 1. For looking up the channel ID for an origin.
//
// 2. For looking up the channel creation timestamp for an origin.
//
// 3. To check if any channels have changed status since the last time
// they were checked, in order to notify observers. This is necessary to
// detect channels getting blocked/enabled by the user, in the absence of a
// callback for this event.
std::map<std::string, NotificationChannel> cached_channels_;
base::WeakPtrFactory<NotificationChannelsProviderAndroid> weak_factory_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment