Commit e220820b authored by Jeffrey Cohen's avatar Jeffrey Cohen Committed by Commit Bot

[Screenshots] enable Sharing a screenshot.

This allows screenshots to be shared by looping back to the share sheet.
A new JNI call to make a Uri from an arbitrary bitmap is also included.

Bug: 1093372
Change-Id: I444634f5a19ac5a9b3b30c961127de7fe5cda72e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2243496
Commit-Queue: Jeffrey Cohen <jeffreycohen@chromium.org>
Reviewed-by: default avatarTanya Gupta <tgupta@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarKyle Milka <kmilka@chromium.org>
Reviewed-by: default avatarKristi Park <kristipark@chromium.org>
Cr-Commit-Position: refs/heads/master@{#780501}
parent ccde4769
......@@ -794,6 +794,7 @@ junit_binary("chrome_junit_tests") {
"//components/browser_ui/android/bottomsheet:java",
"//components/browser_ui/media/android:java",
"//components/browser_ui/notifications/android:java",
"//components/browser_ui/share/android:java",
"//components/browser_ui/site_settings/android:java",
"//components/browser_ui/util/android:java",
"//components/browser_ui/widget/android:java",
......
......@@ -5,6 +5,7 @@
source_set("share") {
sources = [
"bitmap_download_request.cc",
"bitmap_uri_request.cc",
"features.cc",
"features.h",
"qr_code_generation_request.cc",
......
......@@ -29,6 +29,7 @@ android_resources("java_resources") {
generate_jni("jni_headers") {
sources = [
"java/src/org/chromium/chrome/browser/share/BitmapDownloadRequest.java",
"java/src/org/chromium/chrome/browser/share/BitmapUriRequest.java",
"java/src/org/chromium/chrome/browser/share/qrcode/QRCodeGenerationRequest.java",
]
}
// Copyright 2020 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.share;
import android.graphics.Bitmap;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.annotations.NativeMethods;
/**
* A Java API for requesting an Uri from a bitmap.
*/
public class BitmapUriRequest {
public static String bitmapUri(Bitmap bitmap) {
return BitmapUriRequestJni.get().bitmapUri(bitmap);
}
@VisibleForTesting
@NativeMethods
public interface Natives {
String bitmapUri(Bitmap bitmap);
}
}
......@@ -11,6 +11,7 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.image_editor.ImageEditorDialogCoordinator;
import org.chromium.chrome.browser.modules.ModuleInstallUi;
import org.chromium.chrome.browser.screenshot.EditorScreenshotTask;
import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.modules.image_editor.ImageEditorModuleProvider;
......@@ -24,13 +25,16 @@ public class ScreenshotCoordinator {
private final Activity mActivity;
private final Tab mTab;
private final ChromeOptionShareCallback mChromeOptionShareCallback;
private EditorScreenshotTask mScreenshotTask;
private Bitmap mScreenshot;
public ScreenshotCoordinator(Activity activity, Tab tab) {
public ScreenshotCoordinator(
Activity activity, Tab tab, ChromeOptionShareCallback chromeOptionShareCallback) {
mActivity = activity;
mTab = tab;
mChromeOptionShareCallback = chromeOptionShareCallback;
}
/**
......@@ -79,7 +83,8 @@ public class ScreenshotCoordinator {
*/
private void launchSharesheet() {
ScreenshotShareSheetDialogCoordinator shareSheet =
new ScreenshotShareSheetDialogCoordinator(mActivity, mScreenshot);
new ScreenshotShareSheetDialogCoordinator(
mActivity, mScreenshot, mTab, mChromeOptionShareCallback);
shareSheet.showShareSheet();
mScreenshot = null;
}
......
......@@ -7,6 +7,8 @@ package org.chromium.chrome.browser.share.screenshot;
import android.content.Context;
import android.graphics.Bitmap;
import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
......@@ -31,7 +33,8 @@ public class ScreenshotShareSheetCoordinator {
* @param screenshotShareSheetView the view for the screenshot share sheet.
*/
public ScreenshotShareSheetCoordinator(Context context, Bitmap screenshot,
Runnable deleteRunnable, ScreenshotShareSheetView screenshotShareSheetView) {
Runnable deleteRunnable, ScreenshotShareSheetView screenshotShareSheetView, Tab tab,
ChromeOptionShareCallback shareSheetCallback) {
ArrayList<PropertyKey> allProperties =
new ArrayList<>(Arrays.asList(ScreenshotShareSheetViewProperties.ALL_KEYS));
mModel = new PropertyModel(allProperties);
......@@ -39,7 +42,7 @@ public class ScreenshotShareSheetCoordinator {
mModel.set(ScreenshotShareSheetViewProperties.SCREENSHOT_BITMAP, screenshot);
mSaveDelegate = new ScreenshotShareSheetSaveDelegate(context, mModel);
mMediator = new ScreenshotShareSheetMediator(
context, mModel, deleteRunnable, mSaveDelegate::save);
context, mModel, deleteRunnable, mSaveDelegate::save, tab, shareSheetCallback);
PropertyModelChangeProcessor.create(
mModel, screenshotShareSheetView, ScreenshotShareSheetViewBinder::bind);
......
......@@ -13,6 +13,8 @@ import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback;
import org.chromium.chrome.browser.tab.Tab;
/**
* ScreenshotShareSheetDialog is the main view for sharing non edited screenshots.
......@@ -22,6 +24,8 @@ public class ScreenshotShareSheetDialog extends DialogFragment {
private ScreenshotShareSheetView mDialogView;
private Bitmap mScreenshot;
private Runnable mDeleteRunnable;
private Tab mTab;
private ChromeOptionShareCallback mShareCallback;
/**
* The ScreenshotShareSheetDialog constructor.
......@@ -32,10 +36,15 @@ public class ScreenshotShareSheetDialog extends DialogFragment {
* Initialize the dialog outside of the constructor as fragments require default constructor.
* @param screenshot The screenshot image to show.
* @param deleteRunnable The function to call on delete.
* @param tab The shared tab.
* @param shareSheetCoordnator the base share sheet coordinator
*/
public void init(Bitmap screenshot, Runnable deleteRunnable) {
public void init(Bitmap screenshot, Runnable deleteRunnable, Tab tab,
ChromeOptionShareCallback shareSheetCallback) {
mScreenshot = screenshot;
mDeleteRunnable = deleteRunnable;
mTab = tab;
mShareCallback = shareSheetCallback;
}
@Override
......@@ -53,8 +62,9 @@ public class ScreenshotShareSheetDialog extends DialogFragment {
org.chromium.chrome.browser.share.R.layout.screenshot_share_sheet, null);
builder.setView(screenshotShareSheetView);
ScreenshotShareSheetCoordinator shareCoordinator = new ScreenshotShareSheetCoordinator(
mContext, mScreenshot, mDeleteRunnable, screenshotShareSheetView);
ScreenshotShareSheetCoordinator shareCoordinator =
new ScreenshotShareSheetCoordinator(mContext, mScreenshot, mDeleteRunnable,
screenshotShareSheetView, mTab, mShareCallback);
return builder.create();
}
}
......@@ -8,6 +8,8 @@ import android.app.Activity;
import android.app.FragmentManager;
import android.graphics.Bitmap;
import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback;
import org.chromium.chrome.browser.tab.Tab;
/**
* Coordinator for displaying the screenshot share sheet dialog.
*/
......@@ -17,16 +19,17 @@ public class ScreenshotShareSheetDialogCoordinator {
private final Bitmap mScreenshot;
/**
* Constructs a new ShareSheetCoordinator.
* Constructs a new Screenshot Dialog.
*
* @param context The context to use for user permissions.
* @param screenshot The screenshot to be shared.
*/
public ScreenshotShareSheetDialogCoordinator(Activity activity, Bitmap screenshot) {
public ScreenshotShareSheetDialogCoordinator(Activity activity, Bitmap screenshot, Tab tab,
ChromeOptionShareCallback shareCallback) {
mFragmentManager = activity.getFragmentManager();
mScreenshot = screenshot;
mDialog = new ScreenshotShareSheetDialog();
mDialog.init(mScreenshot, this::dismiss);
mDialog.init(mScreenshot, this::dismiss, tab, shareCallback);
}
/**
......
......@@ -5,8 +5,16 @@
package org.chromium.chrome.browser.share.screenshot;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import org.chromium.chrome.browser.share.BitmapUriRequest;
import org.chromium.chrome.browser.share.ChromeShareExtras;
import org.chromium.chrome.browser.share.screenshot.ScreenshotShareSheetViewProperties.NoArgOperation;
import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.components.browser_ui.share.ShareParams;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modelutil.PropertyModel;
/**
......@@ -16,8 +24,10 @@ import org.chromium.ui.modelutil.PropertyModel;
class ScreenshotShareSheetMediator {
private final PropertyModel mModel;
private final Context mContext;
private final Runnable mDeleteRunnable;
private final Runnable mSaveRunnable;
private final Runnable mDeleteRunnable;
private final ChromeOptionShareCallback mChromeOptionShareCallback;
private Tab mTab;
/**
* The ScreenshotShareSheetMediator constructor.
......@@ -26,11 +36,14 @@ class ScreenshotShareSheetMediator {
* @param deleteRunnable The action to take when cancel or delete is called.
*/
ScreenshotShareSheetMediator(Context context, PropertyModel propertyModel,
Runnable deleteRunnable, Runnable saveRunnable) {
Runnable deleteRunnable, Runnable saveRunnable, Tab tab,
ChromeOptionShareCallback chromeOptionShareCallback) {
mDeleteRunnable = deleteRunnable;
mSaveRunnable = saveRunnable;
mContext = context;
mModel = propertyModel;
mTab = tab;
mChromeOptionShareCallback = chromeOptionShareCallback;
mModel.set(ScreenshotShareSheetViewProperties.NO_ARG_OPERATION_LISTENER,
operation -> { performNoArgOperation(operation); });
}
......@@ -55,6 +68,25 @@ class ScreenshotShareSheetMediator {
* Sends the current image to the share target.
*/
private void share() {
// TODO(crbug/1024586): export image
Bitmap bitmap = mModel.get(ScreenshotShareSheetViewProperties.SCREENSHOT_BITMAP);
final Uri bitmapUri = Uri.parse(BitmapUriRequest.bitmapUri(bitmap));
// TODO(crbug.com/1093386) Add Metrics for tracking size or Uri and performance of
// UriRequest.
WindowAndroid window = mTab.getWindowAndroid();
String title = mTab.getTitle();
String visibleUrl = mTab.getUrlString();
ShareParams params = new ShareParams.Builder(window, title, visibleUrl)
.setScreenshotUri(bitmapUri)
.build();
ChromeShareExtras chromeShareExtras = new ChromeShareExtras.Builder()
.setSaveLastUsed(false)
.setShareDirectly(false)
.setIsUrlOfVisiblePage(false)
.build();
mChromeOptionShareCallback.showThirdPartyShareSheet(
params, chromeShareExtras, System.currentTimeMillis());
mDeleteRunnable.run();
}
}
// Copyright 2020 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.share.share_sheet;
import org.chromium.chrome.browser.share.ChromeShareExtras;
import org.chromium.components.browser_ui.share.ShareParams;
/**
* An interface to help other chrome features surface share sheet APIs.
*
* TODO(crbug.com/1009124) This class can become the Public API of ShareSheetCoordinator, and
* ShareSheetCoordinator can be rewritten as ShareSheetCoordinatorImpl.
*/
public interface ChromeOptionShareCallback {
/**
* Used to show only the bottom bar of the share sheet
* @param params The share parameters.
* @param chromeShareExtras The extras not contained in {@code params}.
*/
public void showThirdPartyShareSheet(
ShareParams params, ChromeShareExtras chromeShareExtras, long shareStartTime);
}
\ No newline at end of file
......@@ -43,7 +43,7 @@ import java.util.Set;
* Provides {@code PropertyModel}s of Chrome-provided sharing options.
*/
// TODO(crbug/1022172): Should be package-protected once modularization is complete.
class ChromeProvidedSharingOptionsProvider {
public class ChromeProvidedSharingOptionsProvider {
private final Activity mActivity;
private final Supplier<Tab> mTabProvider;
private final BottomSheetController mBottomSheetController;
......@@ -53,6 +53,7 @@ class ChromeProvidedSharingOptionsProvider {
private final Callback<Tab> mPrintTabCallback;
private final long mShareStartTime;
private final List<FirstPartyOption> mOrderedFirstPartyOptions;
private final ChromeOptionShareCallback mChromeOptionShareCallback;
private ScreenshotCoordinator mScreenshotCoordinator;
/**
......@@ -68,11 +69,14 @@ class ChromeProvidedSharingOptionsProvider {
* @param shareParams The {@link ShareParams} for the current share.
* @param printTab A {@link Callback} that will print a given Tab.
* @param shareStartTime The start time of the current share.
* @param chromeOptionShareCallback A ChromeOptionShareCallback that can be used by
* Chrome-provdied sharing options.
*/
ChromeProvidedSharingOptionsProvider(Activity activity, Supplier<Tab> tabProvider,
BottomSheetController bottomSheetController,
ShareSheetBottomSheetContent bottomSheetContent, PrefServiceBridge prefServiceBridge,
ShareParams shareParams, Callback<Tab> printTab, long shareStartTime) {
ShareParams shareParams, Callback<Tab> printTab, long shareStartTime,
ChromeOptionShareCallback chromeOptionShareCallback) {
mActivity = activity;
mTabProvider = tabProvider;
mBottomSheetController = bottomSheetController;
......@@ -83,6 +87,7 @@ class ChromeProvidedSharingOptionsProvider {
mShareStartTime = shareStartTime;
mOrderedFirstPartyOptions = new ArrayList<>();
initializeFirstPartyOptionsInOrder();
mChromeOptionShareCallback = chromeOptionShareCallback;
}
/**
......@@ -170,8 +175,8 @@ class ChromeProvidedSharingOptionsProvider {
RecordHistogram.recordMediumTimesHistogram(
"Sharing.SharingHubAndroid.TimeToShare",
System.currentTimeMillis() - mShareStartTime);
mScreenshotCoordinator =
new ScreenshotCoordinator(mActivity, mTabProvider.get());
mScreenshotCoordinator = new ScreenshotCoordinator(
mActivity, mTabProvider.get(), mChromeOptionShareCallback);
// Capture a screenshot once the bottom sheet is fully hidden. The
// observer will then remove itself.
mBottomSheetController.addObserver(mSheetObserver);
......
......@@ -31,7 +31,7 @@ import java.util.Set;
* Coordinator for displaying the share sheet.
*/
// TODO(crbug/1022172): Should be package-protected once modularization is complete.
public class ShareSheetCoordinator implements ActivityStateObserver {
public class ShareSheetCoordinator implements ActivityStateObserver, ChromeOptionShareCallback {
private final BottomSheetController mBottomSheetController;
private final Supplier<Tab> mTabProvider;
private final ShareSheetPropertyModelBuilder mPropertyModelBuilder;
......@@ -92,7 +92,8 @@ public class ShareSheetCoordinator implements ActivityStateObserver {
}
// Used by first party features to share with only non-chrome apps.
protected void showThirdPartyShareSheet(
@Override
public void showThirdPartyShareSheet(
ShareParams params, ChromeShareExtras chromeShareExtras, long shareStartTime) {
mExcludeFirstParty = true;
showShareSheet(params, chromeShareExtras, shareStartTime);
......@@ -106,7 +107,7 @@ public class ShareSheetCoordinator implements ActivityStateObserver {
ChromeProvidedSharingOptionsProvider chromeProvidedSharingOptionsProvider =
new ChromeProvidedSharingOptionsProvider(activity, mTabProvider,
mBottomSheetController, mBottomSheet, mPrefServiceBridge, shareParams,
mPrintTabCallback, mShareStartTime);
mPrintTabCallback, mShareStartTime, this);
return chromeProvidedSharingOptionsProvider.getPropertyModels(contentTypes);
}
......
......@@ -6,6 +6,7 @@
# dependencies are removed.
share_java_sources = [
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/BitmapDownloadRequest.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/BitmapUriRequest.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/clipboard/ClipboardImageFileProvider.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QRCodeGenerationRequest.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeCoordinator.java",
......@@ -34,6 +35,7 @@ share_java_sources = [
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetView.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetViewBinder.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetViewProperties.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeOptionShareCallback.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java",
......
......@@ -4,8 +4,11 @@
package org.chromium.chrome.browser.share.screenshot;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
......@@ -21,7 +24,12 @@ import org.robolectric.annotation.Config;
import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.share.BitmapUriRequest;
import org.chromium.chrome.browser.share.BitmapUriRequestJni;
import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.ui.modelutil.PropertyModel;
......@@ -46,6 +54,18 @@ public class ScreenshotShareSheetMediatorUnitTest {
@Mock
Activity mContext;
@Mock
Tab mTab;
@Mock
ChromeOptionShareCallback mShareCallback;
@Rule
public JniMocker mJniMocker = new JniMocker();
@Mock
private BitmapUriRequest.Natives mBitmapUriRequest;
private PropertyModel mModel;
private ScreenshotShareSheetMediator mMediator;
......@@ -54,13 +74,21 @@ public class ScreenshotShareSheetMediatorUnitTest {
MockitoAnnotations.initMocks(this);
doNothing().when(mDeleteRunnable).run();
doNothing().when(mSaveRunnable).run();
doNothing().when(mShareCallback).showThirdPartyShareSheet(any(), any(), anyLong());
mJniMocker.mock(BitmapUriRequestJni.TEST_HOOKS, mBitmapUriRequest);
when(mBitmapUriRequest.bitmapUri(any())).thenReturn("bitmapUri");
mModel = new PropertyModel(ScreenshotShareSheetViewProperties.ALL_KEYS);
mMediator =
new ScreenshotShareSheetMediator(mContext, mModel, mDeleteRunnable, mSaveRunnable);
mMediator = new ScreenshotShareSheetMediator(
mContext, mModel, mDeleteRunnable, mSaveRunnable, mTab, mShareCallback);
}
@Test
public void onClickDelete() {
Callback<Integer> callback =
......@@ -79,6 +107,16 @@ public class ScreenshotShareSheetMediatorUnitTest {
verify(mSaveRunnable).run();
}
@Test
public void onClickShare() {
Callback<Integer> callback =
mModel.get(ScreenshotShareSheetViewProperties.NO_ARG_OPERATION_LISTENER);
callback.onResult(ScreenshotShareSheetViewProperties.NoArgOperation.SHARE);
verify(mShareCallback).showThirdPartyShareSheet(any(), any(), anyLong());
verify(mDeleteRunnable).run();
}
@After
public void tearDown() {}
}
......@@ -191,7 +191,8 @@ public class ChromeProvidedSharingOptionsProviderTest {
new ShareSheetBottomSheetContent(mActivity), mPrefServiceBridge,
/*shareParams=*/null,
/*TabPrinterDelegate=*/null,
/*shareStartTime=*/0);
/*shareStartTime=*/0,
/*shareSheetCoordinator=*/null);
}
private void assertModelsAreInTheRightOrder(
......
......@@ -5,6 +5,7 @@
# TODO(crbug.com/1022172): This should be a separate build target when circular dependencies are removed.
share_test_java_sources = [
"//chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/clipboard/ClipboardImageFileProviderTest.java",
"//chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetViewTest.java",
"//chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java",
"//chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinatorTest.java",
"//chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetPropertyModelBuilderTest.java",
......
// Copyright 2020 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 <jni.h>
#include "base/android/jni_string.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/share/android/jni_headers/BitmapUriRequest_jni.h"
#include "chrome/browser/share/qr_code_generation_request.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/download_request_utils.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/gfx/android/java_bitmap.h"
using base::android::JavaParamRef;
static base::android::ScopedJavaLocalRef<jstring>
JNI_BitmapUriRequest_BitmapUri(JNIEnv* env,
const JavaParamRef<jobject>& j_bitmap) {
SkBitmap bitmap =
gfx::CreateSkBitmapFromJavaBitmap(gfx::JavaBitmap(j_bitmap));
return base::android::ConvertUTF8ToJavaString(
env, webui::GetBitmapDataUrl(bitmap));
}
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