Chromium Code Reviews| 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); |
| + } |
| +} |