Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3708)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotifier.java

Issue 2754363004: OfflineContentProvider changes to start service (Closed)
Patch Set: Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotifier.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotifier.java b/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotifier.java
new file mode 100644
index 0000000000000000000000000000000000000000..a23b9904a0c427b096a92d72704f81e506fbfb9a
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotifier.java
@@ -0,0 +1,198 @@
+// Copyright 2017 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.download.items;
+
+import org.chromium.components.offline_items_collection.ContentId;
+import org.chromium.components.offline_items_collection.OfflineContentProvider;
+import org.chromium.components.offline_items_collection.OfflineItem;
+import org.chromium.components.offline_items_collection.OfflineItemState;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * A glue class that bridges an OfflineContentProvider with an Android notification UI layer. This
+ * class assumes that the UI layer might not necessarily be ready to receive events (which is true
+ * in the case of a {@link Service}), so it can queue updates until the UI is ready.
+ */
+public class OfflineContentAggregatorNotifier implements OfflineContentProvider.Observer {
+ /**
+ * An interface that represents the Android notification UI surface that this class will post
+ * events to.
+ */
+ public interface NotifierUi {
+ /**
+ * Called when this {@link OfflineContentAggregatorNotifier} needs the UI to be available
+ * because it has to send an update to it.
+ * @param onReadyEvent A {@link Runnable} that should be run when the UI becomes available.
+ * Should only be called if this method returns {@code false}.
+ * @return Whether or not the UI is available. If not, {@code onReadyEvent}
+ * will be notified once the UI is available.
+ */
+ boolean onUiNeeded(Runnable onReadyEvent);
+
+ /**
+ * Called when this {@link OfflineContentAggregatorNotifier} no longer expects to send
+ * updates to the UI in the short term and it can shut down or suspend itself.
+ */
+ void onUiNotNeeded();
+
+ /**
+ * Called when there is an update to {@code item} that needs to be propagated to the UI.
+ * The item might be new or might be an update to an existing {@link OfflineItem}.
+ * @param item The {@link OfflineItem} to show state for.
+ */
+ void updateItem(OfflineItem item);
+
+ /**
+ * Called when {@code id} has been removed from the underlying data source and any UI should
+ * be removed.
+ * @param id The {@link ContentId} of the {@link OfflineItem} to remove from the UI.
+ */
+ void removeItem(ContentId id);
+ }
+
+ /** Any updates from {@code mProvider} that have not been propagated to {@code mUi} yet. */
gone 2017/03/20 19:03:36 @link #mUi, etc
David Trainor- moved to gerrit 2017/03/25 03:31:13 Done.
+ private final Set<ContentId> mPendingDeadUpdates = new HashSet<>();
+
+ /** Any removals from {@code mProvider} that have not been propagated to {@code mUi} yet. */
+ private final Map<ContentId, OfflineItem> mPendingLiveUpdates = new HashMap<>();
+
+ /**
+ * A list of 'active' {@link OfflineItem}'s as currently known by this class. 'Active' means
gone 2017/03/20 19:03:36 no apostrophe before s
David Trainor- moved to gerrit 2017/03/25 03:31:13 Done.
+ * {@link OfflineItem#state} is {@link OfflineItemState#IN_PROGRESS} or
+ * {@link OfflineItemState#PENDING}.
+ */
+ private final Set<ContentId> mActiveItems = new HashSet<>();
+
+ private final OfflineContentProvider mProvider;
+ private final NotifierUi mUi;
+
+ /** A helper {@link Runnable} that will be called when {@code mUi} is initialized and ready. */
+ private final Runnable mUiReadyObserver = new Runnable() {
+ @Override
+ public void run() {
+ flushPendingActions();
+ }
+ };
+
+ /**
+ * Creates an instance of {@link OfflineContentAggregatorNotifier} that will glue
+ * {@code provider} to {@code ui}.
+ * @param provider The {@link OfflineContentProvider} to expose to {@code ui}.
+ * @param ui The {@link NotifierUi} that will visually represent {@code provider}.
+ */
+ public OfflineContentAggregatorNotifier(OfflineContentProvider provider, NotifierUi ui) {
+ mProvider = provider;
+ mUi = ui;
+
+ mProvider.addObserver(this);
+ }
+
+ /**
+ * Destroys this {@link OfflineContentAggregatorNotifier}. This will detach from any internal
+ * links to the glued objects specified in the constructor.
+ */
+ public void destroy() {
+ mProvider.removeObserver(this);
+ }
+
+ private void flushPendingActions() {
+ for (Iterator<ContentId> it = mPendingDeadUpdates.iterator(); it.hasNext();) {
+ ContentId id = it.next();
+ if (!mUi.onUiNeeded(mUiReadyObserver)) break;
gone 2017/03/20 19:03:36 does this really need to happen for every single i
David Trainor- moved to gerrit 2017/03/25 03:31:13 I think the main worry is that the service could d
+
+ removeItemInternal(id);
+ it.remove();
+ }
+
+ for (Iterator<Entry<ContentId, OfflineItem>> it = mPendingLiveUpdates.entrySet().iterator();
+ it.hasNext();) {
+ Entry<ContentId, OfflineItem> item = it.next();
+ if (!mUi.onUiNeeded(mUiReadyObserver)) break;
+
+ updateItemInternal(item.getValue());
+ it.remove();
+ }
+
+ if (mActiveItems.isEmpty()) mUi.onUiNotNeeded();
+ }
+
+ private void processLiveItem(OfflineItem item) {
+ if (mUi.onUiNeeded(mUiReadyObserver)) {
+ updateItemInternal(item);
+ if (mActiveItems.isEmpty()) mUi.onUiNotNeeded();
+ } else {
+ mPendingDeadUpdates.remove(item.id);
+ mPendingLiveUpdates.put(item.id, item);
+ }
+ }
+
+ private void processDeadItem(ContentId id) {
+ if (mUi.onUiNeeded(mUiReadyObserver)) {
+ removeItemInternal(id);
+ if (mActiveItems.isEmpty()) mUi.onUiNotNeeded();
+ } else {
+ mPendingDeadUpdates.add(id);
+ mPendingLiveUpdates.remove(id);
+ }
+ }
+
+ private void updateItemInternal(OfflineItem item) {
+ switch (item.state) {
+ case OfflineItemState.IN_PROGRESS:
+ mActiveItems.add(item.id);
+ break;
+ case OfflineItemState.PENDING:
+ case OfflineItemState.COMPLETE:
+ case OfflineItemState.CANCELLED:
+ case OfflineItemState.INTERRUPTED:
+ case OfflineItemState.FAILED:
+ case OfflineItemState.PAUSED:
+ mActiveItems.remove(item.id);
+ break;
+ }
+ mUi.updateItem(item);
+ }
+
+ private void removeItemInternal(ContentId id) {
+ mActiveItems.remove(id);
+ mUi.removeItem(id);
+ }
+
+ // OfflineContentProvider.Observer implementation.
+ @Override
+ public void onItemsAvailable() {
+ // TODO(dtrainor): Query all items and push the current state to notifications?
+ }
+
+ @Override
+ public void onItemsAdded(ArrayList<OfflineItem> items) {
+ for (int i = 0; i < items.size(); i++) {
+ OfflineItem item = items.get(i);
+
+ // Only update the UI for new OfflineItems that are in progress or pending.
+ if (item.state == OfflineItemState.IN_PROGRESS
+ || item.state == OfflineItemState.PENDING) {
+ processLiveItem(item);
+ }
+ }
+ }
+
+ @Override
+ public void onItemRemoved(ContentId id) {
+ processDeadItem(id);
+ }
+
+ @Override
+ public void onItemUpdated(OfflineItem item) {
+ processLiveItem(item);
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698