Bug 1257492 - DLC Sync: Better scheduling of sync action r?sebastian draft
authorKrishna <k.krish@yahoo.com>
Thu, 18 Aug 2016 23:00:34 +0530
changeset 402677 4067d49354253adb4f69cd0a73ea6c9f5e651254
parent 398172 f99e7b8b787e3069652404c8426dc50805d8d707
child 528741 b321c1f6940c873a97435712e2f749624f6b48ea
push id26733
push userk.krish@yahoo.com
push dateThu, 18 Aug 2016 17:32:19 +0000
reviewerssebastian
bugs1257492
milestone51.0a1
Bug 1257492 - DLC Sync: Better scheduling of sync action r?sebastian MozReview-Commit-ID: HaluOO39A8v
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentHelper.java
mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentJobCreator.java
mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentService.java
mobile/android/base/java/org/mozilla/gecko/dlc/SyncAction.java
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -29,16 +29,17 @@ import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.SuggestedSites;
 import org.mozilla.gecko.delegates.BrowserAppDelegate;
 import org.mozilla.gecko.delegates.OfflineTabStatusDelegate;
 import org.mozilla.gecko.delegates.ScreenshotDelegate;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.distribution.DistributionStoreCallback;
 import org.mozilla.gecko.distribution.PartnerBrowserCustomizationsClient;
 import org.mozilla.gecko.dlc.DownloadContentService;
+import org.mozilla.gecko.dlc.SyncAction;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
 import org.mozilla.gecko.feeds.ContentNotificationsDelegate;
 import org.mozilla.gecko.feeds.FeedService;
 import org.mozilla.gecko.firstrun.FirstrunAnimationContainer;
 import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
 import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
@@ -159,16 +160,19 @@ import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.animation.Interpolator;
 import android.widget.Button;
 import android.widget.ListView;
 import android.widget.RelativeLayout;
 import android.widget.ViewFlipper;
+
+import com.evernote.android.job.JobManager;
+import com.evernote.android.job.JobRequest;
 import com.keepsafe.switchboard.AsyncConfigLoader;
 import com.keepsafe.switchboard.SwitchBoard;
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.ByteArrayOutputStream;
@@ -180,16 +184,17 @@ import java.net.URLEncoder;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Vector;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 
 public class BrowserApp extends GeckoApp
                         implements TabsPanel.TabsLayoutChangeListener,
                                    PropertyAnimator.PropertyAnimationListener,
                                    View.OnKeyListener,
                                    LayerView.DynamicToolbarListener,
                                    BrowserSearch.OnSearchListener,
@@ -1932,20 +1937,20 @@ public class BrowserApp extends GeckoApp
                         @Override
                         public void run() {
                              GeckoPreferences.broadcastStumblerPref(BrowserApp.this);
                         }
                     }, oneSecondInMillis);
                 }
 
                 if (AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
-                    // TODO: Better scheduling of sync action (Bug 1257492)
-                    DownloadContentService.startSync(this);
-
-                    DownloadContentService.startVerification(this);
+                    //To check whether the SyncAction (Periodic Job) is already scheduled to run.
+                    if (JobManager.instance().getAllJobRequestsForTag(SyncAction.JOBTAG).isEmpty()) {
+                        scheduleSyncAction();
+                    }
                 }
 
                 FeedService.setup(this);
 
                 super.handleMessage(event, message);
             } else if (event.equals("Gecko:Ready")) {
                 // Handle this message in GeckoApp, but also enable the Settings
                 // menuitem, which is specific to BrowserApp.
@@ -4105,9 +4110,21 @@ public class BrowserApp extends GeckoApp
         }
 
         if (GeckoProfile.get(this).inGuestMode()) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, method, "guest");
         } else if (Restrictions.isRestrictedProfile(this)) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, method, "restricted");
         }
     }
