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