Commit 0780ac2a authored by benwgold's avatar benwgold Committed by Commit Bot

Pass signed in account name when available along with the deeplink intent.

Also pass an indicator about whether incognito mode is available and do
not send account name if enabled.

Finally, refactor image sharing to separate out image retrieval from sharing functionality and use callbacks to get cleaner separation between lens and other sharing use cases.

Bug: 1002247
Change-Id: I72f05cf17c8623cc47ac4f35adeab16f70381f30
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1792239
Commit-Queue: Ben Goldberger <benwgold@google.com>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#696136}
parent b756a028
......@@ -544,7 +544,7 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
ShareHelper.share(linkShareParams);
} else if (itemId == R.id.contextmenu_search_with_google_lens) {
ContextMenuUma.record(params, ContextMenuUma.Action.SEARCH_WITH_GOOGLE_LENS);
helper.searchWithGoogleLens();
helper.searchWithGoogleLens(mDelegate.isIncognito());
} else if (itemId == R.id.contextmenu_search_by_image) {
ContextMenuUma.record(params, ContextMenuUma.Action.SEARCH_BY_IMAGE);
helper.searchForImage();
......
......@@ -5,11 +5,10 @@
package org.chromium.chrome.browser.contextmenu;
import android.app.Activity;
import android.content.ComponentName;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.Nullable;
import android.util.Pair;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
......@@ -188,9 +187,12 @@ public class ContextMenuHelper implements OnCreateContextMenuListener {
/**
* Search for the image by intenting to the lens app with the image data attached.
* @param isIncognito Whether the image to search came from an incognito context.
*/
public void searchWithGoogleLens() {
shareImageDirectly(null, /* shareWithGoogleLens = */ true);
public void searchWithGoogleLens(boolean isIncognito) {
retrieveImage((Uri imageUri) -> {
ShareHelper.shareImageWithGoogleLens(mActivity, imageUri, isIncognito);
});
}
/**
......@@ -207,36 +209,37 @@ public class ContextMenuHelper implements OnCreateContextMenuListener {
* it will use the right activity set when the menu was displayed.
*/
void shareImage() {
shareImageDirectly(null, /* shareWithGoogleLens = */ false);
retrieveImage((Uri imageUri) -> { ShareHelper.shareImage(mActivity, null, imageUri); });
}
/**
* Share the image that triggered the current context menu with the last app used to share.
*/
private void shareImageWithLastShareComponent() {
shareImageDirectly(
ShareHelper.getLastShareComponentName(null), /* shareWithGoogleLens = */ false);
retrieveImage((Uri imageUri) -> {
ShareHelper.shareImage(
mActivity, ShareHelper.getLastShareComponentName(null), imageUri);
});
}
/**
* Share image triggered with the current context menu directly with a specific app.
* @param name The {@link ComponentName} of the app to share the image directly with.
* @param shareWithGoogleLens Whether to share with the Google Lens (overrides the
* value specified in the ComponentName field).
* Retrieves a URI for the selected image for sharing with external apps. If the function fails
* to retrieve the image bytes or generate a URI the callback will *not* be called.
* @param callback Called once the the image is generated and ready to be shared.
*/
private void shareImageDirectly(
@Nullable final ComponentName name, boolean shareWithGoogleLens) {
private void retrieveImage(Callback<Uri> callback) {
if (mNativeContextMenuHelper == 0) return;
Callback<byte[]> callback = new Callback<byte[]>() {
Callback<byte[]> imageRetrievalCallback = new Callback<byte[]>() {
@Override
public void onResult(byte[] result) {
if (mActivity == null) return;
ShareHelper.shareImage(mActivity, result, name, shareWithGoogleLens);
ShareHelper.generateUriFromData(mActivity, result, callback);
}
};
ContextMenuHelperJni.get().retrieveImageForShare(mNativeContextMenuHelper,
ContextMenuHelper.this, callback, MAX_SHARE_DIMEN_PX, MAX_SHARE_DIMEN_PX);
ContextMenuHelper.this, imageRetrievalCallback, MAX_SHARE_DIMEN_PX,
MAX_SHARE_DIMEN_PX);
}
/**
......
......@@ -15,6 +15,7 @@ import android.text.TextUtils;
import org.chromium.base.ContextUtils;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.components.signin.ChromeSigninController;
/**
* This class provides utilities for intenting into Google Lens.
......@@ -22,6 +23,8 @@ import org.chromium.chrome.browser.IntentHandler;
public class LensUtils {
private static final String LENS_CONTRACT_URI = "googleapp://lens";
private static final String LENS_BITMAP_URI_KEY = "LensBitmapUriKey";
private static final String ACCOUNT_NAME_URI_KEY = "AccountNameUriKey";
private static final String INCOGNITO_URI_KEY = "IncognitoUriKey";
private static final String MIN_AGSA_VERSION_FEATURE_PARAM_NAME = "minAgsaVersionName";
private static final String MIN_AGSA_VERSION_NAME_FOR_LENS_POSTCAPTURE = "8.19";
......@@ -35,7 +38,8 @@ public class LensUtils {
public static String getLensActivityVersionNameIfAvailable(Context context) {
try {
PackageManager pm = context.getPackageManager();
Intent lensIntent = getShareWithGoogleLensIntent(Uri.EMPTY);
// No data transmission occurring so safe to assume incognito is false.
Intent lensIntent = getShareWithGoogleLensIntent(Uri.EMPTY, /* isIncognito= */ false);
ComponentName lensActivity = lensIntent.resolveActivity(pm);
if (lensActivity == null) return "";
PackageInfo packageInfo = pm.getPackageInfo(lensActivity.getPackageName(), 0);
......@@ -111,14 +115,22 @@ public class LensUtils {
* Get a deeplink intent to Google Lens with an optional content provider image URI.
* @param imageUri The content provider URI generated by chrome (or empty URI)
* if only resolving the activity.
* @param isIncognito Whether the current tab is in incognito mode.
* @return The intent to Google Lens.
*/
public static Intent getShareWithGoogleLensIntent(Uri imageUri) {
public static Intent getShareWithGoogleLensIntent(Uri imageUri, boolean isIncognito) {
String signedInAccountName = ChromeSigninController.get().getSignedInAccountName();
// If incognito do not send the account name to avoid leaking session information to Lens.
if (signedInAccountName == null || isIncognito) signedInAccountName = "";
Uri lensUri = Uri.parse(LENS_CONTRACT_URI);
if (!Uri.EMPTY.equals(imageUri)) {
lensUri = lensUri.buildUpon()
.appendQueryParameter(LENS_BITMAP_URI_KEY, imageUri.toString())
.build();
lensUri =
lensUri.buildUpon()
.appendQueryParameter(LENS_BITMAP_URI_KEY, imageUri.toString())
.appendQueryParameter(ACCOUNT_NAME_URI_KEY, signedInAccountName)
.appendQueryParameter(INCOGNITO_URI_KEY, Boolean.toString(isIncognito))
.build();
ContextUtils.getApplicationContext().grantUriPermission(
IntentHandler.PACKAGE_GSA, imageUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
......
......@@ -315,16 +315,16 @@ public class ShareHelper {
}
/**
* Trigger the share action for the given image data.
* Generate a temporary URI for a set of JPEG bytes and provide that URI to a callback for
* sharing.
* @param activity The activity used to trigger the share action.
* @param jpegImageData The image data to be shared in jpeg format.
* @param name When this is not null, it will share the image directly with the
* {@link ComponentName}
* @param shareWithGoogleLens When this is true activate a special intent
* to Google Lens and ignore the value set in 'name'.
* @param callback A provided callback function which will act on the generated URI.
*/
public static void shareImage(final Activity activity, final byte[] jpegImageData,
final ComponentName name, final boolean shareWithGoogleLens) {
public static void generateUriFromData(
final Activity activity, final byte[] jpegImageData, Callback<Uri> callback) {
if (jpegImageData.length == 0) {
Log.w(TAG, "Share failed -- Received image contains no data.");
return;
......@@ -359,35 +359,53 @@ public class ShareHelper {
@Override
protected void onPostExecute(Uri imageUri) {
if (imageUri == null) return;
if (imageUri == null) {
return;
}
if (ApplicationStatus.getStateForApplication()
!= ApplicationState.HAS_DESTROYED_ACTIVITIES) {
Intent shareIntent;
if (shareWithGoogleLens) {
shareIntent = LensUtils.getShareWithGoogleLensIntent(imageUri);
fireIntent(activity, shareIntent, true);
} else {
shareIntent = getShareImageIntent(imageUri);
if (name == null) {
if (TargetChosenReceiver.isSupported()) {
TargetChosenReceiver.sendChooserIntent(
true, activity, shareIntent, null, null);
} else {
Intent chooserIntent = Intent.createChooser(shareIntent,
activity.getString(R.string.share_link_chooser_title));
fireIntent(activity, chooserIntent, false);
}
} else {
shareIntent.setComponent(name);
fireIntent(activity, shareIntent, false);
}
}
== ApplicationState.HAS_DESTROYED_ACTIVITIES) {
return;
}
callback.onResult(imageUri);
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
/**
* Share an image URI with an activity identified by the provided Component Name.
* @param activity The current activity
* @param name The component name of the activity to share the image with.
* @param imageUri The url to share with the external activity.
*/
public static void shareImage(final Activity activity, final ComponentName name, Uri imageUri) {
Intent shareIntent = getShareImageIntent(imageUri);
if (name == null) {
if (TargetChosenReceiver.isSupported()) {
TargetChosenReceiver.sendChooserIntent(true, activity, shareIntent, null, null);
} else {
Intent chooserIntent = Intent.createChooser(
shareIntent, activity.getString(R.string.share_link_chooser_title));
fireIntent(activity, chooserIntent, false);
}
} else {
shareIntent.setComponent(name);
fireIntent(activity, shareIntent, false);
}
}
/**
* Share an image URI with Google Lens.
* @param activity The current activity
* @param imageUri The url to share with the app.
* @param isIncognito Whether the current tab is in incognito mode.
*/
public static void shareImageWithGoogleLens(
final Activity activity, Uri imageUri, boolean isIncognito) {
Intent shareIntent = LensUtils.getShareWithGoogleLensIntent(imageUri, isIncognito);
fireIntent(activity, shareIntent, /* allowIdentification= */ true);
}
private static class ExternallyVisibleUriCallback implements Callback<String> {
private Callback<Uri> mComposedCallback;
ExternallyVisibleUriCallback(Callback<Uri> cb) {
......
......@@ -8,7 +8,9 @@ import android.content.Intent;
import android.net.Uri;
import android.support.test.filters.SmallTest;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -19,6 +21,8 @@ import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.test.ChromeBrowserTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.signin.SigninTestUtil;
import org.chromium.components.signin.ChromeSigninController;
/**
* Tests of {@link LensUtils}.
......@@ -29,6 +33,17 @@ public class LensUtilsTest {
@Rule
public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule();
@Before
public void setUp() throws Exception {
// Account not signed in by default.
SigninTestUtil.setUpAuthForTest();
}
@After
public void tearDown() {
SigninTestUtil.tearDownAuthForTest();
}
/**
* Test {@link LensUtils#isAgsaVersionBelowMinimum()} method if the
* feature is disabled.
......@@ -95,19 +110,86 @@ public class LensUtilsTest {
}
/**
* Test {@link LensUtils#getShareWithGoogleLensIntent()} method.
* Test {@link LensUtils#getShareWithGoogleLensIntent()} method when user is signed in.
*/
@Test
@SmallTest
public void getShareWithGoogleLensIntentTest() {
Intent intentNoUri = LensUtils.getShareWithGoogleLensIntent(Uri.EMPTY);
Assert.assertEquals("googleapp://lens", intentNoUri.getData().toString());
Assert.assertEquals(Intent.ACTION_VIEW, intentNoUri.getAction());
public void getShareWithGoogleLensIntentSignedInTest() {
SigninTestUtil.addAndSignInTestAccount();
Assert.assertEquals("Chrome should be signed into the test account", "test@gmail.com",
ChromeSigninController.get().getSignedInAccountName());
Intent intentNoUri =
LensUtils.getShareWithGoogleLensIntent(Uri.EMPTY, /* isIncognito= */ false);
Assert.assertEquals("Intent without image has incorrect URI", "googleapp://lens",
intentNoUri.getData().toString());
Assert.assertEquals("Intent without image has incorrect action", Intent.ACTION_VIEW,
intentNoUri.getAction());
final String contentUrl = "content://image-url";
Intent intentWithContentUri = LensUtils.getShareWithGoogleLensIntent(
Uri.parse(contentUrl), /* isIncognito= */ false);
Assert.assertEquals("Intent with image has incorrect URI",
"googleapp://lens?LensBitmapUriKey=content%3A%2F%2Fimage-url&AccountNameUriKey="
+ "test%40gmail.com&IncognitoUriKey=false",
intentWithContentUri.getData().toString());
Assert.assertEquals("Intent with image has incorrect action", Intent.ACTION_VIEW,
intentWithContentUri.getAction());
}
/**
* Test {@link LensUtils#getShareWithGoogleLensIntent()} method when user is incognito.
*/
@Test
@SmallTest
public void getShareWithGoogleLensIntentIncognitoTest() {
SigninTestUtil.addAndSignInTestAccount();
Assert.assertEquals("Chrome should be signed into the test account", "test@gmail.com",
ChromeSigninController.get().getSignedInAccountName());
Intent intentNoUri =
LensUtils.getShareWithGoogleLensIntent(Uri.EMPTY, /* isIncognito= */ true);
Assert.assertEquals("Intent without image has incorrect URI", "googleapp://lens",
intentNoUri.getData().toString());
Assert.assertEquals("Intent without image has incorrect action", Intent.ACTION_VIEW,
intentNoUri.getAction());
final String contentUrl = "content://image-url";
Intent intentWithContentUri = LensUtils.getShareWithGoogleLensIntent(
Uri.parse(contentUrl), /* isIncognito= */ true);
// The account name should not be included in the intent because the uesr is incognito.
Assert.assertEquals("Intent with image has incorrect URI",
"googleapp://lens?LensBitmapUriKey=content%3A%2F%2Fimage-url&AccountNameUriKey="
+ "&IncognitoUriKey=true",
intentWithContentUri.getData().toString());
Assert.assertEquals("Intent with image has incorrect action", Intent.ACTION_VIEW,
intentWithContentUri.getAction());
}
/**
* Test {@link LensUtils#getShareWithGoogleLensIntent()} method when user is not signed in.
*/
@Test
@SmallTest
public void getShareWithGoogleLensIntentNotSignedInTest() {
Assert.assertNull("Chrome should not be signed in",
ChromeSigninController.get().getSignedInAccountName());
Intent intentNoUri =
LensUtils.getShareWithGoogleLensIntent(Uri.EMPTY, /* isIncognito= */ false);
Assert.assertEquals("Intent without image has incorrect URI", "googleapp://lens",
intentNoUri.getData().toString());
Assert.assertEquals("Intent without image has incorrect action", Intent.ACTION_VIEW,
intentNoUri.getAction());
final String contentUrl = "content://image-url";
Intent intentWithContentUri = LensUtils.getShareWithGoogleLensIntent(Uri.parse(contentUrl));
Assert.assertEquals("googleapp://lens?LensBitmapUriKey=content%3A%2F%2Fimage-url",
Intent intentWithContentUri = LensUtils.getShareWithGoogleLensIntent(
Uri.parse(contentUrl), /* isIncognito= */ false);
Assert.assertEquals("Intent with image has incorrect URI",
"googleapp://lens?LensBitmapUriKey=content%3A%2F%2Fimage-url&AccountNameUriKey="
+ "&IncognitoUriKey=false",
intentWithContentUri.getData().toString());
Assert.assertEquals(Intent.ACTION_VIEW, intentWithContentUri.getAction());
Assert.assertEquals("Intent with image has incorrect action", Intent.ACTION_VIEW,
intentWithContentUri.getAction());
}
}
\ No newline at end of file
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