// Copyright 2016 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.offlinepages.downloads;
import android.content.ComponentName;
import android.support.annotation.Nullable;
import org.chromium.base.ContextUtils;
import org.chromium.base.ObserverList;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.DownloadItem;
import org.chromium.chrome.browser.download.DownloadServiceDelegate;
import org.chromium.chrome.browser.download.ui.BackendProvider.OfflinePageDelegate;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.document.AsyncTabCreationParams;
import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
import org.chromium.content_public.browser.LoadUrlParams;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Serves as an interface between Download Home UI and offline page related items that are to be
* displayed in the downloads UI.
*/
@JNINamespace("offline_pages::android")
public class OfflinePageDownloadBridge implements DownloadServiceDelegate, OfflinePageDelegate {
/**
* Base observer class for notifications on changes to the offline page related download items.
*/
public abstract static class Observer {
/**
* Indicates that the bridge is loaded and consumers can call GetXXX methods on it.
* If the bridge is loaded at the time the observer is being added, the Loaded event will be
* dispatched immediately.
*/
public void onItemsLoaded() {}
/**
* Event fired when an new item was added.
* @param item A newly added download item.
*/
public void onItemAdded(OfflinePageDownloadItem item) {}
/**
* Event fired when an item was deleted
* @param guid A GUID of the deleted download item.
*/
public void onItemDeleted(String guid) {}
/**
* Event fired when an new item was updated.
* @param item A newly updated download item.
*/
public void onItemUpdated(OfflinePageDownloadItem item) {}
}
private static boolean sIsTesting = false;
private final ObserverList<Observer> mObservers = new ObserverList<Observer>();
private long mNativeOfflinePageDownloadBridge;
private boolean mIsLoaded;
/**
* Gets DownloadServiceDelegate that is suitable for interacting with offline download items.
*/
public static DownloadServiceDelegate getDownloadServiceDelegate() {
return new OfflinePageDownloadBridge(Profile.getLastUsedProfile());
}
public OfflinePageDownloadBridge(Profile profile) {
mNativeOfflinePageDownloadBridge = sIsTesting ? 0L : nativeInit(profile);
}
/** Destroys the native portion of the bridge. */
@Override
public void destroy() {
if (mNativeOfflinePageDownloadBridge != 0) {
nativeDestroy(mNativeOfflinePageDownloadBridge);
mNativeOfflinePageDownloadBridge = 0;
mIsLoaded = false;
}
mObservers.clear();
}
/**
* Add an observer of offline download items changes.
* @param observer The observer to be added.
*/
@Override
public void addObserver(Observer observer) {
mObservers.addObserver(observer);
if (mIsLoaded) {
observer.onItemsLoaded();
}
}
/**
* Remove an observer of offline download items changes.
* @param observer The observer to be removed.
*/
@Override
public void removeObserver(Observer observer) {
mObservers.removeObserver(observer);
}
/** @return all of the download items related to offline pages. */
@Override
public List<OfflinePageDownloadItem> getAllItems() {
List<OfflinePageDownloadItem> items = new ArrayList<>();
nativeGetAllItems(mNativeOfflinePageDownloadBridge, items);
return items;
}
/**
* Gets a download item related to the provided GUID.
* @param guid a GUID of the item to get.
* @return download item related to the offline page identified by GUID.
*/
public OfflinePageDownloadItem getItem(String guid) {
return nativeGetItemByGuid(mNativeOfflinePageDownloadBridge, guid);
}
@Override
public void cancelDownload(String downloadGuid, boolean isOffTheRecord,
boolean isNotificationDismissed) {
nativeCancelDownload(mNativeOfflinePageDownloadBridge, downloadGuid);
}
@Override
public void pauseDownload(String downloadGuid, boolean isOffTheRecord) {
nativePauseDownload(mNativeOfflinePageDownloadBridge, downloadGuid);
}
@Override
public void resumeDownload(DownloadItem item, boolean hasUserGesture) {
if (hasUserGesture) {
nativeResumeDownload(mNativeOfflinePageDownloadBridge, item.getId());
}
}
/**
* Schedules deletion of the offline page identified by the GUID.
* If the item is still in the process of download, the download is canceled.
* Actual cancel and/or deletion happens asynchronously, Observer is notified when it's done.
* @param guid a GUID of the item to delete.
*/
@Override
public void deleteItem(String guid) {
nativeDeleteItemByGuid(mNativeOfflinePageDownloadBridge, guid);
}
@Override
public void destroyServiceDelegate() {
destroy();
}
/**
* 'Opens' the offline page identified by the GUID.
* This is done by creating a new tab and navigating it to the saved local snapshot.
* No automatic redirection is happening based on the connection status.
* If the item with specified GUID is not found or can't be opened, nothing happens.
* @param guid GUID of the item to open.
* @param componentName If specified, targets a specific Activity to open the offline page in.
*/
@Override
public void openItem(String guid, @Nullable ComponentName componentName) {
OfflinePageDownloadItem item = getItem(guid);
if (item == null) return;
LoadUrlParams params = new LoadUrlParams(item.getUrl());
Map<String, String> headers = new HashMap<String, String>();
headers.put("X-Chrome-offline", "persist=1 reason=download id="
+ Long.toString(nativeGetOfflineIdByGuid(
mNativeOfflinePageDownloadBridge, guid)));
params.setExtraHeaders(headers);
AsyncTabCreationParams asyncParams = componentName == null
? new AsyncTabCreationParams(params)
: new AsyncTabCreationParams(params, componentName);
final TabDelegate tabDelegate = new TabDelegate(false);
tabDelegate.createNewTab(asyncParams, TabLaunchType.FROM_CHROME_UI, Tab.INVALID_TAB_ID);
}
/**
* Starts download of the page currently open in the specified Tab.
* If tab's contents are not yet loaded completely, we'll wait for it
* to load enough for snapshot to be reasonable. If the Chrome is made
* background and killed, the background request remains that will
* eventually load the page in background and obtain its offline
* snapshot.
* @param tab a tab contents of which will be saved locally.
*/
public void startDownload(Tab tab) {
nativeStartDownload(mNativeOfflinePageDownloadBridge, tab,
ContextUtils.getApplicationContext().getString(R.string.menu_downloads));
}
/**
* Method to ensure that the bridge is created for tests without calling the native portion of
* initialization.
* @param isTesting flag indicating whether the constructor will initialize native code.
*/
static void setIsTesting(boolean isTesting) {
sIsTesting = isTesting;
}
/**
* Waits for the download items to get loaded and opens the offline page identified by the GUID.
* @param GUID of the item to open.
*/
public static void openDownloadedPage(final String guid) {
final OfflinePageDownloadBridge bridge =
new OfflinePageDownloadBridge(Profile.getLastUsedProfile());
bridge.addObserver(
new Observer() {
@Override
public void onItemsLoaded() {
bridge.openItem(guid, null);
bridge.destroyServiceDelegate();
}
});
}
@CalledByNative
void downloadItemsLoaded() {
mIsLoaded = true;
for (Observer observer : mObservers) {
observer.onItemsLoaded();
}
}
@CalledByNative
void downloadItemAdded(OfflinePageDownloadItem item) {
assert item != null;
for (Observer observer : mObservers) {
observer.onItemAdded(item);
}
}
@CalledByNative
void downloadItemDeleted(String guid) {
for (Observer observer : mObservers) {
observer.onItemDeleted(guid);
}
}
@CalledByNative
void downloadItemUpdated(OfflinePageDownloadItem item) {
assert item != null;
for (Observer observer : mObservers) {
observer.onItemUpdated(item);
}
}
@CalledByNative
static void createDownloadItemAndAddToList(List<OfflinePageDownloadItem> list, String guid,
String url, String title, String targetPath, long startTimeMs, long totalBytes) {
list.add(createDownloadItem(guid, url, title, targetPath, startTimeMs, totalBytes));
}
@CalledByNative
static OfflinePageDownloadItem createDownloadItem(
String guid, String url, String title, String targetPath,
long startTimeMs, long totalBytes) {
return new OfflinePageDownloadItem(guid, url, title, targetPath, startTimeMs, totalBytes);
}
private native long nativeInit(Profile profile);
private native void nativeDestroy(long nativeOfflinePageDownloadBridge);
native void nativeGetAllItems(
long nativeOfflinePageDownloadBridge, List<OfflinePageDownloadItem> items);
native OfflinePageDownloadItem nativeGetItemByGuid(
long nativeOfflinePageDownloadBridge, String guid);
native void nativeCancelDownload(long nativeOfflinePageDownloadBridge, String guid);
native void nativePauseDownload(long nativeOfflinePageDownloadBridge, String guid);
native void nativeResumeDownload(long nativeOfflinePageDownloadBridge, String guid);
native void nativeDeleteItemByGuid(long nativeOfflinePageDownloadBridge, String guid);
native long nativeGetOfflineIdByGuid(long nativeOfflinePageDownloadBridge, String guid);
native void nativeStartDownload(
long nativeOfflinePageDownloadBridge, Tab tab, String downloadsLabel);
}