Index: chrome/android/java/src/org/chromium/chrome/browser/invalidation/DelayedInvalidationsController.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/invalidation/DelayedInvalidationsController.java b/chrome/android/java/src/org/chromium/chrome/browser/invalidation/DelayedInvalidationsController.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e5d6668ca0522818cb9cb68c04312efa266f602e |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/invalidation/DelayedInvalidationsController.java |
@@ -0,0 +1,154 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.invalidation; |
+ |
+import android.accounts.Account; |
+import android.content.ContentResolver; |
+import android.content.Context; |
+import android.content.SharedPreferences; |
+import android.os.AsyncTask; |
+import android.os.Bundle; |
+import android.preference.PreferenceManager; |
+ |
+import org.chromium.base.ApplicationStatus; |
+import org.chromium.base.Log; |
+import org.chromium.base.VisibleForTesting; |
+import org.chromium.components.invalidation.PendingInvalidation; |
+import org.chromium.sync.AndroidSyncSettings; |
+import org.chromium.sync.signin.AccountManagerHelper; |
+ |
+import java.util.ArrayList; |
+import java.util.Arrays; |
+import java.util.HashSet; |
+import java.util.List; |
+import java.util.Set; |
+ |
+/** |
+ * A class for controlling whether an invalidation should be notified immediately, or should be |
+ * delayed until Chrome comes to the foreground again. |
+ */ |
+public class DelayedInvalidationsController { |
+ private static final String TAG = Log.makeTag("invalidation"); |
+ private static final String DELAYED_ACCOUNT_NAME = "delayed_account"; |
+ private static final String DELAYED_INVALIDATIONS = "delayed_invalidations"; |
+ |
+ private static class LazyHolder { |
+ private static final DelayedInvalidationsController INSTANCE = |
+ new DelayedInvalidationsController(); |
+ } |
+ |
+ public static DelayedInvalidationsController getInstance() { |
+ return LazyHolder.INSTANCE; |
+ } |
+ |
+ @VisibleForTesting |
+ DelayedInvalidationsController() {} |
+ |
+ /** |
+ * Notify any invalidations that were delayed while Chromium was backgrounded. |
+ * @return whether there were any invalidations pending to be notified. |
+ */ |
+ public boolean notifyPendingInvalidations(final Context context) { |
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); |
+ String accountName = prefs.getString(DELAYED_ACCOUNT_NAME, null); |
+ if (accountName == null) { |
+ Log.d(TAG, "No pending invalidations."); |
+ return false; |
+ } else { |
+ Log.d(TAG, "Handling pending invalidations."); |
+ Account account = AccountManagerHelper.createAccountFromName(accountName); |
+ List<Bundle> bundles = popPendingInvalidations(context); |
+ notifyInvalidationsOnBackgroundThread(context, account, bundles); |
+ return true; |
+ } |
+ } |
+ |
+ /** |
+ * Calls ContentResolver.requestSync() in a separate thread as it performs some blocking |
+ * IO operations. |
+ */ |
+ @VisibleForTesting |
+ void notifyInvalidationsOnBackgroundThread( |
+ final Context context, final Account account, final List<Bundle> bundles) { |
+ new AsyncTask<Void, Void, Void>() { |
+ @Override |
+ protected Void doInBackground(Void... unused) { |
+ String contractAuthority = AndroidSyncSettings.getContractAuthority(context); |
+ for (Bundle bundle : bundles) { |
+ ContentResolver.requestSync(account, contractAuthority, bundle); |
+ } |
+ return null; |
+ } |
+ }.execute(); |
+ } |
+ |
+ /** |
+ * Stores preferences to indicate that an invalidation has arrived, but dropped on the floor. |
+ */ |
+ @VisibleForTesting |
+ void addPendingInvalidation(Context context, String account, PendingInvalidation invalidation) { |
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); |
+ String oldAccount = prefs.getString(DELAYED_ACCOUNT_NAME, null); |
+ Set<String> invals = prefs.getStringSet(DELAYED_INVALIDATIONS, new HashSet<String>(1)); |
+ assert invals.isEmpty() || oldAccount != null; |
+ if (oldAccount != null && !oldAccount.equals(account)) { |
+ invals.clear(); |
+ } |
+ SharedPreferences.Editor editor = prefs.edit(); |
+ editor.putString(DELAYED_ACCOUNT_NAME, account); |
+ if (invalidation.mObjectSource == 0 || (oldAccount != null && invals.isEmpty())) { |
+ editor.putStringSet(DELAYED_INVALIDATIONS, null); |
+ } else { |
+ invals.add(invalidation.encodeToString()); |
+ editor.putStringSet(DELAYED_INVALIDATIONS, invals); |
+ } |
+ editor.apply(); |
+ } |
+ |
+ private List<Bundle> popPendingInvalidations(final Context context) { |
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); |
+ assert prefs.contains(DELAYED_ACCOUNT_NAME); |
+ Set<String> savedInvalidations = prefs.getStringSet(DELAYED_INVALIDATIONS, null); |
+ clearPendingInvalidations(context); |
+ // Absence of specific invalidations indicates invalidate all types. |
+ if (savedInvalidations == null) return Arrays.asList(new Bundle()); |
+ |
+ List<Bundle> bundles = new ArrayList<Bundle>(savedInvalidations.size()); |
+ for (String invalidation : savedInvalidations) { |
+ Bundle bundle = PendingInvalidation.decodeToBundle(invalidation); |
+ if (bundle == null) { |
+ Log.e(TAG, "Error parsing saved invalidation. Invalidating all."); |
+ return Arrays.asList(new Bundle()); |
+ } |
+ bundles.add(bundle); |
+ } |
+ return bundles; |
+ } |
+ |
+ /** |
+ * If there are any pending invalidations, they will be cleared. |
+ */ |
+ @VisibleForTesting |
+ public void clearPendingInvalidations(Context context) { |
+ SharedPreferences.Editor editor = |
+ PreferenceManager.getDefaultSharedPreferences(context).edit(); |
+ editor.putString(DELAYED_ACCOUNT_NAME, null); |
+ editor.putStringSet(DELAYED_INVALIDATIONS, null); |
+ editor.apply(); |
+ } |
+ |
+ @VisibleForTesting |
+ boolean shouldNotifyInvalidation(Bundle extras) { |
+ return isManualRequest(extras) || ApplicationStatus.hasVisibleActivities(); |
+ } |
+ |
+ private static boolean isManualRequest(Bundle extras) { |
+ if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) { |
+ Log.d(TAG, "Manual sync requested."); |
+ return true; |
+ } |
+ return false; |
+ } |
+} |