Bug 1241810 - Add background service and action to check feeds for updates. r?mcomella
MozReview-Commit-ID: 5hD4I65OGX3
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -355,16 +355,21 @@
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.dlc.DownloadContentService">
</service>
<service
+ android:exported="false"
+ android:name="org.mozilla.gecko.feeds.FeedService">
+ </service>
+
+ <service
android:name="org.mozilla.gecko.telemetry.TelemetryUploadService"
android:exported="false"/>
#include ../services/manifests/FxAccountAndroidManifest_services.xml.in
<service
android:name="org.mozilla.gecko.tabqueue.TabReceivedService"
android:exported="false" />
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/feeds/FeedService.java
@@ -0,0 +1,62 @@
+/* -*- 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;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.util.Log;
+
+import com.keepsafe.switchboard.SwitchBoard;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.feeds.action.CheckAction;
+import org.mozilla.gecko.feeds.subscriptions.SubscriptionStorage;
+import org.mozilla.gecko.util.Experiments;
+
+/**
+ * Background service for subscribing to and checking website feeds to notify the user about updates.
+ */
+public class FeedService extends IntentService {
+ private static final String LOGTAG = "GeckoFeedService";
+
+ public static final String ACTION_CHECK = AppConstants.ANDROID_PACKAGE_NAME + ".FEEDS.CHECK";
+
+ private SubscriptionStorage storage;
+
+ public FeedService() {
+ super(LOGTAG);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ storage = new SubscriptionStorage(getApplicationContext());
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ if (intent == null) {
+ return;
+ }
+
+ if (!SwitchBoard.isInExperiment(this, Experiments.CONTENT_NOTIFICATIONS)) {
+ Log.d(LOGTAG, "Not in content notifications experiment. Skipping.");
+ return;
+ }
+
+ switch (intent.getAction()) {
+ case ACTION_CHECK:
+ new CheckAction(this, storage).perform();
+ break;
+
+ default:
+ Log.e(LOGTAG, "Unknown action: " + intent.getAction());
+ }
+
+ storage.persistChanges();
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckAction.java
@@ -0,0 +1,97 @@
+/* -*- 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+
+import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.feeds.FeedFetcher;
+import org.mozilla.gecko.feeds.parser.Feed;
+import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;
+import org.mozilla.gecko.feeds.subscriptions.SubscriptionStorage;
+
+import java.util.List;
+
+/**
+ * CheckAction: Check if feeds we subscribed to have new content available.
+ */
+public class CheckAction {
+ private static final String LOGTAG = "FeedCheckAction";
+
+ private Context context;
+ private SubscriptionStorage storage;
+
+ public CheckAction(Context context, SubscriptionStorage storage) {
+ this.context = context;
+ this.storage = storage;
+ }
+
+ public void perform() {
+ final List<FeedSubscription> subscriptions = storage.getSubscriptions();
+
+ Log.d(LOGTAG, "Checking feeds for updates (" + subscriptions.size() + " feeds) ..");
+
+ for (FeedSubscription subscription : subscriptions) {
+ Log.i(LOGTAG, "Checking feed: " + subscription.getFeedTitle());
+
+ FeedFetcher.FeedResponse response = fetchFeed(subscription);
+ if (response == null) {
+ continue;
+ }
+
+ if (subscription.isNewer(response)) {
+ Log.d(LOGTAG, "* Feed has changed. New item: " + response.feed.getLastItem().getTitle());
+
+ storage.updateSubscription(subscription, response);
+
+ notify(response.feed);
+ }
+ }
+ }
+
+ private void notify(Feed feed) {
+ NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle()
+ .bigText(feed.getLastItem().getTitle())
+ .setBigContentTitle(feed.getTitle())
+ .setSummaryText(feed.getLastItem().getURL());
+
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setComponent(new ComponentName(context, BrowserApp.class));
+ intent.setData(Uri.parse(feed.getLastItem().getURL()));
+
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+
+ 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.link_blue))
+ .setContentIntent(pendingIntent)
+ .setAutoCancel(true)
+ .build();
+
+ NotificationManagerCompat.from(context).notify(R.id.websiteContentNotification, notification);
+ }
+
+ private FeedFetcher.FeedResponse fetchFeed(FeedSubscription subscription) {
+ return FeedFetcher.fetchAndParseFeedIfModified(
+ subscription.getFeedUrl(),
+ subscription.getETag(),
+ subscription.getLastModified()
+ );
+ }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/util/Experiments.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/Experiments.java
@@ -30,16 +30,19 @@ public class Experiments {
public static final String ONBOARDING2_B = "onboarding2-b"; // 4 static Feature slides
public static final String ONBOARDING2_C = "onboarding2-c"; // 4 static + 1 clickable (Data saving) Feature slides
public static final String PREF_ONBOARDING_VERSION = "onboarding_version";
// Show search mode (instead of home panels) when tapping on urlbar if there is a search term in the urlbar.
public static final String SEARCH_TERM = "search-term";
+ // Subscribe to known, bookmarked sites and show a notification if new content is available.
+ public static final String CONTENT_NOTIFICATIONS = "content-notifications";
+
private static volatile Boolean disabled = null;
/**
* Determines whether Switchboard is disabled by the MOZ_DISABLE_SWITCHBOARD
* environment variable. We need to read this value from the intent string
* extra because environment variables from our test harness aren't set
* until Gecko is loaded, and we need to know this before then.
*
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -270,17 +270,19 @@ gbjar.sources += ['java/org/mozilla/geck
'favicons/decoders/FaviconDecoder.java',
'favicons/decoders/ICODecoder.java',
'favicons/decoders/IconDirectoryEntry.java',
'favicons/decoders/LoadFaviconResult.java',
'favicons/Favicons.java',
'favicons/LoadFaviconTask.java',
'favicons/OnFaviconLoadedListener.java',
'favicons/RemoteFavicon.java',
+ 'feeds/action/CheckAction.java',
'feeds/FeedFetcher.java',
+ 'feeds/FeedService.java',
'feeds/parser/Feed.java',
'feeds/parser/Item.java',
'feeds/parser/SimpleFeedParser.java',
'feeds/subscriptions/FeedSubscription.java',
'feeds/subscriptions/SubscriptionStorage.java',
'FilePicker.java',
'FilePickerResultHandler.java',
'FindInPageBar.java',
--- a/mobile/android/base/resources/values/ids.xml
+++ b/mobile/android/base/resources/values/ids.xml
@@ -12,10 +12,11 @@
<item type="id" name="menu_items"/>
<item type="id" name="menu_margin"/>
<item type="id" name="recycler_view_click_support" />
<item type="id" name="range_list"/>
<item type="id" name="pref_header_general"/>
<item type="id" name="pref_header_privacy"/>
<item type="id" name="pref_header_search"/>
<item type="id" name="updateServicePermissionNotification" />
+ <item type="id" name="websiteContentNotification" />
</resources>