1 // Copyright 2018 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.home;
6 
7 import org.chromium.base.CollectionUtil;
8 import org.chromium.base.ObserverList;
9 import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterObserver;
10 import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterSource;
11 import org.chromium.chrome.browser.download.home.glue.OfflineContentProviderGlue;
12 import org.chromium.components.offline_items_collection.ContentId;
13 import org.chromium.components.offline_items_collection.OfflineContentProvider;
14 import org.chromium.components.offline_items_collection.OfflineItem;
15 import org.chromium.components.offline_items_collection.UpdateDelta;
16 
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 
25 /**
26  * The source of {@link OfflineItem} for the rest of the download home UI.  This will pull items
27  * from a {@link OfflineContentProvider} as well as the downloads backend and unify them to a single
28  * list for the rest of the UI to filter and act on.
29  */
30 public class OfflineItemSource implements OfflineItemFilterSource, OfflineContentProvider.Observer {
31     // TODO(dtrainor): Move this to OfflineContentProvider once downloads are ported.
32     private final OfflineContentProviderGlue mProvider;
33 
34     private final Map<ContentId, OfflineItem> mItems = new HashMap<>();
35     private final ObserverList<OfflineItemFilterObserver> mObservers = new ObserverList<>();
36 
37     /** Used to track whether or not the items have been loaded from {@code mProvider} or not. */
38     private boolean mItemsAvailable;
39 
40     /**
41      * Used to track whether or not this is destroyed so we know whether or not to do additional
42      * work when outstanding callbacks return.
43      */
44     private boolean mDestroyed;
45 
46     /**
47      * Creates an instance of {@link OfflineItemSource} and hooks up to {@code provider}.  This will
48      * automatically try to pull all existing items from {@code provider}.
49      * @param provider The {@link OfflineContentProviderGlue} to reflect in this source.
50      */
OfflineItemSource(OfflineContentProviderGlue provider)51     public OfflineItemSource(OfflineContentProviderGlue provider) {
52         mProvider = provider;
53         mProvider.addObserver(this);
54 
55         mProvider.getAllItems(items -> {
56             if (mDestroyed) return;
57 
58             mItemsAvailable = true;
59             for (OfflineItemFilterObserver observer : mObservers) observer.onItemsAvailable();
60             onItemsAdded(items);
61         });
62     }
63 
64     /**
65      * Destroys this object which means it (1) no longer contains any items and (2) no longer
66      * notifies any observers of changes.
67      */
destroy()68     public void destroy() {
69         mProvider.removeObserver(this);
70         mObservers.clear();
71         mItems.clear();
72         mDestroyed = true;
73     }
74 
75     // OfflineItemFilterSource implementation.
76     @Override
getItems()77     public Collection<OfflineItem> getItems() {
78         return mItems.values();
79     }
80 
81     @Override
areItemsAvailable()82     public boolean areItemsAvailable() {
83         return mItemsAvailable;
84     }
85 
86     @Override
addObserver(OfflineItemFilterObserver observer)87     public void addObserver(OfflineItemFilterObserver observer) {
88         mObservers.addObserver(observer);
89     }
90 
91     @Override
removeObserver(OfflineItemFilterObserver observer)92     public void removeObserver(OfflineItemFilterObserver observer) {
93         mObservers.removeObserver(observer);
94     }
95 
96     // OfflineContentProvider.Observer implementation.
97     @Override
onItemsAdded(List<OfflineItem> items)98     public void onItemsAdded(List<OfflineItem> items) {
99         Set<OfflineItem> addedItems = new HashSet<OfflineItem>();
100         for (OfflineItem item : items) {
101             if (mItems.containsKey(item.id)) {
102                 onItemUpdated(item, null);
103             } else {
104                 mItems.put(item.id, item);
105                 addedItems.add(item);
106             }
107         }
108 
109         if (addedItems.size() > 0) {
110             for (OfflineItemFilterObserver observer : mObservers) observer.onItemsAdded(addedItems);
111         }
112     }
113 
114     @Override
onItemRemoved(ContentId id)115     public void onItemRemoved(ContentId id) {
116         OfflineItem item = mItems.remove(id);
117         if (item == null) return;
118 
119         Set<OfflineItem> removedSet = CollectionUtil.newHashSet(item);
120         for (OfflineItemFilterObserver observer : mObservers) observer.onItemsRemoved(removedSet);
121     }
122 
123     @Override
onItemUpdated(OfflineItem item, UpdateDelta updateDelta)124     public void onItemUpdated(OfflineItem item, UpdateDelta updateDelta) {
125         OfflineItem oldItem = mItems.get(item.id);
126         if (oldItem == null) {
127             onItemsAdded(Collections.singletonList(item));
128         } else {
129             mItems.put(item.id, item);
130             for (OfflineItemFilterObserver observer : mObservers) {
131                 observer.onItemUpdated(oldItem, item);
132             }
133         }
134     }
135 }
136