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 { ...@@ -393,8 +393,8 @@ public class NotificationPlatformBridge {
* 2. NotificationConstants.EXTRA_NOTIFICATION_TAG - set by us on browser UI that should * 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. * launch specific site settings, e.g. the web notifications Site Settings button.
* *
* See {@link SiteChannelsManager#toChannelId} and {@link SiteChannelsManager#toSiteOrigin} for * See {@link SiteChannelsManager#createChannelId} and {@link SiteChannelsManager#toSiteOrigin}
* how we convert origins to and from channel ids. * for how we convert origins to and from channel ids.
* *
* See {@link #makePlatformTag} for details about the format of the EXTRA_NOTIFICATION tag. * See {@link #makePlatformTag} for details about the format of the EXTRA_NOTIFICATION tag.
* *
...@@ -623,10 +623,12 @@ public class NotificationPlatformBridge { ...@@ -623,10 +623,12 @@ public class NotificationPlatformBridge {
// Don't set a channelId for web apk notifications because the channel won't be // 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. // 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.) // (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 String channelId = forWebApk
? null ? null
: ChromeFeatureList.isEnabled(ChromeFeatureList.SITE_NOTIFICATION_CHANNELS) : ChromeFeatureList.isEnabled(ChromeFeatureList.SITE_NOTIFICATION_CHANNELS)
? SiteChannelsManager.toChannelId(origin) ? SiteChannelsManager.getInstance().getChannelIdForOrigin(origin)
: ChannelDefinitions.CHANNEL_ID_SITES; : ChannelDefinitions.CHANNEL_ID_SITES;
if (useCustomLayouts(hasImage)) { if (useCustomLayouts(hasImage)) {
return new CustomNotificationBuilder(context, channelId); return new CustomNotificationBuilder(context, channelId);
......
...@@ -4,8 +4,12 @@ ...@@ -4,8 +4,12 @@
package org.chromium.chrome.browser.notifications; package org.chromium.chrome.browser.notifications;
import android.app.NotificationManager;
import org.chromium.base.BuildInfo; import org.chromium.base.BuildInfo;
import org.chromium.base.annotations.CalledByNative; 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; import org.chromium.chrome.browser.notifications.channels.SiteChannelsManager;
/** /**
...@@ -20,19 +24,23 @@ public class NotificationSettingsBridge { ...@@ -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 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 * @param enabled True if the channel should be initially enabled, false if
* it should start off as blocked. * it should start off as blocked.
* @return The channel created for this origin.
*/ */
@CalledByNative @CalledByNative
static void createChannel(String origin, boolean enabled) { static SiteChannel createChannel(String origin, long creationTime, boolean enabled) {
SiteChannelsManager.getInstance().createSiteChannel(origin, enabled); return SiteChannelsManager.getInstance().createSiteChannel(origin, creationTime, enabled);
} }
@CalledByNative @CalledByNative
static @NotificationChannelStatus int getChannelStatus(String origin) { static @NotificationChannelStatus int getChannelStatus(String channelId) {
return SiteChannelsManager.getInstance().getChannelStatus(origin); return SiteChannelsManager.getInstance().getChannelStatus(channelId);
} }
@CalledByNative @CalledByNative
...@@ -41,22 +49,32 @@ public class NotificationSettingsBridge { ...@@ -41,22 +49,32 @@ public class NotificationSettingsBridge {
} }
@CalledByNative @CalledByNative
static void deleteChannel(String origin) { static void deleteChannel(String channelId) {
SiteChannelsManager.getInstance().deleteSiteChannel(origin); SiteChannelsManager.getInstance().deleteSiteChannel(channelId);
} }
/** /**
* Helper type for passing site channel objects across the JNI. * Helper type for passing site channel objects across the JNI.
*/ */
public static class SiteChannel { public static class SiteChannel {
private final String mId;
private final String mOrigin; private final String mOrigin;
private final long mTimestamp;
private final @NotificationChannelStatus int mStatus; 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; mOrigin = origin;
mTimestamp = creationTimestamp;
mStatus = status; mStatus = status;
} }
@CalledByNative("SiteChannel")
public long getTimestamp() {
return mTimestamp;
}
@CalledByNative("SiteChannel") @CalledByNative("SiteChannel")
public String getOrigin() { public String getOrigin() {
return mOrigin; return mOrigin;
...@@ -66,5 +84,18 @@ public class NotificationSettingsBridge { ...@@ -66,5 +84,18 @@ public class NotificationSettingsBridge {
public @NotificationChannelStatus int getStatus() { public @NotificationChannelStatus int getStatus() {
return mStatus; 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 { ...@@ -33,8 +33,8 @@ public class ChannelDefinitions {
// TODO(crbug.com/700377): Deprecate the 'sites' channel. // TODO(crbug.com/700377): Deprecate the 'sites' channel.
public static final String CHANNEL_ID_SITES = "sites"; public static final String CHANNEL_ID_SITES = "sites";
public static final String CHANNEL_ID_PREFIX_SITES = "web:"; 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_GENERAL = "general";
static final String CHANNEL_GROUP_ID_SITES = "sites";
/** /**
* Version number identifying the current set of channels. This must be incremented whenever * Version number identifying the current set of channels. This must be incremented whenever
* the set of channels returned by {@link #getStartupChannelIds()} or * the set of channels returned by {@link #getStartupChannelIds()} or
......
...@@ -52,10 +52,10 @@ public class ChannelsInitializer { ...@@ -52,10 +52,10 @@ public class ChannelsInitializer {
*/ */
public void ensureInitialized(String channelId) { public void ensureInitialized(String channelId) {
if (channelId.startsWith(ChannelDefinitions.CHANNEL_ID_PREFIX_SITES)) { if (channelId.startsWith(ChannelDefinitions.CHANNEL_ID_PREFIX_SITES)) {
// TODO(crbug.com/700377): Initialize site channels via native, not directly as below, // If we have a valid site channel ID at this point, it is safe to assume a channel
// in order to keep track of when they were created. // has already been created, since the only way to obtain a site channel ID is by
SiteChannelsManager.getInstance().createSiteChannel( // creating a channel.
SiteChannelsManager.toSiteOrigin(channelId), /*enabled=*/true); assert SiteChannelsManager.isValidSiteChannelId(channelId);
return; return;
} }
ChannelDefinitions.PredefinedChannel predefinedChannel = ChannelDefinitions.PredefinedChannel predefinedChannel =
......
...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.notifications.channels; ...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.notifications.channels;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.support.annotation.Nullable;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
...@@ -22,6 +23,8 @@ import java.util.List; ...@@ -22,6 +23,8 @@ import java.util.List;
* Creates/deletes and queries our notification channels for websites. * Creates/deletes and queries our notification channels for websites.
*/ */
public class SiteChannelsManager { public class SiteChannelsManager {
private static final String CHANNEL_ID_SEPARATOR = ";";
private final NotificationManagerProxy mNotificationManager; private final NotificationManagerProxy mNotificationManager;
public static SiteChannelsManager getInstance() { public static SiteChannelsManager getInstance() {
...@@ -41,34 +44,52 @@ public class SiteChannelsManager { ...@@ -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. * 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 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. * @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. // Channel group must be created before the channel.
mNotificationManager.createNotificationChannelGroup( mNotificationManager.createNotificationChannelGroup(
ChannelDefinitions.getChannelGroup(ChannelDefinitions.CHANNEL_GROUP_ID_SITES)); ChannelDefinitions.getChannelGroup(ChannelDefinitions.CHANNEL_GROUP_ID_SITES));
mNotificationManager.createNotificationChannel(new Channel(toChannelId(origin), origin, SiteChannel siteChannel = new SiteChannel(createChannelId(origin, creationTime), origin,
enabled ? NotificationManager.IMPORTANCE_DEFAULT creationTime,
: NotificationManager.IMPORTANCE_NONE, enabled ? NotificationChannelStatus.ENABLED : NotificationChannelStatus.BLOCKED);
ChannelDefinitions.CHANNEL_GROUP_ID_SITES)); 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) { public void deleteSiteChannel(String channelId) {
mNotificationManager.deleteNotificationChannel(toChannelId(origin)); 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). * @return ALLOW, BLOCKED, or UNAVAILABLE (if the channel was never created or was deleted).
*/ */
public @NotificationChannelStatus int getChannelStatus(String origin) { public @NotificationChannelStatus int getChannelStatus(String channelId) {
Channel channel = mNotificationManager.getNotificationChannel(toChannelId(origin)); Channel channel = mNotificationManager.getNotificationChannel(channelId);
if (channel == null) return NotificationChannelStatus.UNAVAILABLE; if (channel == null) return NotificationChannelStatus.UNAVAILABLE;
return toChannelStatus(channel.getImportance()); return toChannelStatus(channel.getImportance());
} }
...@@ -81,21 +102,34 @@ public class SiteChannelsManager { ...@@ -81,21 +102,34 @@ public class SiteChannelsManager {
List<Channel> channels = mNotificationManager.getNotificationChannels(); List<Channel> channels = mNotificationManager.getNotificationChannels();
List<SiteChannel> siteChannels = new ArrayList<>(); List<SiteChannel> siteChannels = new ArrayList<>();
for (Channel channel : channels) { for (Channel channel : channels) {
if (channel.getGroupId() != null if (isValidSiteChannelId(channel.getId())) {
&& channel.getGroupId().equals(ChannelDefinitions.CHANNEL_GROUP_ID_SITES)) { siteChannels.add(toSiteChannel(channel));
siteChannels.add(new SiteChannel(
toSiteOrigin(channel.getId()), toChannelStatus(channel.getImportance())));
} }
} }
return siteChannels.toArray(new SiteChannel[siteChannels.size()]); 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 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 { ...@@ -105,7 +139,8 @@ public class SiteChannelsManager {
*/ */
public static String toSiteOrigin(String channelId) { public static String toSiteOrigin(String channelId) {
assert channelId.startsWith(ChannelDefinitions.CHANNEL_ID_PREFIX_SITES); 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 { ...@@ -119,4 +154,10 @@ public class SiteChannelsManager {
return NotificationChannelStatus.ENABLED; 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 ...@@ -385,14 +385,13 @@ public class SingleWebsitePreferences extends PreferenceFragment
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
// There is no guarantee that a channel has been initialized yet for sites // There is no guarantee that a channel has been initialized yet for sites
// that were granted permission before the channel-initialization-on-grant // that were granted permission before the channel-initialization-on-grant
// code was in place. So initialize it here before launching the settings. // code was in place. However, getChannelIdForOrigin will fall back to the
// TODO(awdf): Once upgrade code is in place, quickly check this ran instead. // generic Sites channel if no specific channel has been created for the given
// (Right now this is laggy!) // origin, so it is safe to open the channel settings for whatever channel ID
SiteChannelsManager.getInstance().createSiteChannel( // it returns.
mSite.getAddress().getOrigin(), value == ContentSetting.ALLOW); String channelId = SiteChannelsManager.getInstance().getChannelIdForOrigin(
mSite.getAddress().getOrigin());
launchOsChannelSettings(preference.getContext(), launchOsChannelSettings(preference.getContext(), channelId);
SiteChannelsManager.toChannelId(mSite.getAddress().getOrigin()));
return true; return true;
} }
}); });
......
...@@ -230,12 +230,14 @@ public class ChannelsInitializerTest { ...@@ -230,12 +230,14 @@ public class ChannelsInitializerTest {
public void testEnsureInitialized_singleOriginSiteChannel() throws Exception { public void testEnsureInitialized_singleOriginSiteChannel() throws Exception {
if (!BuildInfo.isAtLeastO()) return; if (!BuildInfo.isAtLeastO()) return;
String origin = "https://example.com"; String origin = "https://example.com";
mChannelsInitializer.ensureInitialized(SiteChannelsManager.toChannelId(origin)); long creationTime = 621046800000L;
mChannelsInitializer.ensureInitialized(
SiteChannelsManager.createChannelId(origin, creationTime));
assertThat(getChannelsIgnoringMiscellaneous(), hasSize(1)); assertThat(getChannelsIgnoringMiscellaneous(), hasSize(1));
Channel channel = getChannelsIgnoringMiscellaneous().get(0); 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.getName().toString(), is("https://example.com"));
assertThat(channel.getImportance(), is(NotificationManager.IMPORTANCE_DEFAULT)); assertThat(channel.getImportance(), is(NotificationManager.IMPORTANCE_DEFAULT));
assertThat(channel.getGroupId(), is(ChannelDefinitions.CHANNEL_GROUP_ID_SITES)); assertThat(channel.getGroupId(), is(ChannelDefinitions.CHANNEL_GROUP_ID_SITES));
......
...@@ -67,18 +67,19 @@ public class SiteChannelsManagerTest { ...@@ -67,18 +67,19 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26) @MinAndroidSdkLevel(26)
@SmallTest @SmallTest
public void testCreateSiteChannel_enabled() throws Exception { 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)); assertThat(Arrays.asList(mSiteChannelsManager.getSiteChannels()), hasSize(1));
NotificationSettingsBridge.SiteChannel channel = mSiteChannelsManager.getSiteChannels()[0]; NotificationSettingsBridge.SiteChannel channel = mSiteChannelsManager.getSiteChannels()[0];
assertThat(channel.getOrigin(), is("https://chromium.org")); assertThat(channel.getOrigin(), is("https://chromium.org"));
assertThat(channel.getStatus(), matchesChannelStatus(NotificationChannelStatus.ENABLED)); assertThat(channel.getStatus(), matchesChannelStatus(NotificationChannelStatus.ENABLED));
assertThat(channel.getTimestamp(), is(62102180000L));
} }
@Test @Test
@MinAndroidSdkLevel(26) @MinAndroidSdkLevel(26)
@SmallTest @SmallTest
public void testCreateSiteChannel_disabled() throws Exception { 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)); assertThat(Arrays.asList(mSiteChannelsManager.getSiteChannels()), hasSize(1));
NotificationSettingsBridge.SiteChannel channel = mSiteChannelsManager.getSiteChannels()[0]; NotificationSettingsBridge.SiteChannel channel = mSiteChannelsManager.getSiteChannels()[0];
assertThat(channel.getOrigin(), is("https://example.com")); assertThat(channel.getOrigin(), is("https://example.com"));
...@@ -89,8 +90,9 @@ public class SiteChannelsManagerTest { ...@@ -89,8 +90,9 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26) @MinAndroidSdkLevel(26)
@SmallTest @SmallTest
public void testDeleteSiteChannel_channelExists() throws Exception { public void testDeleteSiteChannel_channelExists() throws Exception {
mSiteChannelsManager.createSiteChannel("https://chromium.org", true); NotificationSettingsBridge.SiteChannel channel =
mSiteChannelsManager.deleteSiteChannel("https://chromium.org"); mSiteChannelsManager.createSiteChannel("https://chromium.org", 0L, true);
mSiteChannelsManager.deleteSiteChannel(channel.getId());
assertThat(Arrays.asList(mSiteChannelsManager.getSiteChannels()), hasSize(0)); assertThat(Arrays.asList(mSiteChannelsManager.getSiteChannels()), hasSize(0));
} }
...@@ -98,7 +100,7 @@ public class SiteChannelsManagerTest { ...@@ -98,7 +100,7 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26) @MinAndroidSdkLevel(26)
@SmallTest @SmallTest
public void testDeleteSiteChannel_channelDoesNotExist() throws Exception { 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"); mSiteChannelsManager.deleteSiteChannel("https://some-other-origin.org");
assertThat(Arrays.asList(mSiteChannelsManager.getSiteChannels()), hasSize(1)); assertThat(Arrays.asList(mSiteChannelsManager.getSiteChannels()), hasSize(1));
} }
...@@ -107,8 +109,9 @@ public class SiteChannelsManagerTest { ...@@ -107,8 +109,9 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26) @MinAndroidSdkLevel(26)
@SmallTest @SmallTest
public void testGetChannelStatus_channelCreatedAsEnabled() throws Exception { public void testGetChannelStatus_channelCreatedAsEnabled() throws Exception {
mSiteChannelsManager.createSiteChannel("https://chromium.org", true); NotificationSettingsBridge.SiteChannel channel =
assertThat(mSiteChannelsManager.getChannelStatus("https://chromium.org"), mSiteChannelsManager.createSiteChannel("https://chromium.org", 0L, true);
assertThat(mSiteChannelsManager.getChannelStatus(channel.getId()),
matchesChannelStatus(NotificationChannelStatus.ENABLED)); matchesChannelStatus(NotificationChannelStatus.ENABLED));
} }
...@@ -118,11 +121,9 @@ public class SiteChannelsManagerTest { ...@@ -118,11 +121,9 @@ public class SiteChannelsManagerTest {
public void testGetChannelStatus_channelCreatedAsBlocked() throws Exception { public void testGetChannelStatus_channelCreatedAsBlocked() throws Exception {
assertThat(mSiteChannelsManager.getChannelStatus("https://foobar.com"), assertThat(mSiteChannelsManager.getChannelStatus("https://foobar.com"),
matchesChannelStatus(NotificationChannelStatus.UNAVAILABLE)); matchesChannelStatus(NotificationChannelStatus.UNAVAILABLE));
mSiteChannelsManager.createSiteChannel("https://foobar.com", false); NotificationSettingsBridge.SiteChannel channel =
assertThat(mNotificationManagerProxy.getNotificationChannel("web:https://foobar.com") mSiteChannelsManager.createSiteChannel("https://foobar.com", 0L, false);
.getImportance(), assertThat(mSiteChannelsManager.getChannelStatus(channel.getId()),
is(NotificationManager.IMPORTANCE_NONE));
assertThat(mSiteChannelsManager.getChannelStatus("https://foobar.com"),
matchesChannelStatus(NotificationChannelStatus.BLOCKED)); matchesChannelStatus(NotificationChannelStatus.BLOCKED));
} }
...@@ -130,7 +131,7 @@ public class SiteChannelsManagerTest { ...@@ -130,7 +131,7 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26) @MinAndroidSdkLevel(26)
@SmallTest @SmallTest
public void testGetChannelStatus_channelNotCreated() throws Exception { public void testGetChannelStatus_channelNotCreated() throws Exception {
assertThat(mSiteChannelsManager.getChannelStatus("https://chromium.org"), assertThat(mSiteChannelsManager.getChannelStatus("invalid-channel-id"),
matchesChannelStatus(NotificationChannelStatus.UNAVAILABLE)); matchesChannelStatus(NotificationChannelStatus.UNAVAILABLE));
} }
...@@ -138,9 +139,10 @@ public class SiteChannelsManagerTest { ...@@ -138,9 +139,10 @@ public class SiteChannelsManagerTest {
@MinAndroidSdkLevel(26) @MinAndroidSdkLevel(26)
@SmallTest @SmallTest
public void testGetChannelStatus_channelCreatedThenDeleted() throws Exception { public void testGetChannelStatus_channelCreatedThenDeleted() throws Exception {
mSiteChannelsManager.createSiteChannel("https://chromium.org", true); NotificationSettingsBridge.SiteChannel channel =
mSiteChannelsManager.deleteSiteChannel("https://chromium.org"); mSiteChannelsManager.createSiteChannel("https://chromium.org", 0L, true);
assertThat(mSiteChannelsManager.getChannelStatus("https://chromium.org"), mSiteChannelsManager.deleteSiteChannel(channel.getId());
assertThat(mSiteChannelsManager.getChannelStatus(channel.getId()),
matchesChannelStatus(NotificationChannelStatus.UNAVAILABLE)); matchesChannelStatus(NotificationChannelStatus.UNAVAILABLE));
} }
......
...@@ -120,7 +120,7 @@ public class NotificationPlatformBridgeUnitTest { ...@@ -120,7 +120,7 @@ public class NotificationPlatformBridgeUnitTest {
// Returns the expected origin for a channel id associated with a particular origin. // Returns the expected origin for a channel id associated with a particular origin.
assertEquals("https://example.com", assertEquals("https://example.com",
NotificationPlatformBridge.getOriginFromChannelId( 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. // Returns null for a channel id that is not associated with a particular origin.
assertNull(NotificationPlatformBridge.getOriginFromChannelId( assertNull(NotificationPlatformBridge.getOriginFromChannelId(
......
...@@ -5,12 +5,14 @@ ...@@ -5,12 +5,14 @@
#ifndef CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_CHANNELS_PROVIDER_ANDROID_H_ #ifndef CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_CHANNELS_PROVIDER_ANDROID_H_
#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_CHANNELS_PROVIDER_ANDROID_H_ #define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_CHANNELS_PROVIDER_ANDROID_H_
#include <map>
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.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_observable_provider.h"
#include "components/content_settings/core/browser/content_settings_observer.h" #include "components/content_settings/core/browser/content_settings_observer.h"
#include "components/content_settings/core/browser/content_settings_rule.h" #include "components/content_settings/core/browser/content_settings_rule.h"
...@@ -23,15 +25,17 @@ ...@@ -23,15 +25,17 @@
enum NotificationChannelStatus { ENABLED, BLOCKED, UNAVAILABLE }; enum NotificationChannelStatus { ENABLED, BLOCKED, UNAVAILABLE };
struct NotificationChannel { struct NotificationChannel {
NotificationChannel(std::string origin, NotificationChannelStatus status) NotificationChannel(const std::string& id,
: origin(origin), status(status) {} const std::string& origin,
const base::Time& timestamp,
NotificationChannelStatus status);
NotificationChannel(const NotificationChannel& other);
bool operator==(const NotificationChannel& other) const { bool operator==(const NotificationChannel& other) const {
return origin == other.origin && status == other.status; return origin == other.origin && status == other.status;
} }
bool operator<(const NotificationChannel& other) const { const std::string id;
return std::tie(origin, status) < std::tie(other.origin, other.status); const std::string origin;
} const base::Time timestamp;
std::string origin;
NotificationChannelStatus status = NotificationChannelStatus::UNAVAILABLE; NotificationChannelStatus status = NotificationChannelStatus::UNAVAILABLE;
}; };
...@@ -47,7 +51,9 @@ class NotificationChannelsProviderAndroid ...@@ -47,7 +51,9 @@ class NotificationChannelsProviderAndroid
public: public:
virtual ~NotificationChannelsBridge() = default; virtual ~NotificationChannelsBridge() = default;
virtual bool ShouldUseChannelSettings() = 0; 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( virtual NotificationChannelStatus GetChannelStatus(
const std::string& origin) = 0; const std::string& origin) = 0;
virtual void DeleteChannel(const std::string& origin) = 0; virtual void DeleteChannel(const std::string& origin) = 0;
...@@ -71,22 +77,48 @@ class NotificationChannelsProviderAndroid ...@@ -71,22 +77,48 @@ class NotificationChannelsProviderAndroid
void ClearAllContentSettingsRules(ContentSettingsType content_type) override; void ClearAllContentSettingsRules(ContentSettingsType content_type) override;
void ShutdownOnUIThread() 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: private:
explicit NotificationChannelsProviderAndroid( explicit NotificationChannelsProviderAndroid(
std::unique_ptr<NotificationChannelsBridge> bridge); std::unique_ptr<NotificationChannelsBridge> bridge,
std::unique_ptr<base::Clock> clock);
friend class NotificationChannelsProviderAndroidTest; friend class NotificationChannelsProviderAndroidTest;
std::vector<NotificationChannel> UpdateCachedChannels() const;
void CreateChannelIfRequired(const std::string& origin_string, void CreateChannelIfRequired(const std::string& origin_string,
NotificationChannelStatus new_channel_status); NotificationChannelStatus new_channel_status);
void InitCachedChannels();
std::unique_ptr<NotificationChannelsBridge> bridge_; std::unique_ptr<NotificationChannelsBridge> bridge_;
bool should_use_channels_; bool should_use_channels_;
// This cache is updated every time GetRuleIterator is called. It is used to std::unique_ptr<base::Clock> clock_;
// check if any channels have changed their status since the previous call,
// in order to notify observers. This is necessary to detect channels getting // Flag to keep track of whether |cached_channels_| has been initialized yet.
// blocked/enabled by the user, in the absence of a callback for this event. bool initialized_cached_channels_;
std::vector<NotificationChannel> 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_; 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