Bug 1257492 - DLC Sync: Better scheduling of sync action r?sebastian
MozReview-Commit-ID: HaluOO39A8v
--- 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;
+ }
}