OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.chrome.browser.download.items; | |
6 | |
7 import org.chromium.components.offline_items_collection.ContentId; | |
8 import org.chromium.components.offline_items_collection.OfflineContentProvider; | |
9 import org.chromium.components.offline_items_collection.OfflineItem; | |
10 import org.chromium.components.offline_items_collection.OfflineItemState; | |
11 | |
12 import java.util.ArrayList; | |
13 import java.util.HashMap; | |
14 import java.util.HashSet; | |
15 import java.util.Iterator; | |
16 import java.util.Map; | |
17 import java.util.Map.Entry; | |
18 import java.util.Set; | |
19 | |
20 /** | |
21 * A glue class that bridges an OfflineContentProvider with an Android notificat ion UI layer. This | |
22 * class assumes that the UI layer might not necessarily be ready to receive eve nts (which is true | |
23 * in the case of a {@link Service}), so it can queue updates until the UI is re ady. | |
24 */ | |
25 public class OfflineContentAggregatorNotifier implements OfflineContentProvider. Observer { | |
26 /** | |
27 * An interface that represents the Android notification UI surface that thi s class will post | |
28 * events to. | |
29 */ | |
30 public interface NotifierUi { | |
31 /** | |
32 * Called when this {@link OfflineContentAggregatorNotifier} needs the U I to be available | |
33 * because it has to send an update to it. | |
34 * @param onReadyEvent A {@link Runnable} that should be run when the UI becomes available. | |
35 * Should only be called if this method returns {@co de false}. | |
36 * @return Whether or not the UI is available. If not, {@co de onReadyEvent} | |
37 * will be notified once the UI is available. | |
38 */ | |
39 boolean onUiNeeded(Runnable onReadyEvent); | |
40 | |
41 /** | |
42 * Called when this {@link OfflineContentAggregatorNotifier} no longer e xpects to send | |
43 * updates to the UI in the short term and it can shut down or suspend i tself. | |
44 */ | |
45 void onUiNotNeeded(); | |
46 | |
47 /** | |
48 * Called when there is an update to {@code item} that needs to be propa gated to the UI. | |
49 * The item might be new or might be an update to an existing {@link Off lineItem}. | |
50 * @param item The {@link OfflineItem} to show state for. | |
51 */ | |
52 void updateItem(OfflineItem item); | |
53 | |
54 /** | |
55 * Called when {@code id} has been removed from the underlying data sour ce and any UI should | |
56 * be removed. | |
57 * @param id The {@link ContentId} of the {@link OfflineItem} to remove from the UI. | |
58 */ | |
59 void removeItem(ContentId id); | |
60 } | |
61 | |
62 /** Any updates from {@code mProvider} that have not been propagated to {@co de mUi} yet. */ | |
gone
2017/03/20 19:03:36
@link #mUi, etc
David Trainor- moved to gerrit
2017/03/25 03:31:13
Done.
| |
63 private final Set<ContentId> mPendingDeadUpdates = new HashSet<>(); | |
64 | |
65 /** Any removals from {@code mProvider} that have not been propagated to {@c ode mUi} yet. */ | |
66 private final Map<ContentId, OfflineItem> mPendingLiveUpdates = new HashMap< >(); | |
67 | |
68 /** | |
69 * 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.
| |
70 * {@link OfflineItem#state} is {@link OfflineItemState#IN_PROGRESS} or | |
71 * {@link OfflineItemState#PENDING}. | |
72 */ | |
73 private final Set<ContentId> mActiveItems = new HashSet<>(); | |
74 | |
75 private final OfflineContentProvider mProvider; | |
76 private final NotifierUi mUi; | |
77 | |
78 /** A helper {@link Runnable} that will be called when {@code mUi} is initia lized and ready. */ | |
79 private final Runnable mUiReadyObserver = new Runnable() { | |
80 @Override | |
81 public void run() { | |
82 flushPendingActions(); | |
83 } | |
84 }; | |
85 | |
86 /** | |
87 * Creates an instance of {@link OfflineContentAggregatorNotifier} that will glue | |
88 * {@code provider} to {@code ui}. | |
89 * @param provider The {@link OfflineContentProvider} to expose to {@code ui }. | |
90 * @param ui The {@link NotifierUi} that will visually represent {@cod e provider}. | |
91 */ | |
92 public OfflineContentAggregatorNotifier(OfflineContentProvider provider, Not ifierUi ui) { | |
93 mProvider = provider; | |
94 mUi = ui; | |
95 | |
96 mProvider.addObserver(this); | |
97 } | |
98 | |
99 /** | |
100 * Destroys this {@link OfflineContentAggregatorNotifier}. This will detach from any internal | |
101 * links to the glued objects specified in the constructor. | |
102 */ | |
103 public void destroy() { | |
104 mProvider.removeObserver(this); | |
105 } | |
106 | |
107 private void flushPendingActions() { | |
108 for (Iterator<ContentId> it = mPendingDeadUpdates.iterator(); it.hasNext ();) { | |
109 ContentId id = it.next(); | |
110 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
| |
111 | |
112 removeItemInternal(id); | |
113 it.remove(); | |
114 } | |
115 | |
116 for (Iterator<Entry<ContentId, OfflineItem>> it = mPendingLiveUpdates.en trySet().iterator(); | |
117 it.hasNext();) { | |
118 Entry<ContentId, OfflineItem> item = it.next(); | |
119 if (!mUi.onUiNeeded(mUiReadyObserver)) break; | |
120 | |
121 updateItemInternal(item.getValue()); | |
122 it.remove(); | |
123 } | |
124 | |
125 if (mActiveItems.isEmpty()) mUi.onUiNotNeeded(); | |
126 } | |
127 | |
128 private void processLiveItem(OfflineItem item) { | |
129 if (mUi.onUiNeeded(mUiReadyObserver)) { | |
130 updateItemInternal(item); | |
131 if (mActiveItems.isEmpty()) mUi.onUiNotNeeded(); | |
132 } else { | |
133 mPendingDeadUpdates.remove(item.id); | |
134 mPendingLiveUpdates.put(item.id, item); | |
135 } | |
136 } | |
137 | |
138 private void processDeadItem(ContentId id) { | |
139 if (mUi.onUiNeeded(mUiReadyObserver)) { | |
140 removeItemInternal(id); | |
141 if (mActiveItems.isEmpty()) mUi.onUiNotNeeded(); | |
142 } else { | |
143 mPendingDeadUpdates.add(id); | |
144 mPendingLiveUpdates.remove(id); | |
145 } | |
146 } | |
147 | |
148 private void updateItemInternal(OfflineItem item) { | |
149 switch (item.state) { | |
150 case OfflineItemState.IN_PROGRESS: | |
151 mActiveItems.add(item.id); | |
152 break; | |
153 case OfflineItemState.PENDING: | |
154 case OfflineItemState.COMPLETE: | |
155 case OfflineItemState.CANCELLED: | |
156 case OfflineItemState.INTERRUPTED: | |
157 case OfflineItemState.FAILED: | |
158 case OfflineItemState.PAUSED: | |
159 mActiveItems.remove(item.id); | |
160 break; | |
161 } | |
162 mUi.updateItem(item); | |
163 } | |
164 | |
165 private void removeItemInternal(ContentId id) { | |
166 mActiveItems.remove(id); | |
167 mUi.removeItem(id); | |
168 } | |
169 | |
170 // OfflineContentProvider.Observer implementation. | |
171 @Override | |
172 public void onItemsAvailable() { | |
173 // TODO(dtrainor): Query all items and push the current state to notific ations? | |
174 } | |
175 | |
176 @Override | |
177 public void onItemsAdded(ArrayList<OfflineItem> items) { | |
178 for (int i = 0; i < items.size(); i++) { | |
179 OfflineItem item = items.get(i); | |
180 | |
181 // Only update the UI for new OfflineItems that are in progress or p ending. | |
182 if (item.state == OfflineItemState.IN_PROGRESS | |
183 || item.state == OfflineItemState.PENDING) { | |
184 processLiveItem(item); | |
185 } | |
186 } | |
187 } | |
188 | |
189 @Override | |
190 public void onItemRemoved(ContentId id) { | |
191 processDeadItem(id); | |
192 } | |
193 | |
194 @Override | |
195 public void onItemUpdated(OfflineItem item) { | |
196 processLiveItem(item); | |
197 } | |
198 } | |
OLD | NEW |