+
+    /**
+     *  Scheduling the SyncAction as a periodic job. Repeats once in a day.
+     */
+    private void scheduleSyncAction() {
+        int jobId = new JobRequest.Builder(SyncAction.JOBTAG)
+                .setPeriodic(TimeUnit.HOURS.toMillis(24))
+                .setRequiredNetworkType(JobRequest.NetworkType.UNMETERED)
+                .setPersisted(true)
+                .build()
+                .schedule();
+    }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentHelper.java
@@ -0,0 +1,41 @@
+/* -*- 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.dlc;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.util.ProxySelector;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * DownloadContentHelper Class contains all the utility methods for the the Downloadable Contents
+ */
+public class DownloadContentHelper {
+
+    public static HttpURLConnection buildHttpURLConnection(String url)
+            throws BaseAction.UnrecoverableDownloadContentException, IOException {
+        try {
+            System.setProperty("http.keepAlive", "true");
+
+            HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(new URI(url));
+            connection.setRequestProperty("User-Agent", HardwareUtils.isTablet() ?
+                    AppConstants.USER_AGENT_FENNEC_TABLET :
+                    AppConstants.USER_AGENT_FENNEC_MOBILE);
+            connection.setRequestMethod("GET");
+            connection.setInstanceFollowRedirects(true);
+            return connection;
+        } catch (MalformedURLException e) {
+            throw new BaseAction.UnrecoverableDownloadContentException(e);
+        } catch (URISyntaxException e) {
+            throw new BaseAction.UnrecoverableDownloadContentException(e);
+        }
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentJobCreator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentJobCreator.java
@@ -23,11 +23,16 @@ public class DownloadContentJobCreator i
 
     public DownloadContentJobCreator(Context context) {
         this.context = context;
     }
 
     @Override
     public Job create(String tag) {
         catalog = new DownloadContentCatalog(context);
-        return null;
+        switch (tag) {
+            case SyncAction.JOBTAG:
+                return new SyncAction(context, catalog);
+            default:
+                return null;
+        }
     }
 }
\ No newline at end of file
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentService.java
@@ -1,28 +1,27 @@
 /* -*- 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.dlc;
 
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoEvent;
-import org.mozilla.gecko.dlc.catalog.DownloadContent;
-import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
-import org.mozilla.gecko.util.HardwareUtils;
-
 import android.app.IntentService;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
 
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.dlc.catalog.DownloadContent;
+import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
+import org.mozilla.gecko.util.HardwareUtils;
+
 /**
  * Service to handle downloadable content that did not ship with the APK.
  */
 public class DownloadContentService extends IntentService {
     private static final String LOGTAG = "GeckoDLCService";
 
     /**
      * Study: Scan the catalog for "new" content available for download.
@@ -62,22 +61,16 @@ public class DownloadContentService exte
     }
 
     public static void startDownloads(Context context) {
         Intent intent = new Intent(ACTION_DOWNLOAD_CONTENT);
         intent.setComponent(new ComponentName(context, DownloadContentService.class));
         context.startService(intent);
     }
 
-    public static void startSync(Context context) {
-        Intent intent = new Intent(ACTION_SYNCHRONIZE_CATALOG);
-        intent.setComponent(new ComponentName(context, DownloadContentService.class));
-        context.startService(intent);
-    }
-
     public static void startCleanup(Context context) {
         Intent intent = new Intent(ACTION_CLEANUP_FILES);
         intent.setComponent(new ComponentName(context, DownloadContentService.class));
         context.startService(intent);
     }
 
     private DownloadContentCatalog catalog;
 
@@ -125,20 +118,16 @@ public class DownloadContentService exte
                     }
                 });
                 break;
 
             case ACTION_VERIFY_CONTENT:
                 action = new VerifyAction();
                 break;
 
-            case ACTION_SYNCHRONIZE_CATALOG:
-                action = new SyncAction();
-                break;
-
             default:
                 Log.e(LOGTAG, "Unknown action: " + intent.getAction());
                 return;
         }
 
         action.perform(this, catalog);
         catalog.persistChanges();
     }
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/SyncAction.java
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/SyncAction.java
@@ -2,40 +2,53 @@
  * 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.dlc;
 
 import android.content.Context;
 import android.net.Uri;
+import android.support.annotation.NonNull;
 import android.util.Log;
 
+import com.evernote.android.job.Job;
 import com.keepsafe.switchboard.SwitchBoard;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.dlc.catalog.DownloadContent;
 import org.mozilla.gecko.dlc.catalog.DownloadContentBuilder;
 import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
+import org.mozilla.gecko.dlc.BaseAction.RecoverableDownloadContentException;
+import org.mozilla.gecko.dlc.BaseAction.UnrecoverableDownloadContentException;
+import org.mozilla.gecko.dlc.DownloadContentHelper;
 import org.mozilla.gecko.Experiments;
+import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.IOUtils;
+import org.mozilla.gecko.util.ProxySelector;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
 
 /**
  * Sync: Synchronize catalog from a Kinto instance.
  */
-public class SyncAction extends BaseAction {
+public class SyncAction extends Job {
     private static final String LOGTAG = "DLCSyncAction";
 
+    public static final String JOBTAG = "SyncActionJob";
+
     private static final String KINTO_KEY_ID = "id";
     private static final String KINTO_KEY_DELETED = "deleted";
     private static final String KINTO_KEY_DATA = "data";
     private static final String KINTO_KEY_ATTACHMENT = "attachment";
     private static final String KINTO_KEY_ORIGINAL = "original";
 
     private static final String KINTO_PARAMETER_SINCE = "_since";
     private static final String KINTO_PARAMETER_FIELDS = "_fields";
@@ -44,17 +57,24 @@ public class SyncAction extends BaseActi
     /**
      * Kinto endpoint with online version of downloadable content catalog
      *
      * Dev instance:
      * https://kinto-ota.dev.mozaws.net/v1/buckets/dlc/collections/catalog/records
      */
     private static final String CATALOG_ENDPOINT = "https://firefox.settings.services.mozilla.com/v1/buckets/fennec/collections/catalog/records";
 
-    @Override
+    private Context context;
+    private DownloadContentCatalog catalog;
+
+    public SyncAction(Context context, DownloadContentCatalog catalog) {
+        this.context = context;
+        this.catalog = catalog;
+    }
+
     public void perform(Context context, DownloadContentCatalog catalog) {
         Log.d(LOGTAG, "Synchronizing catalog.");
 
         if (!isSyncEnabledForClient(context)) {
             Log.d(LOGTAG, "Sync is not enabled for client. Skipping.");
             return;
         }
 
@@ -102,27 +122,33 @@ public class SyncAction extends BaseActi
         if (studyRequired) {
             startStudyAction(context);
         }
 
         if (cleanupRequired) {
             startCleanupAction(context);
         }
 
+        startVerifyAction(context);
+
         Log.v(LOGTAG, "Done");
     }
 
     protected void startStudyAction(Context context) {
         DownloadContentService.startStudy(context);
     }
 
     protected void startCleanupAction(Context context) {
         DownloadContentService.startCleanup(context);
     }
 
+    protected void startVerifyAction(Context context) {
+        DownloadContentService.startVerification(context);
+    }
+
     protected JSONArray fetchRawCatalog(long lastModified)
             throws RecoverableDownloadContentException, UnrecoverableDownloadContentException {
         HttpURLConnection connection = null;
 
         try {
             Uri.Builder builder = Uri.parse(CATALOG_ENDPOINT).buildUpon();
 
             if (lastModified > 0) {
@@ -132,17 +158,17 @@ public class SyncAction extends BaseActi
             builder.appendQueryParameter(KINTO_PARAMETER_FIELDS,
                     "attachment.location,attachment.original.filename,attachment.original.hash,attachment.hash,type,kind,attachment.original.size,match");
 
             // We want to process items in the order they have been modified. This is to ensure that
             // our last_modified values are correct if we processing is interrupted and not all items
             // have been processed.
             builder.appendQueryParameter(KINTO_PARAMETER_SORT, "last_modified");
 
-            connection = buildHttpURLConnection(builder.build().toString());
+            connection = DownloadContentHelper.buildHttpURLConnection(builder.build().toString());
 
             // TODO: Read 'Alert' header and EOL message if existing (Bug 1249248)
 
             // TODO: Read and use 'Backoff' header if available (Bug 1249251)
 
             // TODO: Add support for Next-Page header (Bug 1257495)
 
             final int responseCode = connection.getResponseCode();
@@ -255,9 +281,18 @@ public class SyncAction extends BaseActi
             Log.w(LOGTAG, "- Errno:   " + error.getInt("errno"));
             Log.w(LOGTAG, "- Error:   " + error.optString("error", "-"));
             Log.w(LOGTAG, "- Message: " + error.optString("message", "-"));
             Log.w(LOGTAG, "- Info:    " + error.optString("info", "-"));
         } catch (JSONException | IOException e) {
             Log.w(LOGTAG, "Could not fetch error response", e);
         }
     }
+
+    @NonNull
+    @Override
+    protected Result onRunJob(Params params) {
+
+        perform(context, catalog);
+
+        return null;
+    }
 }