Commit 12eeca0a authored by Xing Liu's avatar Xing Liu Committed by Commit Bot

Add share infrastructure to OfflineContentProvider

This class adds the required infrastructure to generalize sharing across
content types.  This comes down to adding an async API to
OfflineContentProvider to request the URI (if available) to use in
generating a share intent.  This also adds the utility class to build
the intent from that set of OfflineItems and share info (URI)s.

TBR=petewil@chromium.org

BUG=850780

Change-Id: If9d5d4f01a8559bec90b7de1adb70c8aaafa9302
Reviewed-on: https://chromium-review.googlesource.com/1105125Reviewed-by: default avatarXing Liu <xingliu@chromium.org>
Reviewed-by: default avatarMin Qin <qinmin@chromium.org>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Reviewed-by: default avatarMugdha Lakhani <nator@chromium.org>
Commit-Queue: Xing Liu <xingliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#581356}
parent a9f7c2b1
...@@ -14,10 +14,13 @@ import org.chromium.chrome.browser.download.DownloadItem; ...@@ -14,10 +14,13 @@ import org.chromium.chrome.browser.download.DownloadItem;
import org.chromium.chrome.browser.download.DownloadManagerService; import org.chromium.chrome.browser.download.DownloadManagerService;
import org.chromium.chrome.browser.download.DownloadManagerService.DownloadObserver; import org.chromium.chrome.browser.download.DownloadManagerService.DownloadObserver;
import org.chromium.chrome.browser.download.DownloadMetrics; import org.chromium.chrome.browser.download.DownloadMetrics;
import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.components.offline_items_collection.ContentId; import org.chromium.components.offline_items_collection.ContentId;
import org.chromium.components.offline_items_collection.LegacyHelpers; import org.chromium.components.offline_items_collection.LegacyHelpers;
import org.chromium.components.offline_items_collection.OfflineContentProvider; import org.chromium.components.offline_items_collection.OfflineContentProvider;
import org.chromium.components.offline_items_collection.OfflineItem; import org.chromium.components.offline_items_collection.OfflineItem;
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.components.offline_items_collection.VisualsCallback;
import java.io.File; import java.io.File;
...@@ -150,6 +153,13 @@ public class DownloadGlue implements DownloadObserver { ...@@ -150,6 +153,13 @@ public class DownloadGlue implements DownloadObserver {
new Handler().post(() -> callback.onVisualsAvailable(id, null)); new Handler().post(() -> callback.onVisualsAvailable(id, null));
} }
/** @see OfflineContentProvider#getShareInfoForItem(ContentId, ShareCallback) */
public void getShareInfoForItem(OfflineItem item, ShareCallback callback) {
OfflineItemShareInfo info = new OfflineItemShareInfo();
info.uri = DownloadUtils.getUriForItem(new File(item.filePath));
new Handler().post(() -> callback.onShareInfoAvailable(item.id, info));
}
/** /**
* There could be some situations where we can't visually represent this download in the UI. * 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. * This should be handled in native/be more generic, but it's here in the glue for now.
...@@ -160,4 +170,4 @@ public class DownloadGlue implements DownloadObserver { ...@@ -160,4 +170,4 @@ public class DownloadGlue implements DownloadObserver {
if (TextUtils.isEmpty(item.getDownloadInfo().getFileName())) return false; if (TextUtils.isEmpty(item.getDownloadInfo().getFileName())) return false;
return true; return true;
} }
} }
\ No newline at end of file
...@@ -11,6 +11,7 @@ import org.chromium.components.offline_items_collection.ContentId; ...@@ -11,6 +11,7 @@ import org.chromium.components.offline_items_collection.ContentId;
import org.chromium.components.offline_items_collection.LegacyHelpers; import org.chromium.components.offline_items_collection.LegacyHelpers;
import org.chromium.components.offline_items_collection.OfflineContentProvider; import org.chromium.components.offline_items_collection.OfflineContentProvider;
import org.chromium.components.offline_items_collection.OfflineItem; import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.ShareCallback;
import org.chromium.components.offline_items_collection.VisualsCallback; import org.chromium.components.offline_items_collection.VisualsCallback;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -133,6 +134,15 @@ public class OfflineContentProviderGlue implements OfflineContentProvider.Observ ...@@ -133,6 +134,15 @@ public class OfflineContentProviderGlue implements OfflineContentProvider.Observ
provider.removeThumbnailsFromDisk(id.id); provider.removeThumbnailsFromDisk(id.id);
} }
/** @see OfflineContentProvider#getShareInfoForItem(ContentId, ShareCallback) */
public void getShareInfoForItem(OfflineItem item, ShareCallback callback) {
if (LegacyHelpers.isLegacyDownload(item.id)) {
mDownloadProvider.getShareInfoForItem(item, callback);
} else {
mProvider.getShareInfoForItem(item.id, callback);
}
}
/** @see OfflineContentProvider#addObserver(OfflineContentProvider.Observer) */ /** @see OfflineContentProvider#addObserver(OfflineContentProvider.Observer) */
public void addObserver(OfflineContentProvider.Observer observer) { public void addObserver(OfflineContentProvider.Observer observer) {
mObservers.addObserver(observer); mObservers.addObserver(observer);
......
...@@ -71,8 +71,8 @@ public class DateOrderedListCoordinator { ...@@ -71,8 +71,8 @@ public class DateOrderedListCoordinator {
ListItemModel model = new ListItemModel(); ListItemModel model = new ListItemModel();
DecoratedListItemModel decoratedModel = new DecoratedListItemModel(model); DecoratedListItemModel decoratedModel = new DecoratedListItemModel(model);
mView = new DateOrderedListView(context, decoratedModel); mView = new DateOrderedListView(context, decoratedModel);
mMediator = new DateOrderedListMediator( mMediator = new DateOrderedListMediator(offTheRecord, provider, context::startActivity,
offTheRecord, provider, deleteController, selectionDelegate, model); deleteController, selectionDelegate, model);
mEmptyCoordinator = mEmptyCoordinator =
new EmptyCoordinator(context, prefetchProvider, mMediator.getEmptySource()); new EmptyCoordinator(context, prefetchProvider, mMediator.getEmptySource());
......
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
package org.chromium.chrome.browser.download.home.list; package org.chromium.chrome.browser.download.home.list;
import android.content.Intent;
import android.os.Handler; import android.os.Handler;
import android.support.v4.util.Pair;
import org.chromium.base.CollectionUtil; import org.chromium.base.CollectionUtil;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
...@@ -25,6 +27,7 @@ import org.chromium.chrome.browser.widget.ThumbnailProviderImpl; ...@@ -25,6 +27,7 @@ import org.chromium.chrome.browser.widget.ThumbnailProviderImpl;
import org.chromium.chrome.browser.widget.selection.SelectionDelegate; import org.chromium.chrome.browser.widget.selection.SelectionDelegate;
import org.chromium.components.offline_items_collection.OfflineContentProvider; import org.chromium.components.offline_items_collection.OfflineContentProvider;
import org.chromium.components.offline_items_collection.OfflineItem; import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.OfflineItemShareInfo;
import org.chromium.components.offline_items_collection.VisualsCallback; import org.chromium.components.offline_items_collection.VisualsCallback;
import java.io.Closeable; import java.io.Closeable;
...@@ -37,9 +40,20 @@ import java.util.List; ...@@ -37,9 +40,20 @@ import java.util.List;
* home. This includes support for filtering, deleting, etc.. * home. This includes support for filtering, deleting, etc..
*/ */
class DateOrderedListMediator { class DateOrderedListMediator {
/** Helper interface for handling share requests by the UI. */
@FunctionalInterface
public interface ShareController {
/**
* Will be called whenever {@link OfflineItem}s are being requested to be shared by the UI.
* @param intent The {@link Intent} representing the share action to broadcast to Android.
*/
void share(Intent intent);
}
private final Handler mHandler = new Handler(); private final Handler mHandler = new Handler();
private final OfflineContentProviderGlue mProvider; private final OfflineContentProviderGlue mProvider;
private final ShareController mShareController;
private final ListItemModel mModel; private final ListItemModel mModel;
private final DeleteController mDeleteController; private final DeleteController mDeleteController;
...@@ -86,11 +100,12 @@ class DateOrderedListMediator { ...@@ -86,11 +100,12 @@ class DateOrderedListMediator {
* @param offTheRecord Whether or not to include off the record items. * @param offTheRecord Whether or not to include off the record items.
* @param provider The {@link OfflineContentProvider} to visually represent. * @param provider The {@link OfflineContentProvider} to visually represent.
* @param deleteController A class to manage whether or not items can be deleted. * @param deleteController A class to manage whether or not items can be deleted.
* @param shareController A class responsible for sharing downloaded item {@link Intent}s.
* @param model The {@link ListItemModel} to push {@code provider} into. * @param model The {@link ListItemModel} to push {@code provider} into.
*/ */
public DateOrderedListMediator(boolean offTheRecord, OfflineContentProvider provider, public DateOrderedListMediator(boolean offTheRecord, OfflineContentProvider provider,
DeleteController deleteController, SelectionDelegate<ListItem> selectionDelegate, ShareController shareController, DeleteController deleteController,
ListItemModel model) { SelectionDelegate<ListItem> selectionDelegate, ListItemModel model) {
// Build a chain from the data source to the model. The chain will look like: // Build a chain from the data source to the model. The chain will look like:
// [OfflineContentProvider] -> // [OfflineContentProvider] ->
// [OfflineItemSource] -> // [OfflineItemSource] ->
...@@ -102,6 +117,7 @@ class DateOrderedListMediator { ...@@ -102,6 +117,7 @@ class DateOrderedListMediator {
// [ListItemModel] // [ListItemModel]
mProvider = new OfflineContentProviderGlue(provider, offTheRecord); mProvider = new OfflineContentProviderGlue(provider, offTheRecord);
mShareController = shareController;
mModel = model; mModel = model;
mDeleteController = deleteController; mDeleteController = deleteController;
...@@ -122,7 +138,7 @@ class DateOrderedListMediator { ...@@ -122,7 +138,7 @@ class DateOrderedListMediator {
mModel.getProperties().setValue( mModel.getProperties().setValue(
ListProperties.CALLBACK_RESUME, item -> mProvider.resumeDownload(item, true)); ListProperties.CALLBACK_RESUME, item -> mProvider.resumeDownload(item, true));
mModel.getProperties().setValue(ListProperties.CALLBACK_CANCEL, mProvider::cancelDownload); mModel.getProperties().setValue(ListProperties.CALLBACK_CANCEL, mProvider::cancelDownload);
mModel.getProperties().setValue(ListProperties.CALLBACK_SHARE, item -> {}); mModel.getProperties().setValue(ListProperties.CALLBACK_SHARE, this ::onShareItem);
mModel.getProperties().setValue(ListProperties.CALLBACK_REMOVE, this ::onDeleteItem); mModel.getProperties().setValue(ListProperties.CALLBACK_REMOVE, this ::onDeleteItem);
mModel.getProperties().setValue(ListProperties.PROVIDER_VISUALS, this ::getVisuals); mModel.getProperties().setValue(ListProperties.PROVIDER_VISUALS, this ::getVisuals);
mModel.getProperties().setValue( mModel.getProperties().setValue(
...@@ -203,6 +219,26 @@ class DateOrderedListMediator { ...@@ -203,6 +219,26 @@ class DateOrderedListMediator {
}); });
} }
private void onShareItem(OfflineItem item) {
onShareItems(CollectionUtil.newHashSet(item));
}
private void onShareItems(Collection<OfflineItem> items) {
final Collection<Pair<OfflineItem, OfflineItemShareInfo>> shareInfo = new ArrayList<>();
for (OfflineItem item : items) {
mProvider.getShareInfoForItem(item, (id, info) -> {
shareInfo.add(Pair.create(item, info));
// When we've gotten callbacks for all items, create and share the intent.
if (shareInfo.size() == items.size()) {
Intent intent = ShareUtils.createIntent(shareInfo);
if (intent != null) mShareController.share(intent);
}
});
}
}
private Runnable getVisuals( private Runnable getVisuals(
OfflineItem item, int iconWidthPx, int iconHeightPx, VisualsCallback callback) { OfflineItem item, int iconWidthPx, int iconHeightPx, VisualsCallback callback) {
if (!UiUtils.canHaveThumbnails(item)) { if (!UiUtils.canHaveThumbnails(item)) {
......
// 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.download.home.list;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.util.Pair;
import android.text.TextUtils;
import org.chromium.base.CollectionUtil;
import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.OfflineItemShareInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/** Helper class containing utility methods to make sharing {@link OfflineItem}s easier. */
public class ShareUtils {
private static final String DEFAULT_MIME_TYPE = "*/*";
private static final String MIME_TYPE_DELIMITER = "/";
private ShareUtils() {}
/**
* Creates an {@link Intent} that represents
* @param items A {@link Collection} of pairs of {@link OfflineItem}s and
* {@link OfflineItemShareInfo}s. The {@link OfflineItemShareInfo} contains extra
* information about the specific {@link OfflineItem} required to properly build
* the share {@link Intent}.
* @return An {@link Intent} that can be sent through the Android framework that will share
* {@code items} properly. This might include specific {@link URI}s or it might
* include text URLs depending on whether or not the item can be exposed and shared
* directly.
* @see OfflineContentProvider#getShareInfoForItem(ContentId, ShareCallback)
*/
public static Intent createIntent(Collection<Pair<OfflineItem, OfflineItemShareInfo>> items) {
ArrayList<Uri> uris = new ArrayList<>();
Set<String> mimeTypes = new HashSet<>();
StringBuilder urls = new StringBuilder();
for (Pair<OfflineItem, OfflineItemShareInfo> item : items) {
mimeTypes.add(Intent.normalizeMimeType(item.first.mimeType));
Uri uri = item.second == null ? null : item.second.uri;
if (uri != null && uri.compareTo(Uri.EMPTY) != 0) {
uris.add(uri);
} else if (!TextUtils.isEmpty(item.first.pageUrl)) {
if (urls.length() > 0) urls.append("\n");
urls.append(item.first.pageUrl);
}
}
// If we have nothing to share, don't create the intent. We should theoretically always
// have the pageURL, but this is a safety precaution. In the future we could just use the
// title instead of the URL if the URL doesn't exist.
if (uris.isEmpty() && urls.length() == 0) return null;
boolean sendingText = urls.length() > 0;
boolean singleItem = ((sendingText ? 1 : 0) + uris.size()) == 1;
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setType(Intent.normalizeMimeType(resolveMimeType(mimeTypes)));
intent.setAction(singleItem ? Intent.ACTION_SEND : Intent.ACTION_SEND_MULTIPLE);
if (sendingText) intent.putExtra(Intent.EXTRA_TEXT, urls.toString());
if (items.size() == 1) {
intent.putExtra(Intent.EXTRA_SUBJECT, items.iterator().next().first.title);
}
if (uris.size() == 1) {
intent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
} else if (uris.size() > 1) {
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
}
return intent;
}
private static String resolveMimeType(Collection<String> mimeTypes) {
if (mimeTypes.isEmpty()) return DEFAULT_MIME_TYPE;
if (mimeTypes.size() == 1) return mimeTypes.iterator().next();
Set<String> firstParts = new HashSet<>();
Set<String> secondParts = new HashSet<>();
CollectionUtil.forEach(mimeTypes, mimeType -> {
String[] parts = mimeType.split(MIME_TYPE_DELIMITER);
firstParts.add(parts[0]);
secondParts.add(parts[1]);
});
if (firstParts.size() == 1) {
String secondPart = secondParts.size() > 1 ? "*" : secondParts.iterator().next();
return firstParts.iterator().next() + MIME_TYPE_DELIMITER + secondPart;
} else {
return DEFAULT_MIME_TYPE;
}
}
}
\ No newline at end of file
...@@ -10,6 +10,7 @@ import org.chromium.components.offline_items_collection.ContentId; ...@@ -10,6 +10,7 @@ import org.chromium.components.offline_items_collection.ContentId;
import org.chromium.components.offline_items_collection.LegacyHelpers; import org.chromium.components.offline_items_collection.LegacyHelpers;
import org.chromium.components.offline_items_collection.OfflineContentProvider; import org.chromium.components.offline_items_collection.OfflineContentProvider;
import org.chromium.components.offline_items_collection.OfflineItem; import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.ShareCallback;
import org.chromium.components.offline_items_collection.VisualsCallback; import org.chromium.components.offline_items_collection.VisualsCallback;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -124,4 +125,7 @@ class DownloadBlockedOfflineContentProvider ...@@ -124,4 +125,7 @@ class DownloadBlockedOfflineContentProvider
} }
return filteredList; return filteredList;
} }
@Override
public void getShareInfoForItem(ContentId id, ShareCallback callback) {}
} }
...@@ -502,6 +502,7 @@ chrome_java_sources = [ ...@@ -502,6 +502,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java", "java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java",
"java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java", "java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java",
"java/src/org/chromium/chrome/browser/download/home/list/ListUtils.java", "java/src/org/chromium/chrome/browser/download/home/list/ListUtils.java",
"java/src/org/chromium/chrome/browser/download/home/list/ShareUtils.java",
"java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java", "java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java",
"java/src/org/chromium/chrome/browser/download/home/snackbars/DeleteUndoCoordinator.java", "java/src/org/chromium/chrome/browser/download/home/snackbars/DeleteUndoCoordinator.java",
"java/src/org/chromium/chrome/browser/download/home/snackbars/UndoUiUtils.java", "java/src/org/chromium/chrome/browser/download/home/snackbars/UndoUiUtils.java",
...@@ -2171,6 +2172,7 @@ chrome_junit_test_java_sources = [ ...@@ -2171,6 +2172,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/download/home/glue/FileDeletionQueueTest.java", "junit/src/org/chromium/chrome/browser/download/home/glue/FileDeletionQueueTest.java",
"junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java", "junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java",
"junit/src/org/chromium/chrome/browser/download/home/list/ItemUtilsTest.java", "junit/src/org/chromium/chrome/browser/download/home/list/ItemUtilsTest.java",
"junit/src/org/chromium/chrome/browser/download/home/list/ShareUtilsTest.java",
"junit/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotificationBridgeUiTest.java", "junit/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotificationBridgeUiTest.java",
"junit/src/org/chromium/chrome/browser/download/DownloadResumptionSchedulerTest.java", "junit/src/org/chromium/chrome/browser/download/DownloadResumptionSchedulerTest.java",
"junit/src/org/chromium/chrome/browser/externalauth/ExternalAuthUtilsTest.java", "junit/src/org/chromium/chrome/browser/externalauth/ExternalAuthUtilsTest.java",
......
...@@ -27,6 +27,7 @@ import org.chromium.components.offline_items_collection.OfflineItem.Progress; ...@@ -27,6 +27,7 @@ import org.chromium.components.offline_items_collection.OfflineItem.Progress;
import org.chromium.components.offline_items_collection.OfflineItemFilter; import org.chromium.components.offline_items_collection.OfflineItemFilter;
import org.chromium.components.offline_items_collection.OfflineItemProgressUnit; import org.chromium.components.offline_items_collection.OfflineItemProgressUnit;
import org.chromium.components.offline_items_collection.OfflineItemState; import org.chromium.components.offline_items_collection.OfflineItemState;
import org.chromium.components.offline_items_collection.ShareCallback;
import org.chromium.components.offline_items_collection.VisualsCallback; import org.chromium.components.offline_items_collection.VisualsCallback;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
...@@ -161,7 +162,14 @@ public class StubbedProvider implements BackendProvider { ...@@ -161,7 +162,14 @@ public class StubbedProvider implements BackendProvider {
} }
@Override @Override
public void getVisualsForItem(ContentId id, VisualsCallback callback) {} public void getVisualsForItem(ContentId id, VisualsCallback callback) {
mHandler.post(() -> callback.onVisualsAvailable(id, null));
}
@Override
public void getShareInfoForItem(ContentId id, ShareCallback callback) {
mHandler.post(() -> callback.onShareInfoAvailable(id, null));
}
} }
/** Stubs out all attempts to get thumbnails for files. */ /** Stubs out all attempts to get thumbnails for files. */
......
// 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.list;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.util.Pair;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.chromium.base.CollectionUtil;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.OfflineItemShareInfo;
/** Unit tests for the ShareUtils class. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ShareUtilsTest {
@Test
public void testNoContent() {
Assert.assertNull(ShareUtils.createIntent(CollectionUtil.newArrayList()));
Assert.assertNull(ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem(null, "text/plain", "", null), createItem("", "text/plain", "", ""))));
}
@Test
public void testAction() {
Intent intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null)));
Assert.assertEquals(Intent.ACTION_SEND, intent.getAction());
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null),
createItem("http://www.chrome.com", "text/plain", "", null)));
Assert.assertEquals(Intent.ACTION_SEND, intent.getAction());
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null),
createItem("", "text/plain", "", "http://www.chrome.com")));
Assert.assertEquals(Intent.ACTION_SEND_MULTIPLE, intent.getAction());
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("", "text/plain", "", "http://www.google.com"),
createItem("", "text/plain", "", "http://www.chrome.com")));
Assert.assertEquals(Intent.ACTION_SEND_MULTIPLE, intent.getAction());
}
@Test
public void testFlags() {
Intent intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null)));
Assert.assertNotEquals(0, intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
}
@Test
public void testExtraText() {
Intent intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null)));
Assert.assertEquals("http://www.google.com", intent.getStringExtra(Intent.EXTRA_TEXT));
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null),
createItem("http://www.chrome.com", "text/plain", "", "http://www.chrome.com")));
Assert.assertEquals("http://www.google.com", intent.getStringExtra(Intent.EXTRA_TEXT));
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null),
createItem("http://www.chrome.com", "text/plain", "", null)));
Assert.assertEquals("http://www.google.com\nhttp://www.chrome.com",
intent.getStringExtra(Intent.EXTRA_TEXT));
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("", "text/plain", "", "http://www.google.com")));
Assert.assertFalse(intent.hasExtra(Intent.EXTRA_TEXT));
}
@Test
public void testExtraSubject() {
Intent intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "title", null)));
Assert.assertEquals("title", intent.getStringExtra(Intent.EXTRA_SUBJECT));
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "title1", null),
createItem(
"http://www.chrome.com", "text/plain", "title2", "http://www.chrome.com")));
Assert.assertFalse(intent.hasExtra(Intent.EXTRA_SUBJECT));
}
@Test
public void testExtraStream() {
Intent intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null)));
Assert.assertFalse(intent.hasExtra(Intent.EXTRA_STREAM));
Assert.assertNull(intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM));
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("", "text/plain", "", "http://www.google.com")));
Assert.assertEquals(
Uri.parse("http://www.google.com"), intent.getParcelableExtra(Intent.EXTRA_STREAM));
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", "http://www.google.com")));
Assert.assertEquals(
Uri.parse("http://www.google.com"), intent.getParcelableExtra(Intent.EXTRA_STREAM));
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("", "text/plain", "", "http://www.google.com"),
createItem("http://www.chrome.com", "text/plain", "", "")));
Assert.assertEquals(
Uri.parse("http://www.google.com"), intent.getParcelableExtra(Intent.EXTRA_STREAM));
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("", "text/plain", "", "http://www.google.com"),
createItem("http://www.chrome.com", "text/plain", "", "http://www.chrome.com")));
Assert.assertEquals(CollectionUtil.newArrayList(Uri.parse("http://www.google.com"),
Uri.parse("http://www.chrome.com")),
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM));
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("", "text/plain", "", "http://www.google.com"),
createItem("", "text/plain", "", "http://www.chrome.com")));
Assert.assertEquals(CollectionUtil.newArrayList(Uri.parse("http://www.google.com"),
Uri.parse("http://www.chrome.com")),
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM));
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null)));
Assert.assertFalse(intent.hasExtra(Intent.EXTRA_STREAM));
Assert.assertNull(intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM));
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null),
createItem("http://www.chrome.com", "text/plain", "", null)));
Assert.assertFalse(intent.hasExtra(Intent.EXTRA_STREAM));
Assert.assertNull(intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM));
}
@Test
public void testType() {
Intent intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null)));
Assert.assertEquals(Intent.normalizeMimeType("text/plain"), intent.getType());
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null),
createItem("http://www.google.com", "text/plain", "", null),
createItem("http://www.google.com", "text/html", "", null),
createItem("http://www.google.com", "text/html", "", null)));
Assert.assertEquals(Intent.normalizeMimeType("text/*"), intent.getType());
intent = ShareUtils.createIntent(CollectionUtil.newArrayList(
createItem("http://www.google.com", "text/plain", "", null),
createItem("http://www.google.com", "application/octet-stream", "", null)));
Assert.assertEquals(Intent.normalizeMimeType("*/*"), intent.getType());
}
private static Pair<OfflineItem, OfflineItemShareInfo> createItem(
String pageUrl, String mimeType, String title, String uri) {
OfflineItem item = new OfflineItem();
item.pageUrl = pageUrl;
item.mimeType = mimeType;
item.title = title;
OfflineItemShareInfo info = new OfflineItemShareInfo();
if (uri != null) info.uri = Uri.parse(uri);
return Pair.create(item, info);
}
}
\ No newline at end of file
...@@ -516,7 +516,7 @@ void BackgroundFetchDelegateImpl::GetAllItems(MultipleItemCallback callback) { ...@@ -516,7 +516,7 @@ void BackgroundFetchDelegateImpl::GetAllItems(MultipleItemCallback callback) {
void BackgroundFetchDelegateImpl::GetVisualsForItem( void BackgroundFetchDelegateImpl::GetVisualsForItem(
const offline_items_collection::ContentId& id, const offline_items_collection::ContentId& id,
const VisualsCallback& callback) { VisualsCallback callback) {
// GetVisualsForItem mustn't be called directly since offline_items_collection // GetVisualsForItem mustn't be called directly since offline_items_collection
// is not re-entrant and it must be called even if there are no visuals. // is not re-entrant and it must be called even if there are no visuals.
auto visuals = auto visuals =
...@@ -529,7 +529,16 @@ void BackgroundFetchDelegateImpl::GetVisualsForItem( ...@@ -529,7 +529,16 @@ void BackgroundFetchDelegateImpl::GetVisualsForItem(
} }
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, id, std::move(visuals))); FROM_HERE, base::BindOnce(std::move(callback), id, std::move(visuals)));
}
void BackgroundFetchDelegateImpl::GetShareInfoForItem(
const offline_items_collection::ContentId& id,
ShareCallback callback) {
// TODO(xingliu): Provide OfflineItemShareInfo to |callback|.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), id,
nullptr /* OfflineItemShareInfo */));
} }
void BackgroundFetchDelegateImpl::AddObserver(Observer* observer) { void BackgroundFetchDelegateImpl::AddObserver(Observer* observer) {
......
...@@ -88,7 +88,9 @@ class BackgroundFetchDelegateImpl ...@@ -88,7 +88,9 @@ class BackgroundFetchDelegateImpl
SingleItemCallback callback) override; SingleItemCallback callback) override;
void GetAllItems(MultipleItemCallback callback) override; void GetAllItems(MultipleItemCallback callback) override;
void GetVisualsForItem(const offline_items_collection::ContentId& id, void GetVisualsForItem(const offline_items_collection::ContentId& id,
const VisualsCallback& callback) override; VisualsCallback callback) override;
void GetShareInfoForItem(const offline_items_collection::ContentId& id,
ShareCallback callback) override;
void AddObserver(Observer* observer) override; void AddObserver(Observer* observer) override;
void RemoveObserver(Observer* observer) override; void RemoveObserver(Observer* observer) override;
......
...@@ -120,7 +120,7 @@ void DownloadOfflineContentProvider::GetAllItems( ...@@ -120,7 +120,7 @@ void DownloadOfflineContentProvider::GetAllItems(
void DownloadOfflineContentProvider::GetVisualsForItem( void DownloadOfflineContentProvider::GetVisualsForItem(
const ContentId& id, const ContentId& id,
const VisualsCallback& callback) { VisualsCallback callback) {
// TODO(crbug.com/855330) Supply thumbnail if item is visible. // TODO(crbug.com/855330) Supply thumbnail if item is visible.
DownloadItem* item = manager_->GetDownloadByGuid(id.id); DownloadItem* item = manager_->GetDownloadByGuid(id.id);
if (!item) if (!item)
...@@ -132,7 +132,7 @@ void DownloadOfflineContentProvider::GetVisualsForItem( ...@@ -132,7 +132,7 @@ void DownloadOfflineContentProvider::GetVisualsForItem(
auto request = std::make_unique<ImageThumbnailRequest>( auto request = std::make_unique<ImageThumbnailRequest>(
icon_size, icon_size,
base::BindOnce(&DownloadOfflineContentProvider::OnThumbnailRetrieved, base::BindOnce(&DownloadOfflineContentProvider::OnThumbnailRetrieved,
weak_ptr_factory_.GetWeakPtr(), id, callback)); weak_ptr_factory_.GetWeakPtr(), id, std::move(callback)));
request->Start(item->GetTargetFilePath()); request->Start(item->GetTargetFilePath());
// Dropping ownership of |request| here because it will clean itself up once // Dropping ownership of |request| here because it will clean itself up once
...@@ -140,9 +140,13 @@ void DownloadOfflineContentProvider::GetVisualsForItem( ...@@ -140,9 +140,13 @@ void DownloadOfflineContentProvider::GetVisualsForItem(
request.release(); request.release();
} }
void DownloadOfflineContentProvider::GetShareInfoForItem(
const ContentId& id,
ShareCallback callback) {}
void DownloadOfflineContentProvider::OnThumbnailRetrieved( void DownloadOfflineContentProvider::OnThumbnailRetrieved(
const ContentId& id, const ContentId& id,
const VisualsCallback& callback, VisualsCallback callback,
const SkBitmap& bitmap) { const SkBitmap& bitmap) {
auto visuals = std::make_unique<OfflineItemVisuals>(); auto visuals = std::make_unique<OfflineItemVisuals>();
visuals->icon = gfx::Image::CreateFrom1xBitmap(bitmap); visuals->icon = gfx::Image::CreateFrom1xBitmap(bitmap);
......
...@@ -45,7 +45,9 @@ class DownloadOfflineContentProvider ...@@ -45,7 +45,9 @@ class DownloadOfflineContentProvider
OfflineContentProvider::MultipleItemCallback callback) override; OfflineContentProvider::MultipleItemCallback callback) override;
void GetVisualsForItem( void GetVisualsForItem(
const ContentId& id, const ContentId& id,
const OfflineContentProvider::VisualsCallback& callback) override; OfflineContentProvider::VisualsCallback callback) override;
void GetShareInfoForItem(const ContentId& id,
ShareCallback callback) override;
void AddObserver(OfflineContentProvider::Observer* observer) override; void AddObserver(OfflineContentProvider::Observer* observer) override;
void RemoveObserver(OfflineContentProvider::Observer* observer) override; void RemoveObserver(OfflineContentProvider::Observer* observer) override;
...@@ -55,7 +57,7 @@ class DownloadOfflineContentProvider ...@@ -55,7 +57,7 @@ class DownloadOfflineContentProvider
void OnDownloadRemoved(DownloadManager* manager, DownloadItem* item) override; void OnDownloadRemoved(DownloadManager* manager, DownloadItem* item) override;
void OnThumbnailRetrieved(const ContentId& id, void OnThumbnailRetrieved(const ContentId& id,
const VisualsCallback& callback, VisualsCallback callback,
const SkBitmap& bitmap); const SkBitmap& bitmap);
DownloadManager* manager_; DownloadManager* manager_;
......
...@@ -40,6 +40,8 @@ static_library("core") { ...@@ -40,6 +40,8 @@ static_library("core") {
"android/offline_content_aggregator_bridge.h", "android/offline_content_aggregator_bridge.h",
"android/offline_item_bridge.cc", "android/offline_item_bridge.cc",
"android/offline_item_bridge.h", "android/offline_item_bridge.h",
"android/offline_item_share_info_bridge.cc",
"android/offline_item_share_info_bridge.h",
"android/offline_item_visuals_bridge.cc", "android/offline_item_visuals_bridge.cc",
"android/offline_item_visuals_bridge.h", "android/offline_item_visuals_bridge.h",
] ]
...@@ -68,13 +70,16 @@ if (is_android) { ...@@ -68,13 +70,16 @@ if (is_android) {
android_library("core_java") { android_library("core_java") {
java_files = [ java_files = [
"android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemBridge.java", "android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemBridge.java",
"android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemShareInfoBridge.java",
"android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemVisualsBridge.java", "android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemVisualsBridge.java",
"android/java/src/org/chromium/components/offline_items_collection/ContentId.java", "android/java/src/org/chromium/components/offline_items_collection/ContentId.java",
"android/java/src/org/chromium/components/offline_items_collection/LegacyHelpers.java", "android/java/src/org/chromium/components/offline_items_collection/LegacyHelpers.java",
"android/java/src/org/chromium/components/offline_items_collection/OfflineContentAggregatorBridge.java", "android/java/src/org/chromium/components/offline_items_collection/OfflineContentAggregatorBridge.java",
"android/java/src/org/chromium/components/offline_items_collection/OfflineContentProvider.java", "android/java/src/org/chromium/components/offline_items_collection/OfflineContentProvider.java",
"android/java/src/org/chromium/components/offline_items_collection/OfflineItem.java", "android/java/src/org/chromium/components/offline_items_collection/OfflineItem.java",
"android/java/src/org/chromium/components/offline_items_collection/OfflineItemShareInfo.java",
"android/java/src/org/chromium/components/offline_items_collection/OfflineItemVisuals.java", "android/java/src/org/chromium/components/offline_items_collection/OfflineItemVisuals.java",
"android/java/src/org/chromium/components/offline_items_collection/ShareCallback.java",
"android/java/src/org/chromium/components/offline_items_collection/VisualsCallback.java", "android/java/src/org/chromium/components/offline_items_collection/VisualsCallback.java",
] ]
...@@ -92,6 +97,7 @@ if (is_android) { ...@@ -92,6 +97,7 @@ if (is_android) {
sources = [ sources = [
"android/java/src/org/chromium/components/offline_items_collection/OfflineContentAggregatorBridge.java", "android/java/src/org/chromium/components/offline_items_collection/OfflineContentAggregatorBridge.java",
"android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemBridge.java", "android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemBridge.java",
"android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemShareInfoBridge.java",
"android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemVisualsBridge.java", "android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemVisualsBridge.java",
] ]
......
...@@ -86,6 +86,12 @@ public class OfflineContentAggregatorBridge implements OfflineContentProvider { ...@@ -86,6 +86,12 @@ public class OfflineContentAggregatorBridge implements OfflineContentProvider {
mNativeOfflineContentAggregatorBridge, id.namespace, id.id, callback); mNativeOfflineContentAggregatorBridge, id.namespace, id.id, callback);
} }
@Override
public void getShareInfoForItem(ContentId id, ShareCallback callback) {
nativeGetShareInfoForItem(
mNativeOfflineContentAggregatorBridge, id.namespace, id.id, callback);
}
@Override @Override
public void addObserver(final OfflineContentProvider.Observer observer) { public void addObserver(final OfflineContentProvider.Observer observer) {
mObservers.addObserver(observer); mObservers.addObserver(observer);
...@@ -128,6 +134,12 @@ public class OfflineContentAggregatorBridge implements OfflineContentProvider { ...@@ -128,6 +134,12 @@ public class OfflineContentAggregatorBridge implements OfflineContentProvider {
callback.onVisualsAvailable(new ContentId(nameSpace, id), visuals); callback.onVisualsAvailable(new ContentId(nameSpace, id), visuals);
} }
@CalledByNative
private static void onShareInfoAvailable(
ShareCallback callback, String nameSpace, String id, OfflineItemShareInfo shareInfo) {
callback.onShareInfoAvailable(new ContentId(nameSpace, id), shareInfo);
}
/** /**
* Called when the C++ OfflineContentAggregatorBridge is destroyed. This tears down the Java * Called when the C++ OfflineContentAggregatorBridge is destroyed. This tears down the Java
* component of the JNI bridge so that this class, which may live due to other references, no * component of the JNI bridge so that this class, which may live due to other references, no
...@@ -167,4 +179,6 @@ public class OfflineContentAggregatorBridge implements OfflineContentProvider { ...@@ -167,4 +179,6 @@ public class OfflineContentAggregatorBridge implements OfflineContentProvider {
long nativeOfflineContentAggregatorBridge, Callback<ArrayList<OfflineItem>> callback); long nativeOfflineContentAggregatorBridge, Callback<ArrayList<OfflineItem>> callback);
private native void nativeGetVisualsForItem(long nativeOfflineContentAggregatorBridge, private native void nativeGetVisualsForItem(long nativeOfflineContentAggregatorBridge,
String nameSpace, String id, VisualsCallback callback); String nameSpace, String id, VisualsCallback callback);
private native void nativeGetShareInfoForItem(long nativeOfflineContentAggregatorBridge,
String nameSpace, String id, ShareCallback callback);
} }
\ No newline at end of file
...@@ -52,6 +52,9 @@ public interface OfflineContentProvider { ...@@ -52,6 +52,9 @@ public interface OfflineContentProvider {
/** See OfflineContentProvider::GetVisualsForItem(...). */ /** See OfflineContentProvider::GetVisualsForItem(...). */
void getVisualsForItem(ContentId id, VisualsCallback callback); void getVisualsForItem(ContentId id, VisualsCallback callback);
/** See OfflineContentProvider::GetShareInfoForItem(...). */
void getShareInfoForItem(ContentId id, ShareCallback callback);
/** See OfflineContentProvider::AddObserver(...). */ /** See OfflineContentProvider::AddObserver(...). */
void addObserver(Observer observer); void addObserver(Observer observer);
......
// Copyright 2017 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.components.offline_items_collection;
import android.net.Uri;
/**
* This class is the Java counterpart to the C++ OfflineItemShareInfo
* (components/offline_items_collection/core/offline_item.h) class.
*
* For all member variable descriptions see the C++ class.
* TODO(dtrainor): Investigate making all class members for this and the C++ counterpart const.
*/
public class OfflineItemShareInfo { public Uri uri; }
\ No newline at end of file
// 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.components.offline_items_collection;
import android.support.annotation.Nullable;
/**
* This interface is a Java counterpart to the C++ base::Callback meant to be used in response
* to {@link OfflineItemShareInfo} requests.
*/
public interface ShareCallback {
/**
* @param id The {@link ContentId} that {@code shareInfo} is associated with.
* @param shareInfo The {@link OfflineItemShareInfo}, if any, associated with {@code id}.
*/
void onShareInfoAvailable(ContentId id, @Nullable OfflineItemShareInfo shareInfo);
}
\ No newline at end of file
// 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.components.offline_items_collection.bridges;
import android.net.Uri;
import android.text.TextUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.components.offline_items_collection.OfflineItemShareInfo;
/**
* The Java counterpart to the C++ class OfflineItemShareInfoBridge
* (components/offline_items_collection/core/android/offline_item_share_info_bridge.h). This class
* has no public members or methods and is meant as a private factory to build
* {@link OfflineItemShareInfo} instances.
*/
@JNINamespace("offline_items_collection::android")
public final class OfflineItemShareInfoBridge {
private OfflineItemShareInfoBridge() {}
/**
* This is a helper method to allow C++ to create an {@link OfflineItemShareInfo} object.
* @return The newly created {@link OfflineItemShareInfo} based on the input parameters.
*/
@CalledByNative
private static OfflineItemShareInfo createOfflineItemShareInfo(String uri) {
OfflineItemShareInfo shareInfo = new OfflineItemShareInfo();
if (!TextUtils.isEmpty(uri)) shareInfo.uri = Uri.parse(uri);
return shareInfo;
}
}
\ No newline at end of file
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "components/offline_items_collection/core/android/offline_item_bridge.h" #include "components/offline_items_collection/core/android/offline_item_bridge.h"
#include "components/offline_items_collection/core/android/offline_item_share_info_bridge.h"
#include "components/offline_items_collection/core/android/offline_item_visuals_bridge.h" #include "components/offline_items_collection/core/android/offline_item_visuals_bridge.h"
#include "components/offline_items_collection/core/offline_item.h" #include "components/offline_items_collection/core/offline_item.h"
#include "components/offline_items_collection/core/throttled_offline_content_provider.h" #include "components/offline_items_collection/core/throttled_offline_content_provider.h"
...@@ -39,6 +40,8 @@ ContentId JNI_OfflineContentAggregatorBridge_CreateContentId( ...@@ -39,6 +40,8 @@ ContentId JNI_OfflineContentAggregatorBridge_CreateContentId(
ConvertJavaStringToUTF8(env, j_id)); ConvertJavaStringToUTF8(env, j_id));
} }
// Helper callback that glues the Java-specific callback logic to the native
// VisualsCallback that is required by the OfflineContentProvider native class.
void GetVisualsForItemHelperCallback( void GetVisualsForItemHelperCallback(
ScopedJavaGlobalRef<jobject> j_callback, ScopedJavaGlobalRef<jobject> j_callback,
const ContentId& id, const ContentId& id,
...@@ -51,6 +54,18 @@ void GetVisualsForItemHelperCallback( ...@@ -51,6 +54,18 @@ void GetVisualsForItemHelperCallback(
std::move(visuals))); std::move(visuals)));
} }
void ForwardShareInfoToJavaCallback(
ScopedJavaGlobalRef<jobject> j_callback,
const ContentId& id,
std::unique_ptr<OfflineItemShareInfo> shareInfo) {
JNIEnv* env = AttachCurrentThread();
Java_OfflineContentAggregatorBridge_onShareInfoAvailable(
env, j_callback, ConvertUTF8ToJavaString(env, id.name_space),
ConvertUTF8ToJavaString(env, id.id),
OfflineItemShareInfoBridge::CreateOfflineItemShareInfo(
env, std::move(shareInfo)));
}
void RunGetAllItemsCallback(const base::android::JavaRef<jobject>& j_callback, void RunGetAllItemsCallback(const base::android::JavaRef<jobject>& j_callback,
const std::vector<OfflineItem>& items) { const std::vector<OfflineItem>& items) {
JNIEnv* env = AttachCurrentThread(); JNIEnv* env = AttachCurrentThread();
...@@ -185,8 +200,21 @@ void OfflineContentAggregatorBridge::GetVisualsForItem( ...@@ -185,8 +200,21 @@ void OfflineContentAggregatorBridge::GetVisualsForItem(
provider_->GetVisualsForItem( provider_->GetVisualsForItem(
JNI_OfflineContentAggregatorBridge_CreateContentId(env, j_namespace, JNI_OfflineContentAggregatorBridge_CreateContentId(env, j_namespace,
j_id), j_id),
base::Bind(&GetVisualsForItemHelperCallback, base::BindOnce(&GetVisualsForItemHelperCallback,
ScopedJavaGlobalRef<jobject>(env, j_callback))); ScopedJavaGlobalRef<jobject>(env, j_callback)));
}
void OfflineContentAggregatorBridge::GetShareInfoForItem(
JNIEnv* env,
const JavaParamRef<jobject>& jobj,
const JavaParamRef<jstring>& j_namespace,
const JavaParamRef<jstring>& j_id,
const JavaParamRef<jobject>& j_callback) {
provider_->GetShareInfoForItem(
JNI_OfflineContentAggregatorBridge_CreateContentId(env, j_namespace,
j_id),
base::BindOnce(&ForwardShareInfoToJavaCallback,
ScopedJavaGlobalRef<jobject>(env, j_callback)));
} }
void OfflineContentAggregatorBridge::OnItemsAdded( void OfflineContentAggregatorBridge::OnItemsAdded(
......
...@@ -72,6 +72,12 @@ class OfflineContentAggregatorBridge : public OfflineContentProvider::Observer, ...@@ -72,6 +72,12 @@ class OfflineContentAggregatorBridge : public OfflineContentProvider::Observer,
const base::android::JavaParamRef<jstring>& j_namespace, const base::android::JavaParamRef<jstring>& j_namespace,
const base::android::JavaParamRef<jstring>& j_id, const base::android::JavaParamRef<jstring>& j_id,
const base::android::JavaParamRef<jobject>& j_callback); const base::android::JavaParamRef<jobject>& j_callback);
void GetShareInfoForItem(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jobj,
const base::android::JavaParamRef<jstring>& j_namespace,
const base::android::JavaParamRef<jstring>& j_id,
const base::android::JavaParamRef<jobject>& j_callback);
private: private:
OfflineContentAggregatorBridge(OfflineContentAggregator* aggregator); OfflineContentAggregatorBridge(OfflineContentAggregator* aggregator);
......
// 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.
#include "components/offline_items_collection/core/android/offline_item_share_info_bridge.h"
#include "base/android/jni_string.h"
#include "components/offline_items_collection/core/offline_item.h"
#include "jni/OfflineItemShareInfoBridge_jni.h"
using base::android::ConvertUTF8ToJavaString;
using base::android::ScopedJavaLocalRef;
namespace offline_items_collection {
namespace android {
// static
ScopedJavaLocalRef<jobject>
OfflineItemShareInfoBridge::CreateOfflineItemShareInfo(
JNIEnv* env,
std::unique_ptr<OfflineItemShareInfo> const share_info) {
if (!share_info)
return nullptr;
return Java_OfflineItemShareInfoBridge_createOfflineItemShareInfo(
env, ConvertUTF8ToJavaString(env, share_info->uri.value()));
}
} // namespace android
} // namespace offline_items_collection
// 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.
#ifndef COMPONENTS_OFFLINE_ITEMS_COLLECTION_CORE_ANDROID_OFFLINE_ITEM_SHARE_INFO_BRIDGE_H_
#define COMPONENTS_OFFLINE_ITEMS_COLLECTION_CORE_ANDROID_OFFLINE_ITEM_SHARE_INFO_BRIDGE_H_
#include <vector>
#include "base/android/jni_android.h"
#include "base/android/scoped_java_ref.h"
namespace offline_items_collection {
struct OfflineItemShareInfo;
namespace android {
// A helper class for creating Java OfflineItemShareInfo instances from the C++
// OfflineItemShareInfo counterpart.
class OfflineItemShareInfoBridge {
public:
// Creates a Java OfflineItemVisuals from |visuals|.
static base::android::ScopedJavaLocalRef<jobject> CreateOfflineItemShareInfo(
JNIEnv* env,
std::unique_ptr<OfflineItemShareInfo> share_info);
private:
OfflineItemShareInfoBridge();
};
} // namespace android
} // namespace offline_items_collection
#endif // COMPONENTS_OFFLINE_ITEMS_COLLECTION_CORE_ANDROID_OFFLINE_ITEM_SHARE_INFO_BRIDGE_H_
...@@ -182,19 +182,31 @@ void OfflineContentAggregator::OnGetAllItemsDone( ...@@ -182,19 +182,31 @@ void OfflineContentAggregator::OnGetAllItemsDone(
std::move(callback).Run(item_vec); std::move(callback).Run(item_vec);
} }
void OfflineContentAggregator::GetVisualsForItem( void OfflineContentAggregator::GetVisualsForItem(const ContentId& id,
const ContentId& id, VisualsCallback callback) {
const VisualsCallback& callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = providers_.find(id.name_space); auto it = providers_.find(id.name_space);
if (it == providers_.end()) { if (it == providers_.end()) {
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, id, nullptr)); FROM_HERE, base::BindOnce(std::move(callback), id, nullptr));
return; return;
} }
it->second->GetVisualsForItem(id, callback); it->second->GetVisualsForItem(id, std::move(callback));
}
void OfflineContentAggregator::GetShareInfoForItem(const ContentId& id,
ShareCallback callback) {
auto it = providers_.find(id.name_space);
if (it == providers_.end()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), id, nullptr));
return;
}
it->second->GetShareInfoForItem(id, std::move(callback));
} }
void OfflineContentAggregator::AddObserver( void OfflineContentAggregator::AddObserver(
......
...@@ -65,7 +65,10 @@ class OfflineContentAggregator : public OfflineContentProvider, ...@@ -65,7 +65,10 @@ class OfflineContentAggregator : public OfflineContentProvider,
void GetItemById(const ContentId& id, SingleItemCallback callback) override; void GetItemById(const ContentId& id, SingleItemCallback callback) override;
void GetAllItems(MultipleItemCallback callback) override; void GetAllItems(MultipleItemCallback callback) override;
void GetVisualsForItem(const ContentId& id, void GetVisualsForItem(const ContentId& id,
const VisualsCallback& callback) override; VisualsCallback callback) override;
void GetShareInfoForItem(const ContentId& id,
ShareCallback callback) override;
void AddObserver(OfflineContentProvider::Observer* observer) override; void AddObserver(OfflineContentProvider::Observer* observer) override;
void RemoveObserver(OfflineContentProvider::Observer* observer) override; void RemoveObserver(OfflineContentProvider::Observer* observer) override;
......
...@@ -187,6 +187,8 @@ TEST_F(OfflineContentAggregatorTest, ActionPropagatesToRightProvider) { ...@@ -187,6 +187,8 @@ TEST_F(OfflineContentAggregatorTest, ActionPropagatesToRightProvider) {
EXPECT_CALL(provider2, PauseDownload(id2)).Times(1); EXPECT_CALL(provider2, PauseDownload(id2)).Times(1);
EXPECT_CALL(provider1, GetVisualsForItem(id1, _)).Times(1); EXPECT_CALL(provider1, GetVisualsForItem(id1, _)).Times(1);
EXPECT_CALL(provider2, GetVisualsForItem(id2, _)).Times(1); EXPECT_CALL(provider2, GetVisualsForItem(id2, _)).Times(1);
EXPECT_CALL(provider1, GetShareInfoForItem(id1, _)).Times(1);
EXPECT_CALL(provider2, GetShareInfoForItem(id2, _)).Times(1);
aggregator_.OpenItem(id1); aggregator_.OpenItem(id1);
aggregator_.OpenItem(id2); aggregator_.OpenItem(id2);
aggregator_.RemoveItem(id1); aggregator_.RemoveItem(id1);
...@@ -199,6 +201,8 @@ TEST_F(OfflineContentAggregatorTest, ActionPropagatesToRightProvider) { ...@@ -199,6 +201,8 @@ TEST_F(OfflineContentAggregatorTest, ActionPropagatesToRightProvider) {
aggregator_.PauseDownload(id2); aggregator_.PauseDownload(id2);
aggregator_.GetVisualsForItem(id1, OfflineContentProvider::VisualsCallback()); aggregator_.GetVisualsForItem(id1, OfflineContentProvider::VisualsCallback());
aggregator_.GetVisualsForItem(id2, OfflineContentProvider::VisualsCallback()); aggregator_.GetVisualsForItem(id2, OfflineContentProvider::VisualsCallback());
aggregator_.GetShareInfoForItem(id1, OfflineContentProvider::ShareCallback());
aggregator_.GetShareInfoForItem(id2, OfflineContentProvider::ShareCallback());
} }
TEST_F(OfflineContentAggregatorTest, ActionPropagatesImmediately) { TEST_F(OfflineContentAggregatorTest, ActionPropagatesImmediately) {
......
...@@ -17,6 +17,7 @@ namespace offline_items_collection { ...@@ -17,6 +17,7 @@ namespace offline_items_collection {
struct ContentId; struct ContentId;
struct OfflineItem; struct OfflineItem;
struct OfflineItemShareInfo;
struct OfflineItemVisuals; struct OfflineItemVisuals;
// A provider of a set of OfflineItems that are meant to be exposed to the UI. // A provider of a set of OfflineItems that are meant to be exposed to the UI.
...@@ -24,8 +25,11 @@ class OfflineContentProvider { ...@@ -24,8 +25,11 @@ class OfflineContentProvider {
public: public:
using OfflineItemList = std::vector<OfflineItem>; using OfflineItemList = std::vector<OfflineItem>;
using VisualsCallback = using VisualsCallback =
base::Callback<void(const ContentId&, base::OnceCallback<void(const ContentId&,
std::unique_ptr<OfflineItemVisuals>)>; std::unique_ptr<OfflineItemVisuals>)>;
using ShareCallback =
base::OnceCallback<void(const ContentId&,
std::unique_ptr<OfflineItemShareInfo>)>;
using MultipleItemCallback = base::OnceCallback<void(const OfflineItemList&)>; using MultipleItemCallback = base::OnceCallback<void(const OfflineItemList&)>;
using SingleItemCallback = using SingleItemCallback =
base::OnceCallback<void(const base::Optional<OfflineItem>&)>; base::OnceCallback<void(const base::Optional<OfflineItem>&)>;
...@@ -92,8 +96,19 @@ class OfflineContentProvider { ...@@ -92,8 +96,19 @@ class OfflineContentProvider {
// |id| or |nullptr| if one doesn't exist. The implementer should post any // |id| or |nullptr| if one doesn't exist. The implementer should post any
// replies even if the results are available immediately to prevent reentrancy // replies even if the results are available immediately to prevent reentrancy
// and for consistent behavior. // and for consistent behavior.
// |callback| should be called no matter what (error, unavailable content,
// etc.).
virtual void GetVisualsForItem(const ContentId& id, virtual void GetVisualsForItem(const ContentId& id,
const VisualsCallback& callback) = 0; VisualsCallback callback) = 0;
// Asks for the right URI to use to share an OfflineItem represented by |id|
// or |nullptr| if there is no associated information to use to share the
// item. Implementer should post any replies even if the results are
// available immediately to prevent reentrancy and for consistent behavior.
// |callback| should be called no matter what (error, unavailable content,
// etc.).
virtual void GetShareInfoForItem(const ContentId& id,
ShareCallback callback) = 0;
// Adds an observer that should be notified of OfflineItem list modifications. // Adds an observer that should be notified of OfflineItem list modifications.
virtual void AddObserver(Observer* observer) = 0; virtual void AddObserver(Observer* observer) = 0;
......
...@@ -98,4 +98,9 @@ OfflineItemVisuals::OfflineItemVisuals(const OfflineItemVisuals& other) = ...@@ -98,4 +98,9 @@ OfflineItemVisuals::OfflineItemVisuals(const OfflineItemVisuals& other) =
default; default;
OfflineItemVisuals::~OfflineItemVisuals() = default; OfflineItemVisuals::~OfflineItemVisuals() = default;
OfflineItemShareInfo::OfflineItemShareInfo() = default;
OfflineItemShareInfo::OfflineItemShareInfo(const OfflineItemShareInfo& other) =
default;
OfflineItemShareInfo::~OfflineItemShareInfo() = default;
} // namespace offline_items_collection } // namespace offline_items_collection
...@@ -216,6 +216,24 @@ struct OfflineItemVisuals { ...@@ -216,6 +216,24 @@ struct OfflineItemVisuals {
gfx::Image icon; gfx::Image icon;
}; };
// This struct holds additional information related to sharing a particular
// OfflineItem. This information doesn't necessarily exist within OfflineItem
// because it may be expensive/unnecessary to compute until the user attempts to
// share the item.
struct OfflineItemShareInfo {
OfflineItemShareInfo();
OfflineItemShareInfo(const OfflineItemShareInfo& other);
~OfflineItemShareInfo();
// The local URI where the file can be accessed on disk. This may be
// different from |OfflineItem::file_path| depending on whether or not the
// file can be accessed directly.
// If this path is invalid the request data from OfflineItem will be used
// to share the information instead (e.g. |OfflineItem::page_url|).
base::FilePath uri;
};
} // namespace offline_items_collection } // namespace offline_items_collection
#endif // COMPONENTS_OFFLINE_ITEMS_COLLECTION_OFFLINE_ITEM_H_ #endif // COMPONENTS_OFFLINE_ITEMS_COLLECTION_OFFLINE_ITEM_H_
...@@ -41,8 +41,8 @@ class MockOfflineContentProvider : public OfflineContentProvider { ...@@ -41,8 +41,8 @@ class MockOfflineContentProvider : public OfflineContentProvider {
MOCK_METHOD1(CancelDownload, void(const ContentId&)); MOCK_METHOD1(CancelDownload, void(const ContentId&));
MOCK_METHOD1(PauseDownload, void(const ContentId&)); MOCK_METHOD1(PauseDownload, void(const ContentId&));
MOCK_METHOD2(ResumeDownload, void(const ContentId&, bool)); MOCK_METHOD2(ResumeDownload, void(const ContentId&, bool));
MOCK_METHOD2(GetVisualsForItem, MOCK_METHOD2(GetVisualsForItem, void(const ContentId&, VisualsCallback));
void(const ContentId&, const VisualsCallback&)); MOCK_METHOD2(GetShareInfoForItem, void(const ContentId&, ShareCallback));
void GetAllItems(MultipleItemCallback callback) override; void GetAllItems(MultipleItemCallback callback) override;
void GetItemById(const ContentId& id, SingleItemCallback callback) override; void GetItemById(const ContentId& id, SingleItemCallback callback) override;
void AddObserver(Observer* observer) override; void AddObserver(Observer* observer) override;
......
...@@ -96,8 +96,14 @@ void ThrottledOfflineContentProvider::OnGetItemByIdDone( ...@@ -96,8 +96,14 @@ void ThrottledOfflineContentProvider::OnGetItemByIdDone(
void ThrottledOfflineContentProvider::GetVisualsForItem( void ThrottledOfflineContentProvider::GetVisualsForItem(
const ContentId& id, const ContentId& id,
const VisualsCallback& callback) { VisualsCallback callback) {
wrapped_provider_->GetVisualsForItem(id, callback); wrapped_provider_->GetVisualsForItem(id, std::move(callback));
}
void ThrottledOfflineContentProvider::GetShareInfoForItem(
const ContentId& id,
ShareCallback callback) {
wrapped_provider_->GetShareInfoForItem(id, std::move(callback));
} }
void ThrottledOfflineContentProvider::AddObserver( void ThrottledOfflineContentProvider::AddObserver(
......
...@@ -45,7 +45,9 @@ class ThrottledOfflineContentProvider ...@@ -45,7 +45,9 @@ class ThrottledOfflineContentProvider
void GetItemById(const ContentId& id, SingleItemCallback callback) override; void GetItemById(const ContentId& id, SingleItemCallback callback) override;
void GetAllItems(MultipleItemCallback callback) override; void GetAllItems(MultipleItemCallback callback) override;
void GetVisualsForItem(const ContentId& id, void GetVisualsForItem(const ContentId& id,
const VisualsCallback& callback) override; VisualsCallback callback) override;
void GetShareInfoForItem(const ContentId& id,
ShareCallback callback) override;
void AddObserver(OfflineContentProvider::Observer* observer) override; void AddObserver(OfflineContentProvider::Observer* observer) override;
void RemoveObserver(OfflineContentProvider::Observer* observer) override; void RemoveObserver(OfflineContentProvider::Observer* observer) override;
......
...@@ -231,26 +231,35 @@ void DownloadUIAdapter::GetAllItems( ...@@ -231,26 +231,35 @@ void DownloadUIAdapter::GetAllItems(
std::move(callback), std::move(offline_items))); std::move(callback), std::move(offline_items)));
} }
void DownloadUIAdapter::GetVisualsForItem( void DownloadUIAdapter::GetVisualsForItem(const ContentId& id,
const ContentId& id, VisualsCallback visuals_callback) {
const VisualsCallback& visuals_callback) { model_->GetPageByGuid(id.id,
model_->GetPageByGuid( base::BindOnce(&DownloadUIAdapter::OnPageGetForVisuals,
id.id, weak_ptr_factory_.GetWeakPtr(), id,
base::BindOnce(&DownloadUIAdapter::OnPageGetForVisuals, std::move(visuals_callback)));
weak_ptr_factory_.GetWeakPtr(), id, visuals_callback));
} }
void DownloadUIAdapter::OnPageGetForVisuals( void DownloadUIAdapter::GetShareInfoForItem(const ContentId& id,
const ContentId& id, ShareCallback callback) {
const VisualsCallback& visuals_callback, // TODO(853850): Publish and expose the content URI here instead of in
const OfflinePageItem* page) { // DownloadUtils.java.
// TODO(xingliu): Provide OfflineItemShareInfo to |callback|.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), id,
nullptr /* OfflineItemShareInfo */));
}
void DownloadUIAdapter::OnPageGetForVisuals(const ContentId& id,
VisualsCallback visuals_callback,
const OfflinePageItem* page) {
if (!page) { if (!page) {
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(visuals_callback, id, nullptr)); FROM_HERE, base::BindOnce(std::move(visuals_callback), id, nullptr));
return; return;
} }
VisualResultCallback callback = base::BindOnce(visuals_callback, id); VisualResultCallback callback =
base::BindOnce(std::move(visuals_callback), id);
if (page->client_id.name_space == kSuggestedArticlesNamespace) { if (page->client_id.name_space == kSuggestedArticlesNamespace) {
// Report PrefetchedItemHasThumbnail along with result callback. // Report PrefetchedItemHasThumbnail along with result callback.
auto report_and_callback = auto report_and_callback =
......
...@@ -91,7 +91,9 @@ class DownloadUIAdapter : public OfflineContentProvider, ...@@ -91,7 +91,9 @@ class DownloadUIAdapter : public OfflineContentProvider,
void GetAllItems( void GetAllItems(
OfflineContentProvider::MultipleItemCallback callback) override; OfflineContentProvider::MultipleItemCallback callback) override;
void GetVisualsForItem(const ContentId& id, void GetVisualsForItem(const ContentId& id,
const VisualsCallback& callback) override; VisualsCallback callback) override;
void GetShareInfoForItem(const ContentId& id,
ShareCallback callback) override;
void AddObserver(OfflineContentProvider::Observer* observer) override; void AddObserver(OfflineContentProvider::Observer* observer) override;
void RemoveObserver(OfflineContentProvider::Observer* observer) override; void RemoveObserver(OfflineContentProvider::Observer* observer) override;
...@@ -139,7 +141,7 @@ class DownloadUIAdapter : public OfflineContentProvider, ...@@ -139,7 +141,7 @@ class DownloadUIAdapter : public OfflineContentProvider,
std::unique_ptr<OfflineContentProvider::OfflineItemList> offline_items, std::unique_ptr<OfflineContentProvider::OfflineItemList> offline_items,
std::vector<std::unique_ptr<SavePageRequest>> requests); std::vector<std::unique_ptr<SavePageRequest>> requests);
void OnPageGetForVisuals(const ContentId& id, void OnPageGetForVisuals(const ContentId& id,
const VisualsCallback& visuals_callback, VisualsCallback visuals_callback,
const OfflinePageItem* page); const OfflinePageItem* page);
void OnPageGetForGetItem(const ContentId& id, void OnPageGetForGetItem(const ContentId& id,
OfflineContentProvider::SingleItemCallback callback, OfflineContentProvider::SingleItemCallback callback,
......
...@@ -675,6 +675,44 @@ TEST_F(DownloadUIAdapterTest, GetVisualsForItemBadDecode) { ...@@ -675,6 +675,44 @@ TEST_F(DownloadUIAdapterTest, GetVisualsForItemBadDecode) {
EXPECT_TRUE(called); EXPECT_TRUE(called);
} }
TEST_F(DownloadUIAdapterTest, GetShareInfoForItem) {
AddInitialPage(kTestClientIdPrefetch);
bool called = false;
auto callback = base::BindLambdaForTesting(
[&](const offline_items_collection::ContentId& id,
std::unique_ptr<offline_items_collection::OfflineItemShareInfo>
share_info) {
EXPECT_EQ(kTestContentId1, id);
// TODO(853850): Properly test that the correct URI is used once
// supported.
EXPECT_FALSE(share_info);
called = true;
});
adapter->GetShareInfoForItem(kTestContentId1, callback);
PumpLoop();
EXPECT_TRUE(called);
}
TEST_F(DownloadUIAdapterTest, GetShareInfoForItemInvalidItem) {
AddInitialPage(kTestClientIdPrefetch);
const ContentId kContentID("not", "valid");
bool called = false;
auto callback = base::BindLambdaForTesting(
[&](const offline_items_collection::ContentId& id,
std::unique_ptr<offline_items_collection::OfflineItemShareInfo>
share_info) {
EXPECT_EQ(kContentID, id);
EXPECT_FALSE(share_info);
called = true;
});
adapter->GetShareInfoForItem(kContentID, callback);
PumpLoop();
EXPECT_TRUE(called);
}
TEST_F(DownloadUIAdapterTest, ThumbnailAddedUpdatesItem) { TEST_F(DownloadUIAdapterTest, ThumbnailAddedUpdatesItem) {
// Add an item without a thumbnail. Then notify the adapter about the added // Add an item without a thumbnail. Then notify the adapter about the added
// thumbnail. It should notify the delegate about the updated item. // thumbnail. It should notify the delegate about the updated item.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment