// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.download.home; import android.text.TextUtils; import org.chromium.base.Callback; import org.chromium.base.ObserverList; import org.chromium.base.task.PostTask; import org.chromium.chrome.browser.download.DownloadInfo; import org.chromium.chrome.browser.download.DownloadItem; import org.chromium.chrome.browser.download.DownloadManagerService; import org.chromium.chrome.browser.download.DownloadManagerService.DownloadObserver; import org.chromium.chrome.browser.download.DownloadMetrics; import org.chromium.chrome.browser.download.DownloadOpenSource; import org.chromium.chrome.browser.download.DownloadUtils; import org.chromium.components.offline_items_collection.ContentId; import org.chromium.components.offline_items_collection.LegacyHelpers; import org.chromium.components.offline_items_collection.OfflineContentProvider; import org.chromium.components.offline_items_collection.OfflineContentProvider.Observer; import org.chromium.components.offline_items_collection.OfflineItem; import org.chromium.components.offline_items_collection.OfflineItemSchedule; import org.chromium.components.offline_items_collection.OfflineItemShareInfo; import org.chromium.components.offline_items_collection.ShareCallback; import org.chromium.components.offline_items_collection.VisualsCallback; import org.chromium.content_public.browser.UiThreadTaskTraits; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Glue class responsible for converting downloads from {@link DownloadManagerService} into some * semblance of {@link OfflineContentProvider}. This class handles (1) all download idiosyncrasies * that need to happen in Java before hitting the service and (2) converting from * {@link DownloadItem}s to {@link OfflineItem}s. */ class LegacyDownloadProviderImpl implements DownloadObserver, LegacyDownloadProvider { private final ArrayList>> mRequests = new ArrayList<>(); private final ArrayList>> mOffTheRecordRequests = new ArrayList<>(); private final ObserverList mObservers = new ObserverList<>(); /** Creates an instance of a {@link LegacyDownloadProvider}. */ public LegacyDownloadProviderImpl() { DownloadManagerService.getDownloadManagerService().addDownloadObserver(this); } // DownloadObserver (OfflineContentProvider.Observer glue) implementation. /** @see OfflineContentProvider.Observer#onItemsAdded(ArrayList) */ @Override public void onDownloadItemCreated(DownloadItem item) { if (!canShowDownloadItem(item)) return; for (OfflineContentProvider.Observer observer : mObservers) { observer.onItemsAdded(Collections.singletonList(DownloadItem.createOfflineItem(item))); } } /** @see OfflineContentProvider.Observer#onItemUpdated(OfflineItem) */ @Override public void onDownloadItemUpdated(DownloadItem item) { if (!canShowDownloadItem(item)) return; OfflineItem offlineItem = DownloadItem.createOfflineItem(item); for (OfflineContentProvider.Observer observer : mObservers) { observer.onItemUpdated(offlineItem, null); } if (offlineItem.externallyRemoved) { PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> removeItem(offlineItem)); } } /** @see OfflineContentProvider.Observer#onItemRemoved(ContentId) */ @Override public void onDownloadItemRemoved(String guid, boolean isOffTheRecord) { for (OfflineContentProvider.Observer observer : mObservers) { observer.onItemRemoved(LegacyHelpers.buildLegacyContentId(false, guid)); } } @Override public void onAllDownloadsRetrieved(List items, boolean offTheRecord) { List>> list = offTheRecord ? mOffTheRecordRequests : mRequests; if (list.isEmpty()) return; ArrayList offlineItems = new ArrayList<>(); for (DownloadItem item : items) { if (!canShowDownloadItem(item)) continue; offlineItems.add(DownloadItem.createOfflineItem(item)); } // Copy the list and clear the original in case the callbacks are reentrant. List>> listCopy = new ArrayList<>(list); list.clear(); for (Callback> callback : listCopy) callback.onResult(offlineItems); } @Override public void onAddOrReplaceDownloadSharedPreferenceEntry(ContentId id) {} // LegacyDownloadProvider implementation. @Override public void addObserver(Observer observer) { mObservers.addObserver(observer); } @Override public void removeObserver(Observer observer) { mObservers.removeObserver(observer); } @Override public void destroy() { DownloadManagerService.getDownloadManagerService().removeDownloadObserver(this); } @Override public void openItem(OfflineItem item) { // TODO(shaktisahu): May be pass metrics as a param. DownloadManagerService.getDownloadManagerService().openDownload( item.id, item.isOffTheRecord, DownloadOpenSource.DOWNLOAD_HOME); } @Override public void removeItem(OfflineItem item) { DownloadManagerService.getDownloadManagerService().removeDownload( item.id.id, item.isOffTheRecord, item.externallyRemoved); FileDeletionQueue.get().delete(item.filePath); } @Override public void cancelDownload(OfflineItem item) { DownloadMetrics.recordDownloadCancel(DownloadMetrics.CancelFrom.CANCEL_DOWNLOAD_HOME); DownloadManagerService.getDownloadManagerService().cancelDownload( item.id, item.isOffTheRecord); } @Override public void pauseDownload(OfflineItem item) { DownloadManagerService.getDownloadManagerService().pauseDownload( item.id, item.isOffTheRecord); } @Override public void resumeDownload(OfflineItem item, boolean hasUserGesture) { DownloadInfo.Builder builder = DownloadInfo.builderFromOfflineItem(item, null); // This is a temporary hack to work around the assumption that the DownloadItem passed to // DownloadManagerService#resumeDownload() will not be paused. builder.setIsPaused(false); DownloadItem downloadItem = new DownloadItem(false /* useAndroidDownloadManager */, builder.build()); if (item.isResumable) { DownloadManagerService.getDownloadManagerService().resumeDownload( item.id, downloadItem, hasUserGesture); } else { DownloadManagerService.getDownloadManagerService().retryDownload( item.id, downloadItem, hasUserGesture); } } @Override public void getItemById(ContentId id, Callback callback) { assert false : "Not supported."; PostTask.postTask(UiThreadTaskTraits.DEFAULT, callback.bind(null)); } @Override public void getAllItems(Callback> callback, boolean offTheRecord) { List>> list = offTheRecord ? mOffTheRecordRequests : mRequests; list.add(callback); if (list.size() > 1) return; DownloadManagerService.getDownloadManagerService().getAllDownloads(offTheRecord); } @Override public void getVisualsForItem(ContentId id, VisualsCallback callback) { PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> callback.onVisualsAvailable(id, null)); } @Override public void getShareInfoForItem(OfflineItem item, ShareCallback callback) { OfflineItemShareInfo info = new OfflineItemShareInfo(); info.uri = DownloadUtils.getUriForItem(item.filePath); PostTask.postTask( UiThreadTaskTraits.DEFAULT, () -> callback.onShareInfoAvailable(item.id, info)); } @Override public void renameItem( OfflineItem item, String name, Callback callback) { DownloadManagerService.getDownloadManagerService().renameDownload( item.id, name, callback, item.isOffTheRecord); } @Override public void changeSchedule(final OfflineItem item, final OfflineItemSchedule schedule) { DownloadManagerService.getDownloadManagerService().changeSchedule( item.id, schedule, item.isOffTheRecord); } /** * There could be some situations where we can't visually represent this download in the UI. * This should be handled in native/be more generic, but it's here in the glue for now. * @return Whether or not {@code item} should be shown in the UI. */ private static boolean canShowDownloadItem(DownloadItem item) { if (TextUtils.isEmpty(item.getDownloadInfo().getFilePath())) return false; if (TextUtils.isEmpty(item.getDownloadInfo().getFileName())) return false; return true; } }