summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/feeds/action
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /mobile/android/base/java/org/mozilla/gecko/feeds/action
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-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')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java281
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/EnrollSubscriptionsAction.java101
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/FeedAction.java58
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java146
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/SubscribeToFeedAction.java79
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/WithdrawSubscriptionsAction.java109
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;
+ }
+}