Commit 9bc77d0a authored by Gang Wu's avatar Gang Wu Committed by Commit Bot

Reland "Reland "Fix image search from search activity""

This is a reland of 3288b2d9

Original change's description:
> Reland "Fix image search from search activity"
> 
> This is a reland of 91eda9e8
> 
> Original change's description:
> > Fix image search from search activity
> > 
> > SearchActivity did not handle image search data since image search need
> > post data, so the fix is add the post data into intent to pass to chrome.
> > 
> > Bug: 1078773
> > Change-Id: I04efdad0bba315508e0b05d3d9e444d8c1a5d955
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2187191
> > Commit-Queue: Gang Wu <gangwu@chromium.org>
> > Reviewed-by: Robert Sesek <rsesek@chromium.org>
> > Reviewed-by: Ender <ender@google.com>
> > Reviewed-by: Ted Choc <tedchoc@chromium.org>
> > Cr-Commit-Position: refs/heads/master@{#772335}
> 
> Bug: 1078773
> Change-Id: I86f1fbab1cc27a66ceb5ad8ac987179b8f5e8e68
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2220150
> Reviewed-by: Ted Choc <tedchoc@chromium.org>
> Reviewed-by: Gang Wu <gangwu@chromium.org>
> Commit-Queue: Gang Wu <gangwu@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#774036}

Bug: 1078773
Change-Id: I0084e475067c75db4d89ce3a890694525705c865
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2228983
Commit-Queue: Gang Wu <gangwu@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#775232}
parent 983f291e
...@@ -186,6 +186,17 @@ public class IntentHandler { ...@@ -186,6 +186,17 @@ public class IntentHandler {
*/ */
public static final String EXTRA_INCOGNITO_MODE = "org.chromium.chrome.browser.incognito_mode"; public static final String EXTRA_INCOGNITO_MODE = "org.chromium.chrome.browser.incognito_mode";
/**
* Byte array for the POST data when load a url, only Intents sent by Chrome can use this.
*/
public static final String EXTRA_POST_DATA = "com.android.chrome.post_data";
/**
* The type of the POST data, need to be added to the HTTP request header, only Intents sent by
* Chrome can use this.
*/
public static final String EXTRA_POST_DATA_TYPE = "com.android.chrome.post_data_type";
/** /**
* Fake ComponentName used in constructing TRUSTED_APPLICATION_CODE_EXTRA. * Fake ComponentName used in constructing TRUSTED_APPLICATION_CODE_EXTRA.
*/ */
......
...@@ -46,6 +46,7 @@ import org.chromium.ui.ViewProvider; ...@@ -46,6 +46,7 @@ import org.chromium.ui.ViewProvider;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modelutil.LazyConstructionPropertyMcp; import org.chromium.ui.modelutil.LazyConstructionPropertyMcp;
import org.chromium.ui.modelutil.MVCListAdapter; import org.chromium.ui.modelutil.MVCListAdapter;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -335,6 +336,11 @@ public class AutocompleteCoordinatorImpl implements AutocompleteCoordinator { ...@@ -335,6 +336,11 @@ public class AutocompleteCoordinatorImpl implements AutocompleteCoordinator {
return mMediator; return mMediator;
} }
@VisibleForTesting
ModelList getSuggestionModelList() {
return mMediator.getSuggestionModelList();
}
private void onTileSelected(QueryTile queryTile) { private void onTileSelected(QueryTile queryTile) {
mMediator.onQueryTileSelected(queryTile); mMediator.onQueryTileSelected(queryTile);
} }
......
...@@ -14,6 +14,7 @@ import android.view.LayoutInflater; ...@@ -14,6 +14,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.core.app.ActivityOptionsCompat; import androidx.core.app.ActivityOptionsCompat;
...@@ -102,6 +103,8 @@ public class SearchActivity extends AsyncInitializationActivity ...@@ -102,6 +103,8 @@ public class SearchActivity extends AsyncInitializationActivity
/** Input submitted before before the native library was loaded. */ /** Input submitted before before the native library was loaded. */
private String mQueuedUrl; private String mQueuedUrl;
private String mQueuedPostDataType;
private byte[] mQueuedPostData;
/** The View that represents the search box. */ /** The View that represents the search box. */
private SearchActivityLocationBarLayout mSearchBox; private SearchActivityLocationBarLayout mSearchBox;
...@@ -273,7 +276,7 @@ public class SearchActivity extends AsyncInitializationActivity ...@@ -273,7 +276,7 @@ public class SearchActivity extends AsyncInitializationActivity
assert !mIsActivityUsable assert !mIsActivityUsable
: "finishDeferredInitialization() incorrectly called multiple times"; : "finishDeferredInitialization() incorrectly called multiple times";
mIsActivityUsable = true; mIsActivityUsable = true;
if (mQueuedUrl != null) loadUrl(mQueuedUrl); if (mQueuedUrl != null) loadUrl(mQueuedUrl, mQueuedPostDataType, mQueuedPostData);
// TODO(tedchoc): Warmup triggers the CustomTab layout to be inflated, but this widget // TODO(tedchoc): Warmup triggers the CustomTab layout to be inflated, but this widget
// will navigate to Tabbed mode. Investigate whether this can inflate // will navigate to Tabbed mode. Investigate whether this can inflate
...@@ -327,29 +330,53 @@ public class SearchActivity extends AsyncInitializationActivity ...@@ -327,29 +330,53 @@ public class SearchActivity extends AsyncInitializationActivity
} }
@Override @Override
public void loadUrl(String url) { public void loadUrl(String url, @Nullable String postDataType, @Nullable byte[] postData) {
// Wait until native has loaded. // Wait until native has loaded.
if (!mIsActivityUsable) { if (!mIsActivityUsable) {
mQueuedUrl = url; mQueuedUrl = url;
mQueuedPostDataType = postDataType;
mQueuedPostData = postData;
return; return;
} }
Intent intent = createIntentForStartActivity(url, postDataType, postData);
if (intent == null) return;
IntentUtils.safeStartActivity(this, intent,
ActivityOptionsCompat
.makeCustomAnimation(this, android.R.anim.fade_in, android.R.anim.fade_out)
.toBundle());
RecordUserAction.record("SearchWidget.SearchMade");
finish();
}
/**
* Creates an intent that will be used to launch Chrome.
*
* @param url The URL to be loaded.
* @param postDataType postData type.
* @param postData Post-data to include in the tab URL's request body, ex. bitmap when
* image search.
* @return the intent will be passed to ChromeLauncherActivity, null if input was emprty.
*/
private Intent createIntentForStartActivity(
String url, @Nullable String postDataType, @Nullable byte[] postData) {
// Don't do anything if the input was empty. This is done after the native check to prevent // Don't do anything if the input was empty. This is done after the native check to prevent
// resending a queued query after the user deleted it. // resending a queued query after the user deleted it.
if (TextUtils.isEmpty(url)) return; if (TextUtils.isEmpty(url)) return null;
// Fix up the URL and send it to the full browser. // Fix up the URL and send it to the full browser.
GURL fixedUrl = UrlFormatter.fixupUrl(url); GURL fixedUrl = UrlFormatter.fixupUrl(url);
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(fixedUrl.getValidSpecOrEmpty())); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(fixedUrl.getValidSpecOrEmpty()));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
intent.setClass(this, ChromeLauncherActivity.class); intent.setClass(this, ChromeLauncherActivity.class);
if (!TextUtils.isEmpty(postDataType) && postData != null && postData.length != 0) {
intent.putExtra(IntentHandler.EXTRA_POST_DATA_TYPE, postDataType);
intent.putExtra(IntentHandler.EXTRA_POST_DATA, postData);
}
IntentHandler.addTrustedIntentExtras(intent); IntentHandler.addTrustedIntentExtras(intent);
IntentUtils.safeStartActivity(this, intent,
ActivityOptionsCompat return intent;
.makeCustomAnimation(this, android.R.anim.fade_in, android.R.anim.fade_out)
.toBundle());
RecordUserAction.record("SearchWidget.SearchMade");
finish();
} }
private ViewGroup createContentView() { private ViewGroup createContentView() {
......
...@@ -28,7 +28,7 @@ public class SearchActivityLocationBarLayout extends LocationBarLayout { ...@@ -28,7 +28,7 @@ public class SearchActivityLocationBarLayout extends LocationBarLayout {
/** Delegates calls out to the containing Activity. */ /** Delegates calls out to the containing Activity. */
public static interface Delegate { public static interface Delegate {
/** Load a URL in the associated tab. */ /** Load a URL in the associated tab. */
void loadUrl(String url); void loadUrl(String url, @Nullable String postDataType, @Nullable byte[] postData);
/** The user hit the back button. */ /** The user hit the back button. */
void backKeyPressed(); void backKeyPressed();
...@@ -56,8 +56,9 @@ public class SearchActivityLocationBarLayout extends LocationBarLayout { ...@@ -56,8 +56,9 @@ public class SearchActivityLocationBarLayout extends LocationBarLayout {
} }
@Override @Override
public void loadUrl(String url, int transition, long inputStart) { public void loadUrlWithPostData(String url, int transition, long inputStart,
mDelegate.loadUrl(url); @Nullable String postDataType, @Nullable byte[] postData) {
mDelegate.loadUrl(url, postDataType, postData);
LocaleManager.getInstance().recordLocaleBasedSearchMetrics(true, url, transition); LocaleManager.getInstance().recordLocaleBasedSearchMetrics(true, url, transition);
} }
......
...@@ -33,6 +33,7 @@ import org.chromium.components.url_formatter.UrlFormatter; ...@@ -33,6 +33,7 @@ import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.common.Referrer; import org.chromium.content_public.common.Referrer;
import org.chromium.content_public.common.ResourceRequestBody;
import org.chromium.ui.base.PageTransition; import org.chromium.ui.base.PageTransition;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
import org.chromium.url.GURL; import org.chromium.url.GURL;
...@@ -298,8 +299,9 @@ public class ChromeTabCreator extends TabCreatorManager.TabCreator { ...@@ -298,8 +299,9 @@ public class ChromeTabCreator extends TabCreatorManager.TabCreator {
* @param intentTimestamp the time the intent was received. * @param intentTimestamp the time the intent was received.
* @return the tab the URL was opened in, could be a new tab or a reused one. * @return the tab the URL was opened in, could be a new tab or a reused one.
*/ */
public Tab launchUrlFromExternalApp(String url, String referer, String headers, // TODO(crbug.com/1081924): Clean up the launches from SearchActivity/Chrome.
String appId, boolean forceNewTab, Intent intent, long intentTimestamp) { public Tab launchUrlFromExternalApp(String url, String referer, String headers, String appId,
boolean forceNewTab, Intent intent, long intentTimestamp) {
assert !mIncognito; assert !mIncognito;
boolean isLaunchedFromChrome = TextUtils.equals(appId, mActivity.getPackageName()); boolean isLaunchedFromChrome = TextUtils.equals(appId, mActivity.getPackageName());
...@@ -309,11 +311,30 @@ public class ChromeTabCreator extends TabCreatorManager.TabCreator { ...@@ -309,11 +311,30 @@ public class ChromeTabCreator extends TabCreatorManager.TabCreator {
// reused either. // reused either.
LoadUrlParams loadUrlParams = new LoadUrlParams(url); LoadUrlParams loadUrlParams = new LoadUrlParams(url);
loadUrlParams.setIntentReceivedTimestamp(intentTimestamp); loadUrlParams.setIntentReceivedTimestamp(intentTimestamp);
loadUrlParams.setVerbatimHeaders(headers);
if (referer != null) { if (referer != null) {
loadUrlParams.setReferrer( loadUrlParams.setReferrer(
new Referrer(referer, IntentHandler.getReferrerPolicyFromIntent(intent))); new Referrer(referer, IntentHandler.getReferrerPolicyFromIntent(intent)));
} }
// Handle post data case.
if (IntentHandler.wasIntentSenderChrome(intent)) {
String postDataType =
IntentUtils.safeGetStringExtra(intent, IntentHandler.EXTRA_POST_DATA_TYPE);
byte[] postData =
IntentUtils.safeGetByteArrayExtra(intent, IntentHandler.EXTRA_POST_DATA);
if (!TextUtils.isEmpty(postDataType) && postData != null && postData.length != 0) {
StringBuilder appendToHeader = new StringBuilder();
appendToHeader.append("Content-Type: ");
appendToHeader.append(postDataType);
if (TextUtils.isEmpty(headers)) {
headers = appendToHeader.toString();
} else {
headers = headers + "\r\n" + appendToHeader.toString();
}
loadUrlParams.setPostData(ResourceRequestBody.createFromBytes(postData));
}
}
loadUrlParams.setVerbatimHeaders(headers);
return createNewTab(loadUrlParams, TabLaunchType.FROM_EXTERNAL_APP, null, intent); return createNewTab(loadUrlParams, TabLaunchType.FROM_EXTERNAL_APP, null, intent);
} }
......
...@@ -2,6 +2,7 @@ include_rules = [ ...@@ -2,6 +2,7 @@ include_rules = [
"+chrome/app", "+chrome/app",
"+chrome/browser/android/lifecycle", "+chrome/browser/android/lifecycle",
"+chrome/browser/profiles/android/java", "+chrome/browser/profiles/android/java",
"+chrome/browser/share/android/java",
"+chrome/browser/tab/java", "+chrome/browser/tab/java",
"+chrome/browser/thumbnail/generator/android/java", "+chrome/browser/thumbnail/generator/android/java",
"+chrome/browser/ui/android/appmenu", "+chrome/browser/ui/android/appmenu",
......
...@@ -12,11 +12,18 @@ import android.annotation.SuppressLint; ...@@ -12,11 +12,18 @@ import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.Instrumentation; import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor; import android.app.Instrumentation.ActivityMonitor;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.core.content.FileProvider;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
...@@ -29,6 +36,9 @@ import org.mockito.MockitoAnnotations; ...@@ -29,6 +36,9 @@ import org.mockito.MockitoAnnotations;
import org.chromium.base.ApplicationStatus; import org.chromium.base.ApplicationStatus;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.base.ContentUriUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.IntentUtils;
import org.chromium.base.test.params.ParameterAnnotations; import org.chromium.base.test.params.ParameterAnnotations;
import org.chromium.base.test.params.ParameterSet; import org.chromium.base.test.params.ParameterSet;
import org.chromium.base.test.params.ParameterizedRunner; import org.chromium.base.test.params.ParameterizedRunner;
...@@ -37,6 +47,8 @@ import org.chromium.base.test.util.CommandLineFlags; ...@@ -37,6 +47,8 @@ import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.FlakyTest; import org.chromium.base.test.util.FlakyTest;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeTabbedActivity; import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.locale.DefaultSearchEngineDialogHelperUtils; import org.chromium.chrome.browser.locale.DefaultSearchEngineDialogHelperUtils;
...@@ -45,14 +57,19 @@ import org.chromium.chrome.browser.locale.DefaultSearchEnginePromoDialog.Default ...@@ -45,14 +57,19 @@ import org.chromium.chrome.browser.locale.DefaultSearchEnginePromoDialog.Default
import org.chromium.chrome.browser.locale.LocaleManager; import org.chromium.chrome.browser.locale.LocaleManager;
import org.chromium.chrome.browser.omnibox.OmniboxSuggestionType; import org.chromium.chrome.browser.omnibox.OmniboxSuggestionType;
import org.chromium.chrome.browser.omnibox.UrlBar; import org.chromium.chrome.browser.omnibox.UrlBar;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinatorTestUtils;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteResult; import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteResult;
import org.chromium.chrome.browser.omnibox.suggestions.CachedZeroSuggestionsManager; import org.chromium.chrome.browser.omnibox.suggestions.CachedZeroSuggestionsManager;
import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestion; import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestion;
import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionBuilderForTest; import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionBuilderForTest;
import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsDropdown;
import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler; import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.chrome.browser.searchwidget.SearchActivity.SearchActivityDelegate; import org.chromium.chrome.browser.searchwidget.SearchActivity.SearchActivityDelegate;
import org.chromium.chrome.browser.share.clipboard.ClipboardImageFileProvider;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate; import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.chrome.test.MultiActivityTestRule; import org.chromium.chrome.test.MultiActivityTestRule;
import org.chromium.chrome.test.util.ActivityUtils; import org.chromium.chrome.test.util.ActivityUtils;
...@@ -63,9 +80,13 @@ import org.chromium.content_public.browser.test.util.Criteria; ...@@ -63,9 +80,13 @@ import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper; import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.KeyUtils; import org.chromium.content_public.browser.test.util.KeyUtils;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.content_public.browser.test.util.TestTouchUtils;
import org.chromium.content_public.common.ContentUrlConstants; import org.chromium.content_public.common.ContentUrlConstants;
import org.chromium.ui.base.Clipboard;
import org.chromium.url.GURL; import org.chromium.url.GURL;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -88,6 +109,7 @@ import java.util.concurrent.TimeoutException; ...@@ -88,6 +109,7 @@ import java.util.concurrent.TimeoutException;
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class SearchActivityTest { public class SearchActivityTest {
private static final long OMNIBOX_SHOW_TIMEOUT_MS = 5000L; private static final long OMNIBOX_SHOW_TIMEOUT_MS = 5000L;
private static final String TEST_PNG_IMAGE_FILE_EXTENSION = ".png";
@ParameterAnnotations.ClassParameter @ParameterAnnotations.ClassParameter
private static List<ParameterSet> sClassParams = private static List<ParameterSet> sClassParams =
...@@ -156,9 +178,25 @@ public class SearchActivityTest { ...@@ -156,9 +178,25 @@ public class SearchActivityTest {
} }
} }
// Helper class for clipboard Omnibox test.
private class FileProviderHelper implements ContentUriUtils.FileProviderUtil {
private static final String API_AUTHORITY_SUFFIX = ".FileProvider";
@Override
public Uri getContentUriFromFile(File file) {
Context appContext = ContextUtils.getApplicationContext();
return FileProvider.getUriForFile(
appContext, appContext.getPackageName() + API_AUTHORITY_SUFFIX, file);
}
}
@Rule @Rule
public MultiActivityTestRule mTestRule = new MultiActivityTestRule(); public MultiActivityTestRule mTestRule = new MultiActivityTestRule();
@Rule
public ChromeActivityTestRule<ChromeTabbedActivity> mActivityTestRule =
new ChromeActivityTestRule<>(ChromeTabbedActivity.class);
@Mock @Mock
VoiceRecognitionHandler mHandler; VoiceRecognitionHandler mHandler;
...@@ -538,6 +576,182 @@ public class SearchActivityTest { ...@@ -538,6 +576,182 @@ public class SearchActivityTest {
}); });
} }
@Test
@SmallTest
@Features.
EnableFeatures({ChromeFeatureList.OMNIBOX_ENABLE_CLIPBOARD_PROVIDER_IMAGE_SUGGESTIONS})
public void testImageSearch() throws InterruptedException, Exception {
// Put an image into system clipboard.
putAnImageIntoClipboard();
// Start the Activity.
final SearchActivity searchActivity = startSearchActivity();
// Omnibox suggestions should appear now.
final SearchActivityLocationBarLayout locationBar =
(SearchActivityLocationBarLayout) searchActivity.findViewById(
R.id.search_location_bar);
OmniboxTestUtils.waitForOmniboxSuggestions(locationBar, OMNIBOX_SHOW_TIMEOUT_MS);
waitForSuggestionType(locationBar, OmniboxSuggestionType.CLIPBOARD_IMAGE);
OmniboxSuggestionsDropdown suggestionsDropdown =
AutocompleteCoordinatorTestUtils.getSuggestionsDropdown(
locationBar.getAutocompleteCoordinator());
int imageSuggestionIndex = -1;
// Find the index of the image clipboard suggestion.
for (int i = 0; i < suggestionsDropdown.getItemCount(); ++i) {
OmniboxSuggestion suggestion = AutocompleteCoordinatorTestUtils.getOmniboxSuggestionAt(
locationBar.getAutocompleteCoordinator(), i);
if (suggestion != null
&& suggestion.getType() == OmniboxSuggestionType.CLIPBOARD_IMAGE) {
imageSuggestionIndex = i;
break;
}
}
Assert.assertNotEquals(
"Cannot find the image clipboard Omnibox suggestion", -1, imageSuggestionIndex);
OmniboxSuggestion imageSuggestion = AutocompleteCoordinatorTestUtils.getOmniboxSuggestionAt(
locationBar.getAutocompleteCoordinator(), imageSuggestionIndex);
Assert.assertNotNull("The image clipboard suggestion should contains post content type.",
imageSuggestion.getPostContentType());
Assert.assertNotEquals(
"The image clipboard suggestion should not contains am empty post content type.", 0,
imageSuggestion.getPostContentType().length());
Assert.assertNotNull("The image clipboard suggestion should contains post data.",
imageSuggestion.getPostData());
Assert.assertNotEquals(
"The image clipboard suggestion should not contains am empty post data.", 0,
imageSuggestion.getPostData().length);
// Find the index of clipboard suggestion in the dropdown list.
final int clipboardSuggestionIndexInDropdown =
AutocompleteCoordinatorTestUtils.getIndexForFirstSuggestionOfType(
locationBar.getAutocompleteCoordinator(),
OmniboxSuggestionUiType.CLIPBOARD_SUGGESTION);
Assert.assertNotEquals("Cannot find the image clipboard Omnibox suggestion in UI.", -1,
clipboardSuggestionIndexInDropdown);
// Make sure the new tab is launched.
final ChromeTabbedActivity cta = ActivityUtils.waitForActivity(
InstrumentationRegistry.getInstrumentation(), ChromeTabbedActivity.class,
new Callable<Void>() {
@Override
public Void call() throws InterruptedException {
clickSuggestionAt(suggestionsDropdown, clipboardSuggestionIndexInDropdown);
return null;
}
});
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
Tab tab = cta.getActivityTab();
if (tab == null) return false;
// Make sure tab is in either upload page or result page. cannot only verify one of
// them since on fast device tab jump to result page really quick but on slow device
// may stay on upload page for a really long time.
return tab.getUrlString().equals(imageSuggestion.getUrl().getSpec())
|| TemplateUrlServiceFactory.get()
.isSearchResultsPageFromDefaultSearchProvider(
tab.getUrlString());
}
});
}
@Test
@SmallTest
@Features.
EnableFeatures({ChromeFeatureList.OMNIBOX_ENABLE_CLIPBOARD_PROVIDER_IMAGE_SUGGESTIONS})
public void testImageSearch_OnlyTrustedIntentCanPost() throws InterruptedException, Exception {
// Put an image into system clipboard.
putAnImageIntoClipboard();
// Start the Activity.
final SearchActivity searchActivity = startSearchActivity();
// Omnibox suggestions should appear now.
final SearchActivityLocationBarLayout locationBar =
(SearchActivityLocationBarLayout) searchActivity.findViewById(
R.id.search_location_bar);
OmniboxTestUtils.waitForOmniboxSuggestions(locationBar, OMNIBOX_SHOW_TIMEOUT_MS);
waitForSuggestionType(locationBar, OmniboxSuggestionType.CLIPBOARD_IMAGE);
OmniboxSuggestionsDropdown suggestionsDropdown =
AutocompleteCoordinatorTestUtils.getSuggestionsDropdown(
locationBar.getAutocompleteCoordinator());
int imageSuggestionIndex = -1;
// Find the index of the image clipboard suggestion.
for (int i = 0; i < suggestionsDropdown.getItemCount(); ++i) {
OmniboxSuggestion suggestion = AutocompleteCoordinatorTestUtils.getOmniboxSuggestionAt(
locationBar.getAutocompleteCoordinator(), i);
if (suggestion != null
&& suggestion.getType() == OmniboxSuggestionType.CLIPBOARD_IMAGE) {
imageSuggestionIndex = i;
break;
}
}
Assert.assertNotEquals(
"Cannot find the image clipboard Omnibox suggestion", -1, imageSuggestionIndex);
OmniboxSuggestion imageSuggestion = AutocompleteCoordinatorTestUtils.getOmniboxSuggestionAt(
locationBar.getAutocompleteCoordinator(), imageSuggestionIndex);
Intent intent =
new Intent(Intent.ACTION_VIEW, Uri.parse(imageSuggestion.getUrl().getSpec()));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
intent.setClass(searchActivity, ChromeLauncherActivity.class);
intent.putExtra(IntentHandler.EXTRA_POST_DATA_TYPE, imageSuggestion.getPostContentType());
intent.putExtra(IntentHandler.EXTRA_POST_DATA, imageSuggestion.getPostData());
final ChromeTabbedActivity cta =
ActivityUtils.waitForActivity(InstrumentationRegistry.getInstrumentation(),
ChromeTabbedActivity.class, new Callable<Void>() {
@Override
public Void call() {
IntentUtils.safeStartActivity(searchActivity, intent);
return null;
}
});
// Because no POST data, Google wont go to the result page.
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
Tab tab = cta.getActivityTab();
return !TemplateUrlServiceFactory.get()
.isSearchResultsPageFromDefaultSearchProvider(tab.getUrlString());
}
});
}
private void putAnImageIntoClipboard() {
mActivityTestRule.startMainActivityFromLauncher();
ContentUriUtils.setFileProviderUtil(new FileProviderHelper());
Bitmap bitmap =
Bitmap.createBitmap(/* width = */ 10, /* height = */ 10, Bitmap.Config.ARGB_8888);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, /*quality = (0-100) */ 100, baos);
byte[] mTestImageData = baos.toByteArray();
Clipboard.getInstance().setImageFileProvider(new ClipboardImageFileProvider());
Clipboard.getInstance().setImage(mTestImageData, TEST_PNG_IMAGE_FILE_EXTENSION);
CriteriaHelper.pollInstrumentationThread(new Criteria() {
@Override
public boolean isSatisfied() {
return Clipboard.getInstance().getImage() != null;
}
});
}
private void clickSuggestionAt(OmniboxSuggestionsDropdown suggestionsDropdown, int index)
throws InterruptedException {
// Wait a bit since the button may not able to click.
ViewGroup viewGroup = suggestionsDropdown.getViewGroup();
View view = viewGroup.getChildAt(index);
TestTouchUtils.singleClickView(InstrumentationRegistry.getInstrumentation(), view);
}
private SearchActivity startSearchActivity() { private SearchActivity startSearchActivity() {
return startSearchActivity(0, /*isVoiceSearch=*/false); return startSearchActivity(0, /*isVoiceSearch=*/false);
} }
...@@ -583,6 +797,29 @@ public class SearchActivityTest { ...@@ -583,6 +797,29 @@ public class SearchActivityTest {
})); }));
} }
private void waitForSuggestionType(final SearchActivityLocationBarLayout locationBar,
final @OmniboxSuggestionType int type) {
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
OmniboxSuggestionsDropdown suggestionsDropdown =
AutocompleteCoordinatorTestUtils.getSuggestionsDropdown(
locationBar.getAutocompleteCoordinator());
if (suggestionsDropdown == null) return false;
for (int i = 0; i < suggestionsDropdown.getItemCount(); i++) {
OmniboxSuggestion suggestion =
AutocompleteCoordinatorTestUtils.getOmniboxSuggestionAt(
locationBar.getAutocompleteCoordinator(), i);
if (suggestion != null && suggestion.getType() == type) {
return true;
}
}
return false;
}
});
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private void setUrlBarText(final Activity activity, final String url) { private void setUrlBarText(final Activity activity, final String url) {
CriteriaHelper.pollUiThread(new Criteria() { CriteriaHelper.pollUiThread(new Criteria() {
......
...@@ -330,6 +330,8 @@ public abstract class ChromeFeatureList { ...@@ -330,6 +330,8 @@ public abstract class ChromeFeatureList {
public static final String OMNIBOX_ASSISTANT_VOICE_SEARCH = "OmniboxAssistantVoiceSearch"; public static final String OMNIBOX_ASSISTANT_VOICE_SEARCH = "OmniboxAssistantVoiceSearch";
public static final String OMNIBOX_COMPACT_SUGGESTIONS = "OmniboxCompactSuggestions"; public static final String OMNIBOX_COMPACT_SUGGESTIONS = "OmniboxCompactSuggestions";
public static final String OMNIBOX_DEFERRED_KEYBOARD_POPUP = "OmniboxDeferredKeyboardPopup"; public static final String OMNIBOX_DEFERRED_KEYBOARD_POPUP = "OmniboxDeferredKeyboardPopup";
public static final String OMNIBOX_ENABLE_CLIPBOARD_PROVIDER_IMAGE_SUGGESTIONS =
"OmniboxEnableClipboardProviderImageSuggestions";
public static final String OMNIBOX_HIDE_SCHEME_IN_STEADY_STATE = public static final String OMNIBOX_HIDE_SCHEME_IN_STEADY_STATE =
"OmniboxUIExperimentHideSteadyStateUrlScheme"; "OmniboxUIExperimentHideSteadyStateUrlScheme";
public static final String OMNIBOX_HIDE_TRIVIAL_SUBDOMAINS_IN_STEADY_STATE = public static final String OMNIBOX_HIDE_TRIVIAL_SUBDOMAINS_IN_STEADY_STATE =
......
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
package org.chromium.chrome.browser.omnibox.suggestions; package org.chromium.chrome.browser.omnibox.suggestions;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteMediator.DropdownItemViewInfo;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
/** /**
* Utility methods providing access to package-private methods in {@link AutocompleteCoordinator} * Utility methods providing access to package-private methods in {@link AutocompleteCoordinator}
* for tests. * for tests.
...@@ -34,4 +37,28 @@ public class AutocompleteCoordinatorTestUtils { ...@@ -34,4 +37,28 @@ public class AutocompleteCoordinatorTestUtils {
AutocompleteCoordinator coordinator) { AutocompleteCoordinator coordinator) {
return ((AutocompleteCoordinatorImpl) coordinator).getSuggestionsDropdown(); return ((AutocompleteCoordinatorImpl) coordinator).getSuggestionsDropdown();
} }
/**
* @return The {@link OmniboxSuggestion} at the specified index.
*/
public static OmniboxSuggestion getOmniboxSuggestionAt(
AutocompleteCoordinator coordinator, int index) {
return coordinator.getSuggestionAt(index);
}
/**
* @return The index of the first suggestion which is |type|.
*/
public static int getIndexForFirstSuggestionOfType(
AutocompleteCoordinator coordinator, @OmniboxSuggestionUiType int type) {
ModelList currentModels =
((AutocompleteCoordinatorImpl) coordinator).getSuggestionModelList();
for (int i = 0; i < currentModels.size(); i++) {
DropdownItemViewInfo info = (DropdownItemViewInfo) currentModels.get(i);
if (info.type == type) {
return i;
}
}
return -1;
}
} }
include_rules = [ include_rules = [
"+components/dom_distiller/core/android", "+components/dom_distiller/core/android",
"+content/public/android", "+content/public/android",
"+content/public/test/android/javatests",
"+ui/android", "+ui/android",
] ]
...@@ -20,6 +20,7 @@ import android.provider.MediaStore; ...@@ -20,6 +20,7 @@ import android.provider.MediaStore;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import androidx.core.util.ObjectsCompat;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
...@@ -34,6 +35,8 @@ import org.chromium.base.test.BaseJUnit4ClassRunner; ...@@ -34,6 +35,8 @@ import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.CallbackHelper; import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.DisableIf; import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DisabledTest; import org.chromium.base.test.util.DisabledTest;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.ui.base.Clipboard; import org.chromium.ui.base.Clipboard;
import org.chromium.ui.test.util.DummyUiActivityTestCase; import org.chromium.ui.test.util.DummyUiActivityTestCase;
...@@ -132,6 +135,13 @@ public class ShareImageFileUtilsTest extends DummyUiActivityTestCase { ...@@ -132,6 +135,13 @@ public class ShareImageFileUtilsTest extends DummyUiActivityTestCase {
getActivity(), TEST_IMAGE_DATA, fileExtension, imageCallback); getActivity(), TEST_IMAGE_DATA, fileExtension, imageCallback);
imageCallback.waitForCallback(0, 1, WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS); imageCallback.waitForCallback(0, 1, WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
Clipboard.getInstance().setImageUri(imageCallback.getImageUri()); Clipboard.getInstance().setImageUri(imageCallback.getImageUri());
CriteriaHelper.pollInstrumentationThread(new Criteria() {
@Override
public boolean isSatisfied() {
return ObjectsCompat.equals(
Clipboard.getInstance().getImageUri(), imageCallback.getImageUri());
}
});
return imageCallback.getImageUri(); return imageCallback.getImageUri();
} }
......
...@@ -36,6 +36,7 @@ import org.chromium.base.annotations.JNINamespace; ...@@ -36,6 +36,7 @@ import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods; import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.compat.ApiHelperForO; import org.chromium.base.compat.ApiHelperForO;
import org.chromium.base.metrics.RecordUserAction; import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.task.AsyncTask;
import org.chromium.ui.R; import org.chromium.ui.R;
import org.chromium.ui.widget.Toast; import org.chromium.ui.widget.Toast;
...@@ -277,6 +278,8 @@ public class Clipboard implements ClipboardManager.OnPrimaryClipChangedListener ...@@ -277,6 +278,8 @@ public class Clipboard implements ClipboardManager.OnPrimaryClipChangedListener
/** /**
* Setting the clipboard's current primary clip to an image. * Setting the clipboard's current primary clip to an image.
* This method requires background work and might not be immediately committed upon returning
* from this method.
* @param Uri The {@link Uri} will become the content of the clipboard's primary clip. * @param Uri The {@link Uri} will become the content of the clipboard's primary clip.
*/ */
public void setImageUri(final Uri uri) { public void setImageUri(final Uri uri) {
...@@ -287,9 +290,19 @@ public class Clipboard implements ClipboardManager.OnPrimaryClipChangedListener ...@@ -287,9 +290,19 @@ public class Clipboard implements ClipboardManager.OnPrimaryClipChangedListener
grantUriPermission(uri); grantUriPermission(uri);
ClipData clip = ClipData.newUri( // ClipData.newUri may access the disk (for reading mime types), and cause
ContextUtils.getApplicationContext().getContentResolver(), "image", uri); // StrictModeDiskReadViolation if do it on UI thread.
setPrimaryClipNoException(clip); new AsyncTask<ClipData>() {
@Override
protected ClipData doInBackground() {
return ClipData.newUri(
ContextUtils.getApplicationContext().getContentResolver(), "image", uri);
}
@Override
protected void onPostExecute(ClipData clipData) {
setPrimaryClipNoException(clipData);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
/** /**
......
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