diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /mobile/android/base/java/org/mozilla/gecko/feeds/action | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/feeds/action')
6 files changed, 774 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java new file mode 100644 index 000000000..09a2b12b6 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java @@ -0,0 +1,281 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.feeds.action; + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; +import android.support.v4.content.ContextCompat; +import android.text.format.DateFormat; + +import org.json.JSONException; +import org.mozilla.gecko.AppConstants; +import org.mozilla.gecko.BrowserApp; +import org.mozilla.gecko.GeckoApp; +import org.mozilla.gecko.R; +import org.mozilla.gecko.Telemetry; +import org.mozilla.gecko.TelemetryContract; +import org.mozilla.gecko.db.BrowserContract; +import org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.db.UrlAnnotations; +import org.mozilla.gecko.feeds.ContentNotificationsDelegate; +import org.mozilla.gecko.feeds.FeedFetcher; +import org.mozilla.gecko.feeds.FeedService; +import org.mozilla.gecko.feeds.parser.Feed; +import org.mozilla.gecko.feeds.subscriptions.FeedSubscription; +import org.mozilla.gecko.preferences.GeckoPreferences; +import org.mozilla.gecko.util.StringUtils; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * CheckForUpdatesAction: Check if feeds we subscribed to have new content available. + */ +public class CheckForUpdatesAction extends FeedAction { + /** + * This extra will be added to Intents fired by the notification. + */ + public static final String EXTRA_CONTENT_NOTIFICATION = "content-notification"; + + private final Context context; + + public CheckForUpdatesAction(Context context) { + this.context = context; + } + + @Override + public void perform(BrowserDB browserDB, Intent intent) { + final UrlAnnotations urlAnnotations = browserDB.getUrlAnnotations(); + final ContentResolver resolver = context.getContentResolver(); + final List<Feed> updatedFeeds = new ArrayList<>(); + + log("Checking feeds for updates.."); + + Cursor cursor = urlAnnotations.getFeedSubscriptions(resolver); + if (cursor == null) { + return; + } + + try { + while (cursor.moveToNext()) { + FeedSubscription subscription = FeedSubscription.fromCursor(cursor); + + FeedFetcher.FeedResponse response = checkFeedForUpdates(subscription); + if (response != null) { + final Feed feed = response.feed; + + if (!hasBeenVisited(browserDB, feed.getLastItem().getURL())) { + // Only notify about this update if the last item hasn't been visited yet. + updatedFeeds.add(feed); + } else { + Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context)); + Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, + TelemetryContract.Method.SERVICE, + "content_update"); + Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context)); + } + + urlAnnotations.updateFeedSubscription(resolver, subscription); + } + } + } catch (JSONException e) { + log("Could not deserialize subscription", e); + } finally { + cursor.close(); + } + + showNotification(updatedFeeds); + } + + private FeedFetcher.FeedResponse checkFeedForUpdates(FeedSubscription subscription) { + log("Checking feed: " + subscription.getFeedTitle()); + + FeedFetcher.FeedResponse response = fetchFeed(subscription); + if (response == null) { + return null; + } + + if (subscription.hasBeenUpdated(response)) { + log("* Feed has changed. New item: " + response.feed.getLastItem().getTitle()); + + subscription.update(response); + + return response; + + } + + return null; + } + + /** + * Returns true if this URL has been visited before. + * + * We do an exact match. So this can fail if the feed uses a different URL and redirects to + * content. But it's better than no checks at all. + */ + private boolean hasBeenVisited(final BrowserDB browserDB, final String url) { + final Cursor cursor = browserDB.getHistoryForURL(context.getContentResolver(), url); + if (cursor == null) { + return false; + } + + try { + if (cursor.moveToFirst()) { + return cursor.getInt(cursor.getColumnIndex(BrowserContract.History.VISITS)) > 0; + } + } finally { + cursor.close(); + } + + return false; + } + + private void showNotification(List<Feed> updatedFeeds) { + final int feedCount = updatedFeeds.size(); + if (feedCount == 0) { + return; + } + + if (feedCount == 1) { + showNotificationForSingleUpdate(updatedFeeds.get(0)); + } else { + showNotificationForMultipleUpdates(updatedFeeds); + } + + Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context)); + Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.NOTIFICATION, "content_update"); + Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context)); + } + + private void showNotificationForSingleUpdate(Feed feed) { + final String date = DateFormat.getMediumDateFormat(context).format(new Date(feed.getLastItem().getTimestamp())); + + final NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle() + .bigText(feed.getLastItem().getTitle()) + .setBigContentTitle(feed.getTitle()) + .setSummaryText(context.getString(R.string.content_notification_updated_on, date)); + + final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, createOpenIntent(feed), PendingIntent.FLAG_UPDATE_CURRENT); + + final Notification notification = new NotificationCompat.Builder(context) + .setSmallIcon(R.drawable.ic_status_logo) + .setContentTitle(feed.getTitle()) + .setContentText(feed.getLastItem().getTitle()) + .setStyle(style) + .setColor(ContextCompat.getColor(context, R.color.fennec_ui_orange)) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .addAction(createOpenAction(feed)) + .addAction(createNotificationSettingsAction()) + .build(); + + NotificationManagerCompat.from(context).notify(R.id.websiteContentNotification, notification); + } + + private void showNotificationForMultipleUpdates(List<Feed> feeds) { + final NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); + for (Feed feed : feeds) { + inboxStyle.addLine(StringUtils.stripScheme(feed.getLastItem().getURL(), StringUtils.UrlFlags.STRIP_HTTPS)); + } + inboxStyle.setSummaryText(context.getString(R.string.content_notification_summary)); + + final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, createOpenIntent(feeds), PendingIntent.FLAG_UPDATE_CURRENT); + + Notification notification = new NotificationCompat.Builder(context) + .setSmallIcon(R.drawable.ic_status_logo) + .setContentTitle(context.getString(R.string.content_notification_title_plural, feeds.size())) + .setContentText(context.getString(R.string.content_notification_summary)) + .setStyle(inboxStyle) + .setColor(ContextCompat.getColor(context, R.color.fennec_ui_orange)) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .addAction(createOpenAction(feeds)) + .setNumber(feeds.size()) + .addAction(createNotificationSettingsAction()) + .build(); + + NotificationManagerCompat.from(context).notify(R.id.websiteContentNotification, notification); + } + + private Intent createOpenIntent(Feed feed) { + final List<Feed> feeds = new ArrayList<>(); + feeds.add(feed); + + return createOpenIntent(feeds); + } + + private Intent createOpenIntent(List<Feed> feeds) { + final ArrayList<String> urls = new ArrayList<>(); + for (Feed feed : feeds) { + urls.add(feed.getLastItem().getURL()); + } + + final Intent intent = new Intent(context, BrowserApp.class); + intent.setAction(ContentNotificationsDelegate.ACTION_CONTENT_NOTIFICATION); + intent.putStringArrayListExtra(ContentNotificationsDelegate.EXTRA_URLS, urls); + + return intent; + } + + private NotificationCompat.Action createOpenAction(Feed feed) { + final List<Feed> feeds = new ArrayList<>(); + feeds.add(feed); + + return createOpenAction(feeds); + } + + private NotificationCompat.Action createOpenAction(List<Feed> feeds) { + Intent intent = createOpenIntent(feeds); + intent.putExtra(ContentNotificationsDelegate.EXTRA_READ_BUTTON, true); + + PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT); + + return new NotificationCompat.Action( + R.drawable.open_in_browser, + context.getString(R.string.content_notification_action_read_now), + pendingIntent); + } + + private NotificationCompat.Action createNotificationSettingsAction() { + final Intent intent = new Intent(GeckoApp.ACTION_LAUNCH_SETTINGS); + intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS); + intent.putExtra(EXTRA_CONTENT_NOTIFICATION, true); + + GeckoPreferences.setResourceToOpen(intent, "preferences_notifications"); + + PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + + return new NotificationCompat.Action( + R.drawable.settings_notifications, + context.getString(R.string.content_notification_action_settings), + settingsIntent); + } + + private FeedFetcher.FeedResponse fetchFeed(FeedSubscription subscription) { + return FeedFetcher.fetchAndParseFeedIfModified( + subscription.getFeedUrl(), + subscription.getETag(), + subscription.getLastModified() + ); + } + + @Override + public boolean requiresNetwork() { + return true; + } + + @Override + public boolean requiresPreferenceEnabled() { + return true; + } +} diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/EnrollSubscriptionsAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/EnrollSubscriptionsAction.java new file mode 100644 index 000000000..b778938fd --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/feeds/action/EnrollSubscriptionsAction.java @@ -0,0 +1,101 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.feeds.action; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.text.TextUtils; + +import org.mozilla.gecko.db.BrowserContract; +import org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.db.UrlAnnotations; +import org.mozilla.gecko.feeds.FeedService; +import org.mozilla.gecko.feeds.knownsites.KnownSiteBlogger; +import org.mozilla.gecko.feeds.knownsites.KnownSite; +import org.mozilla.gecko.feeds.knownsites.KnownSiteMedium; +import org.mozilla.gecko.feeds.knownsites.KnownSiteTumblr; +import org.mozilla.gecko.feeds.knownsites.KnownSiteWordpress; + +/** + * EnrollSubscriptionsAction: Search for bookmarks of known sites we can subscribe to. + */ +public class EnrollSubscriptionsAction extends FeedAction { + private static final String LOGTAG = "FeedEnrollAction"; + + private static final KnownSite[] knownSites = { + new KnownSiteMedium(), + new KnownSiteBlogger(), + new KnownSiteWordpress(), + new KnownSiteTumblr(), + }; + + private Context context; + + public EnrollSubscriptionsAction(Context context) { + this.context = context; + } + + @Override + public void perform(BrowserDB db, Intent intent) { + log("Searching for bookmarks to enroll in updates"); + + final ContentResolver contentResolver = context.getContentResolver(); + + for (KnownSite knownSite : knownSites) { + searchFor(db, contentResolver, knownSite); + } + } + + @Override + public boolean requiresNetwork() { + return false; + } + + @Override + public boolean requiresPreferenceEnabled() { + return true; + } + + private void searchFor(BrowserDB db, ContentResolver contentResolver, KnownSite knownSite) { + final UrlAnnotations urlAnnotations = db.getUrlAnnotations(); + + final Cursor cursor = db.getBookmarksForPartialUrl(contentResolver, knownSite.getURLSearchString()); + if (cursor == null) { + log("Nothing found (" + knownSite.getClass().getSimpleName() + ")"); + return; + } + + try { + log("Found " + cursor.getCount() + " websites"); + + while (cursor.moveToNext()) { + + final String url = cursor.getString(cursor.getColumnIndex(BrowserContract.Bookmarks.URL)); + + log(" URL: " + url); + + String feedUrl = knownSite.getFeedFromURL(url); + if (TextUtils.isEmpty(feedUrl)) { + log("Could not determine feed for URL: " + url); + return; + } + + if (!urlAnnotations.hasFeedUrlForWebsite(contentResolver, url)) { + urlAnnotations.insertFeedUrl(contentResolver, url, feedUrl); + } + + if (!urlAnnotations.hasFeedSubscription(contentResolver, feedUrl)) { + FeedService.subscribe(context, feedUrl); + } + } + } finally { + cursor.close(); + } + } + +} diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/FeedAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/FeedAction.java new file mode 100644 index 000000000..acfaa8b4d --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/feeds/action/FeedAction.java @@ -0,0 +1,58 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.feeds.action; + +import android.content.Intent; +import android.util.Log; + +import org.mozilla.gecko.db.BrowserDB; + +/** + * Interface for actions run by FeedService. + */ +public abstract class FeedAction { + public static final boolean DEBUG_LOG = false; + + /** + * Perform this action. + * + * @param browserDB database instance to perform the action. + * @param intent used to start the service. + */ + public abstract void perform(BrowserDB browserDB, Intent intent); + + /** + * Does this action require an active network connection? + */ + public abstract boolean requiresNetwork(); + + /** + * Should this action only run if the preference is enabled? + */ + public abstract boolean requiresPreferenceEnabled(); + + /** + * This method will swallow all log messages to avoid logging potential personal information. + * + * For debugging purposes set {@code DEBUG_LOG} to true. + */ + public void log(String message) { + if (DEBUG_LOG) { + Log.d("Gecko" + getClass().getSimpleName(), message); + } + } + + /** + * This method will swallow all log messages to avoid logging potential personal information. + * + * For debugging purposes set {@code DEBUG_LOG} to true. + */ + public void log(String message, Throwable throwable) { + if (DEBUG_LOG) { + Log.d("Gecko" + getClass().getSimpleName(), message, throwable); + } + } +} diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java new file mode 100644 index 000000000..f5bf39997 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java @@ -0,0 +1,146 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.feeds.action; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.SystemClock; + +import com.keepsafe.switchboard.SwitchBoard; + +import org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.feeds.FeedAlarmReceiver; +import org.mozilla.gecko.feeds.FeedService; +import org.mozilla.gecko.Experiments; + +import java.text.DateFormat; +import java.util.Calendar; + +/** + * SetupAlarmsAction: Set up alarms to run various actions every now and then. + */ +public class SetupAlarmsAction extends FeedAction { + private static final String LOGTAG = "FeedSetupAction"; + + private Context context; + + public SetupAlarmsAction(Context context) { + this.context = context; + } + + @Override + public void perform(BrowserDB browserDB, Intent intent) { + final AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + cancelPreviousAlarms(alarmManager); + scheduleAlarms(alarmManager); + } + + @Override + public boolean requiresNetwork() { + return false; + } + + @Override + public boolean requiresPreferenceEnabled() { + return false; + } + + private void cancelPreviousAlarms(AlarmManager alarmManager) { + final PendingIntent withdrawIntent = getWithdrawPendingIntent(); + alarmManager.cancel(withdrawIntent); + + final PendingIntent enrollIntent = getEnrollPendingIntent(); + alarmManager.cancel(enrollIntent); + + final PendingIntent checkIntent = getCheckPendingIntent(); + alarmManager.cancel(checkIntent); + + log("Cancelled previous alarms"); + } + + private void scheduleAlarms(AlarmManager alarmManager) { + alarmManager.setInexactRepeating( + AlarmManager.ELAPSED_REALTIME, + SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_FIFTEEN_MINUTES, + AlarmManager.INTERVAL_DAY, + getWithdrawPendingIntent()); + + alarmManager.setInexactRepeating( + AlarmManager.ELAPSED_REALTIME, + SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR, + AlarmManager.INTERVAL_DAY, + getEnrollPendingIntent() + ); + + if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_12HRS)) { + scheduleUpdateCheckEvery12Hours(alarmManager); + } + + if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_8AM)) { + scheduleUpdateAtFullHour(alarmManager, 8); + } + + if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_5PM)) { + scheduleUpdateAtFullHour(alarmManager, 17); + } + + + log("Scheduled alarms"); + } + + private void scheduleUpdateCheckEvery12Hours(AlarmManager alarmManager) { + alarmManager.setInexactRepeating( + AlarmManager.ELAPSED_REALTIME, + SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HOUR, + AlarmManager.INTERVAL_HALF_DAY, + getCheckPendingIntent() + ); + } + + private void scheduleUpdateAtFullHour(AlarmManager alarmManager, int hourOfDay) { + final Calendar calendar = Calendar.getInstance(); + + if (calendar.get(Calendar.HOUR_OF_DAY) >= hourOfDay) { + // This time has already passed today. Try again tomorrow. + calendar.add(Calendar.DAY_OF_MONTH, 1); + } + + calendar.set(Calendar.HOUR_OF_DAY, hourOfDay); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + alarmManager.setInexactRepeating( + AlarmManager.RTC, + calendar.getTimeInMillis(), + AlarmManager.INTERVAL_DAY, + getCheckPendingIntent() + ); + + log("Scheduled update alarm at " + DateFormat.getDateTimeInstance().format(calendar.getTime())); + } + + private PendingIntent getWithdrawPendingIntent() { + Intent intent = new Intent(context, FeedAlarmReceiver.class); + intent.setAction(FeedService.ACTION_WITHDRAW); + return PendingIntent.getBroadcast(context, 0, intent, 0); + } + + private PendingIntent getEnrollPendingIntent() { + Intent intent = new Intent(context, FeedAlarmReceiver.class); + intent.setAction(FeedService.ACTION_ENROLL); + return PendingIntent.getBroadcast(context, 0, intent, 0); + } + + private PendingIntent getCheckPendingIntent() { + Intent intent = new Intent(context, FeedAlarmReceiver.class); + intent.setAction(FeedService.ACTION_CHECK); + return PendingIntent.getBroadcast(context, 0, intent, 0); + } +} diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/SubscribeToFeedAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/SubscribeToFeedAction.java new file mode 100644 index 000000000..fbfce1af2 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/feeds/action/SubscribeToFeedAction.java @@ -0,0 +1,79 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.feeds.action; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import org.mozilla.gecko.Telemetry; +import org.mozilla.gecko.TelemetryContract; +import org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.db.UrlAnnotations; +import org.mozilla.gecko.feeds.FeedFetcher; +import org.mozilla.gecko.feeds.FeedService; +import org.mozilla.gecko.feeds.subscriptions.FeedSubscription; + +/** + * SubscribeToFeedAction: Try to fetch a feed and create a subscription if successful. + */ +public class SubscribeToFeedAction extends FeedAction { + private static final String LOGTAG = "FeedSubscribeAction"; + + public static final String EXTRA_FEED_URL = "feed_url"; + + private Context context; + + public SubscribeToFeedAction(Context context) { + this.context = context; + } + + @Override + public void perform(BrowserDB browserDB, Intent intent) { + final UrlAnnotations urlAnnotations = browserDB.getUrlAnnotations(); + + final Bundle extras = intent.getExtras(); + final String feedUrl = extras.getString(EXTRA_FEED_URL); + + if (urlAnnotations.hasFeedSubscription(context.getContentResolver(), feedUrl)) { + log("Already subscribed to " + feedUrl + ". Skipping."); + return; + } + + log("Subscribing to feed: " + feedUrl); + + subscribe(urlAnnotations, feedUrl); + } + + @Override + public boolean requiresNetwork() { + return true; + } + + @Override + public boolean requiresPreferenceEnabled() { + return true; + } + + private void subscribe(UrlAnnotations urlAnnotations, String feedUrl) { + FeedFetcher.FeedResponse response = FeedFetcher.fetchAndParseFeed(feedUrl); + if (response == null) { + log(String.format("Could not fetch feed (%s). Not subscribing for now.", feedUrl)); + return; + } + + log("Subscribing to feed: " + response.feed.getTitle()); + log(" Last item: " + response.feed.getLastItem().getTitle()); + + final FeedSubscription subscription = FeedSubscription.create(feedUrl, response); + + urlAnnotations.insertFeedSubscription(context.getContentResolver(), subscription); + + Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context)); + Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.SERVICE, "content_update"); + Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context)); + } +} diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/WithdrawSubscriptionsAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/WithdrawSubscriptionsAction.java new file mode 100644 index 000000000..6f955c185 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/feeds/action/WithdrawSubscriptionsAction.java @@ -0,0 +1,109 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.feeds.action; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; + +import org.json.JSONException; +import org.mozilla.gecko.Telemetry; +import org.mozilla.gecko.TelemetryContract; +import org.mozilla.gecko.db.BrowserContract; +import org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.db.UrlAnnotations; +import org.mozilla.gecko.feeds.FeedService; +import org.mozilla.gecko.feeds.subscriptions.FeedSubscription; + +/** + * WithdrawSubscriptionsAction: Look for feeds to unsubscribe from. + */ +public class WithdrawSubscriptionsAction extends FeedAction { + private static final String LOGTAG = "FeedWithdrawAction"; + + private Context context; + + public WithdrawSubscriptionsAction(Context context) { + this.context = context; + } + + @Override + public void perform(BrowserDB browserDB, Intent intent) { + log("Searching for subscriptions to remove.."); + + final UrlAnnotations urlAnnotations = browserDB.getUrlAnnotations(); + final ContentResolver resolver = context.getContentResolver(); + + removeFeedsOfUnknownUrls(browserDB, urlAnnotations, resolver); + removeSubscriptionsOfRemovedFeeds(urlAnnotations, resolver); + } + + /** + * Search for website URLs with a feed assigned. Remove entry if website URL is not known anymore: + * For now this means the website is not bookmarked. + */ + private void removeFeedsOfUnknownUrls(BrowserDB browserDB, UrlAnnotations urlAnnotations, ContentResolver resolver) { + Cursor cursor = urlAnnotations.getWebsitesWithFeedUrl(resolver); + if (cursor == null) { + return; + } + + try { + while (cursor.moveToNext()) { + final String url = cursor.getString(cursor.getColumnIndex(BrowserContract.UrlAnnotations.URL)); + + if (!browserDB.isBookmark(resolver, url)) { + log("Removing feed for unknown URL: " + url); + + urlAnnotations.deleteFeedUrl(resolver, url); + } + } + } finally { + cursor.close(); + } + } + + /** + * Remove subscriptions of feed URLs that are not assigned to a website URL (anymore). + */ + private void removeSubscriptionsOfRemovedFeeds(UrlAnnotations urlAnnotations, ContentResolver resolver) { + Cursor cursor = urlAnnotations.getFeedSubscriptions(resolver); + if (cursor == null) { + return; + } + + try { + while (cursor.moveToNext()) { + final FeedSubscription subscription = FeedSubscription.fromCursor(cursor); + + if (!urlAnnotations.hasWebsiteForFeedUrl(resolver, subscription.getFeedUrl())) { + log("Removing subscription for feed: " + subscription.getFeedUrl()); + + urlAnnotations.deleteFeedSubscription(resolver, subscription); + + Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context)); + Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.SERVICE, "content_update"); + Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context)); + } + } + } catch (JSONException e) { + log("Could not deserialize subscription", e); + } finally { + cursor.close(); + } + } + + @Override + public boolean requiresNetwork() { + return false; + } + + @Override + public boolean requiresPreferenceEnabled() { + return true; + } +} |