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