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 android.text.TextUtils; 8 9 import org.chromium.base.Callback; 10 import org.chromium.base.ObserverList; 11 import org.chromium.base.task.PostTask; 12 import org.chromium.chrome.browser.download.DownloadInfo; 13 import org.chromium.chrome.browser.download.DownloadItem; 14 import org.chromium.chrome.browser.download.DownloadManagerService; 15 import org.chromium.chrome.browser.download.DownloadManagerService.DownloadObserver; 16 import org.chromium.chrome.browser.download.DownloadMetrics; 17 import org.chromium.chrome.browser.download.DownloadOpenSource; 18 import org.chromium.chrome.browser.download.DownloadUtils; 19 import org.chromium.components.offline_items_collection.ContentId; 20 import org.chromium.components.offline_items_collection.LegacyHelpers; 21 import org.chromium.components.offline_items_collection.OfflineContentProvider; 22 import org.chromium.components.offline_items_collection.OfflineContentProvider.Observer; 23 import org.chromium.components.offline_items_collection.OfflineItem; 24 import org.chromium.components.offline_items_collection.OfflineItemSchedule; 25 import org.chromium.components.offline_items_collection.OfflineItemShareInfo; 26 import org.chromium.components.offline_items_collection.ShareCallback; 27 import org.chromium.components.offline_items_collection.VisualsCallback; 28 import org.chromium.content_public.browser.UiThreadTaskTraits; 29 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.List; 33 34 /** 35 * Glue class responsible for converting downloads from {@link DownloadManagerService} into some 36 * semblance of {@link OfflineContentProvider}. This class handles (1) all download idiosyncrasies 37 * that need to happen in Java before hitting the service and (2) converting from 38 * {@link DownloadItem}s to {@link OfflineItem}s. 39 */ 40 class LegacyDownloadProviderImpl implements DownloadObserver, LegacyDownloadProvider { 41 private final ArrayList<Callback<ArrayList<OfflineItem>>> mRequests = new ArrayList<>(); 42 private final ArrayList<Callback<ArrayList<OfflineItem>>> mOffTheRecordRequests = 43 new ArrayList<>(); 44 45 private final ObserverList<OfflineContentProvider.Observer> mObservers = new ObserverList<>(); 46 47 /** Creates an instance of a {@link LegacyDownloadProvider}. */ LegacyDownloadProviderImpl()48 public LegacyDownloadProviderImpl() { 49 DownloadManagerService.getDownloadManagerService().addDownloadObserver(this); 50 } 51 52 // DownloadObserver (OfflineContentProvider.Observer glue) implementation. 53 /** @see OfflineContentProvider.Observer#onItemsAdded(ArrayList) */ 54 @Override onDownloadItemCreated(DownloadItem item)55 public void onDownloadItemCreated(DownloadItem item) { 56 if (!canShowDownloadItem(item)) return; 57 for (OfflineContentProvider.Observer observer : mObservers) { 58 observer.onItemsAdded(Collections.singletonList(DownloadItem.createOfflineItem(item))); 59 } 60 } 61 62 /** @see OfflineContentProvider.Observer#onItemUpdated(OfflineItem) */ 63 @Override onDownloadItemUpdated(DownloadItem item)64 public void onDownloadItemUpdated(DownloadItem item) { 65 if (!canShowDownloadItem(item)) return; 66 67 OfflineItem offlineItem = DownloadItem.createOfflineItem(item); 68 for (OfflineContentProvider.Observer observer : mObservers) { 69 observer.onItemUpdated(offlineItem, null); 70 } 71 72 if (offlineItem.externallyRemoved) { 73 PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> removeItem(offlineItem)); 74 } 75 } 76 77 /** @see OfflineContentProvider.Observer#onItemRemoved(ContentId) */ 78 @Override onDownloadItemRemoved(String guid, boolean isOffTheRecord)79 public void onDownloadItemRemoved(String guid, boolean isOffTheRecord) { 80 for (OfflineContentProvider.Observer observer : mObservers) { 81 observer.onItemRemoved(LegacyHelpers.buildLegacyContentId(false, guid)); 82 } 83 } 84 85 @Override onAllDownloadsRetrieved(List<DownloadItem> items, boolean offTheRecord)86 public void onAllDownloadsRetrieved(List<DownloadItem> items, boolean offTheRecord) { 87 List<Callback<ArrayList<OfflineItem>>> list = 88 offTheRecord ? mOffTheRecordRequests : mRequests; 89 if (list.isEmpty()) return; 90 91 ArrayList<OfflineItem> offlineItems = new ArrayList<>(); 92 for (DownloadItem item : items) { 93 if (!canShowDownloadItem(item)) continue; 94 offlineItems.add(DownloadItem.createOfflineItem(item)); 95 } 96 97 // Copy the list and clear the original in case the callbacks are reentrant. 98 List<Callback<ArrayList<OfflineItem>>> listCopy = new ArrayList<>(list); 99 list.clear(); 100 101 for (Callback<ArrayList<OfflineItem>> callback : listCopy) callback.onResult(offlineItems); 102 } 103 104 @Override onAddOrReplaceDownloadSharedPreferenceEntry(ContentId id)105 public void onAddOrReplaceDownloadSharedPreferenceEntry(ContentId id) {} 106 107 // LegacyDownloadProvider implementation. 108 @Override addObserver(Observer observer)109 public void addObserver(Observer observer) { 110 mObservers.addObserver(observer); 111 } 112 113 @Override removeObserver(Observer observer)114 public void removeObserver(Observer observer) { 115 mObservers.removeObserver(observer); 116 } 117 118 @Override destroy()119 public void destroy() { 120 DownloadManagerService.getDownloadManagerService().removeDownloadObserver(this); 121 } 122 123 @Override openItem(OfflineItem item)124 public void openItem(OfflineItem item) { 125 // TODO(shaktisahu): May be pass metrics as a param. 126 DownloadManagerService.getDownloadManagerService().openDownload( 127 item.id, item.isOffTheRecord, DownloadOpenSource.DOWNLOAD_HOME); 128 } 129 130 @Override removeItem(OfflineItem item)131 public void removeItem(OfflineItem item) { 132 DownloadManagerService.getDownloadManagerService().removeDownload( 133 item.id.id, item.isOffTheRecord, item.externallyRemoved); 134 FileDeletionQueue.get().delete(item.filePath); 135 } 136 137 @Override cancelDownload(OfflineItem item)138 public void cancelDownload(OfflineItem item) { 139 DownloadMetrics.recordDownloadCancel(DownloadMetrics.CancelFrom.CANCEL_DOWNLOAD_HOME); 140 DownloadManagerService.getDownloadManagerService().cancelDownload( 141 item.id, item.isOffTheRecord); 142 } 143 144 @Override pauseDownload(OfflineItem item)145 public void pauseDownload(OfflineItem item) { 146 DownloadManagerService.getDownloadManagerService().pauseDownload( 147 item.id, item.isOffTheRecord); 148 } 149 150 @Override resumeDownload(OfflineItem item, boolean hasUserGesture)151 public void resumeDownload(OfflineItem item, boolean hasUserGesture) { 152 DownloadInfo.Builder builder = DownloadInfo.builderFromOfflineItem(item, null); 153 154 // This is a temporary hack to work around the assumption that the DownloadItem passed to 155 // DownloadManagerService#resumeDownload() will not be paused. 156 builder.setIsPaused(false); 157 158 DownloadItem downloadItem = 159 new DownloadItem(false /* useAndroidDownloadManager */, builder.build()); 160 161 if (item.isResumable) { 162 DownloadManagerService.getDownloadManagerService().resumeDownload( 163 item.id, downloadItem, hasUserGesture); 164 } else { 165 DownloadManagerService.getDownloadManagerService().retryDownload( 166 item.id, downloadItem, hasUserGesture); 167 } 168 } 169 170 @Override getItemById(ContentId id, Callback<OfflineItem> callback)171 public void getItemById(ContentId id, Callback<OfflineItem> callback) { 172 assert false : "Not supported."; 173 PostTask.postTask(UiThreadTaskTraits.DEFAULT, callback.bind(null)); 174 } 175 176 @Override getAllItems(Callback<ArrayList<OfflineItem>> callback, boolean offTheRecord)177 public void getAllItems(Callback<ArrayList<OfflineItem>> callback, boolean offTheRecord) { 178 List<Callback<ArrayList<OfflineItem>>> list = 179 offTheRecord ? mOffTheRecordRequests : mRequests; 180 181 list.add(callback); 182 if (list.size() > 1) return; 183 DownloadManagerService.getDownloadManagerService().getAllDownloads(offTheRecord); 184 } 185 186 @Override getVisualsForItem(ContentId id, VisualsCallback callback)187 public void getVisualsForItem(ContentId id, VisualsCallback callback) { 188 PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> callback.onVisualsAvailable(id, null)); 189 } 190 191 @Override getShareInfoForItem(OfflineItem item, ShareCallback callback)192 public void getShareInfoForItem(OfflineItem item, ShareCallback callback) { 193 OfflineItemShareInfo info = new OfflineItemShareInfo(); 194 info.uri = DownloadUtils.getUriForItem(item.filePath); 195 PostTask.postTask( 196 UiThreadTaskTraits.DEFAULT, () -> callback.onShareInfoAvailable(item.id, info)); 197 } 198 199 @Override renameItem( OfflineItem item, String name, Callback< Integer> callback)200 public void renameItem( 201 OfflineItem item, String name, Callback</*RenameResult*/ Integer> callback) { 202 DownloadManagerService.getDownloadManagerService().renameDownload( 203 item.id, name, callback, item.isOffTheRecord); 204 } 205 206 @Override changeSchedule(final OfflineItem item, final OfflineItemSchedule schedule)207 public void changeSchedule(final OfflineItem item, final OfflineItemSchedule schedule) { 208 DownloadManagerService.getDownloadManagerService().changeSchedule( 209 item.id, schedule, item.isOffTheRecord); 210 } 211 212 /** 213 * There could be some situations where we can't visually represent this download in the UI. 214 * This should be handled in native/be more generic, but it's here in the glue for now. 215 * @return Whether or not {@code item} should be shown in the UI. 216 */ canShowDownloadItem(DownloadItem item)217 private static boolean canShowDownloadItem(DownloadItem item) { 218 if (TextUtils.isEmpty(item.getDownloadInfo().getFilePath())) return false; 219 if (TextUtils.isEmpty(item.getDownloadInfo().getFileName())) return false; 220 return true; 221 } 222 } 223