Commit 5177798a authored by Mehran Mahmoudi's avatar Mehran Mahmoudi Committed by Chromium LUCI CQ

[CSN] Add UI component to CSN

Bug: 1149972
Change-Id: I9614243851fddcb55402caf5a2d8ea6e1410092f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2582405
Commit-Queue: Mehran Mahmoudi <mahmoudi@chromium.org>
Reviewed-by: default avatarFred Mello <fredmello@chromium.org>
Cr-Commit-Position: refs/heads/master@{#836831}
parent 890d71c5
...@@ -24,7 +24,8 @@ public class ContinuousSearchTabHelper { ...@@ -24,7 +24,8 @@ public class ContinuousSearchTabHelper {
return; return;
} }
ContinuousSearchTabObserver observer = new ContinuousSearchTabObserver(tab); SearchResultUserData.createForTab(tab);
tab.addObserver(observer); SearchResultListCoordinator.createForTab(tab);
new ContinuousSearchTabObserver(tab);
} }
} }
...@@ -140,8 +140,7 @@ public class ContinuousSearchTabHelperTest { ...@@ -140,8 +140,7 @@ public class ContinuousSearchTabHelperTest {
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
Tab tab = mActivityTestRule.getActivity().getActivityTab(); Tab tab = mActivityTestRule.getActivity().getActivityTab();
SearchResultUserData searchResultUserData = SearchResultUserData searchResultUserData = SearchResultUserData.getForTab(tab);
tab.getUserDataHost().getUserData(SearchResultUserData.USER_DATA_KEY);
Assert.assertNotNull(searchResultUserData); Assert.assertNotNull(searchResultUserData);
searchResultUserData.addObserver(observer); searchResultUserData.addObserver(observer);
tab.loadUrl(new LoadUrlParams( tab.loadUrl(new LoadUrlParams(
...@@ -153,8 +152,7 @@ public class ContinuousSearchTabHelperTest { ...@@ -153,8 +152,7 @@ public class ContinuousSearchTabHelperTest {
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
Tab tab = mActivityTestRule.getActivity().getActivityTab(); Tab tab = mActivityTestRule.getActivity().getActivityTab();
SearchResultUserData searchResultUserData = SearchResultUserData searchResultUserData = SearchResultUserData.getForTab(tab);
tab.getUserDataHost().getUserData(SearchResultUserData.USER_DATA_KEY);
Assert.assertTrue(searchResultUserData.isValid()); Assert.assertTrue(searchResultUserData.isValid());
String url = mServer.getURLWithHostName("www.google.com", TEST_URL + "?q=cat+dog"); String url = mServer.getURLWithHostName("www.google.com", TEST_URL + "?q=cat+dog");
Assert.assertTrue(observer.mMetadata.getResultUrl().getSpec().startsWith(url)); Assert.assertTrue(observer.mMetadata.getResultUrl().getSpec().startsWith(url));
......
...@@ -15,6 +15,10 @@ android_library("java") { ...@@ -15,6 +15,10 @@ android_library("java") {
"android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchTabObserver.java", "android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchTabObserver.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResult.java", "android/java/src/org/chromium/chrome/browser/continuous_search/SearchResult.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultGroup.java", "android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultGroup.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultListCoordinator.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultListMediator.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultListProperties.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultListViewBinder.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultListener.java", "android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultListener.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultMetadata.java", "android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultMetadata.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultProducer.java", "android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultProducer.java",
...@@ -25,13 +29,29 @@ android_library("java") { ...@@ -25,13 +29,29 @@ android_library("java") {
] ]
deps = [ deps = [
":java_resources",
"//base:base_java", "//base:base_java",
"//base:jni_java", "//base:jni_java",
"//chrome/browser/tab:java", "//chrome/browser/tab:java",
"//chrome/browser/tabmodel:java", "//chrome/browser/tabmodel:java",
"//content/public/android:content_java",
"//third_party/android_deps:androidx_annotation_annotation_java", "//third_party/android_deps:androidx_annotation_annotation_java",
"//third_party/android_deps:androidx_recyclerview_recyclerview_java",
"//ui/android:ui_full_java",
"//url:gurl_java", "//url:gurl_java",
] ]
resources_package = "org.chromium.chrome.browser.continuous_search"
}
android_resources("java_resources") {
sources = [
"android/java/res/layout/continuous_search_list_ad.xml",
"android/java/res/layout/continuous_search_list_group_label.xml",
"android/java/res/layout/continuous_search_list_result.xml",
]
deps = [ "//chrome/android:chrome_app_java_resources" ]
} }
source_set("internal") { source_set("internal") {
......
<?xml version="1.0" encoding="utf-8"?><!-- 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. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/csn_ad_label"
android:textAppearance="@style/TextAppearance.TextMedium.Disabled" />
<TextView
android:id="@+id/continuous_search_list_item_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAppearance="@style/TextAppearance.TextMedium.Primary" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?><!-- 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. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp">
<TextView
android:id="@+id/continuous_search_list_item_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?><!-- 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. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp">
<TextView
android:id="@+id/continuous_search_list_item_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAppearance="@style/TextAppearance.TextMedium.Primary" />
</FrameLayout>
\ No newline at end of file
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
package org.chromium.chrome.browser.continuous_search; package org.chromium.chrome.browser.continuous_search;
import org.chromium.base.UserDataHost;
import org.chromium.chrome.browser.tab.EmptyTabObserver; import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.url.GURL; import org.chromium.url.GURL;
...@@ -18,15 +17,12 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea ...@@ -18,15 +17,12 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea
public ContinuousSearchTabObserver(Tab tab) { public ContinuousSearchTabObserver(Tab tab) {
mTab = tab; mTab = tab;
UserDataHost host = mTab.getUserDataHost(); mTab.addObserver(this);
assert host.getUserData(SearchResultUserData.USER_DATA_KEY) == null;
SearchResultUserData searchResultUserData = new SearchResultUserData();
host.setUserData(SearchResultUserData.USER_DATA_KEY, searchResultUserData);
} }
@Override @Override
public void onPageLoadFinished(Tab tab, GURL url) { public void onPageLoadFinished(Tab tab, GURL url) {
SearchResultUserData searchResultUserData = getSearchResultUserData(); SearchResultUserData searchResultUserData = SearchResultUserData.getForTab(tab);
searchResultUserData.updateCurrentUrl(url); searchResultUserData.updateCurrentUrl(url);
// Cancel any existing requests. // Cancel any existing requests.
...@@ -35,7 +31,7 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea ...@@ -35,7 +31,7 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea
String query = SearchUrlHelper.getQueryIfSrpUrl(url); String query = SearchUrlHelper.getQueryIfSrpUrl(url);
if (query == null) return; if (query == null) return;
mProducer = SearchResultProducerFactory.create(mTab, this); mProducer = SearchResultProducerFactory.create(tab, this);
// TODO: Remove this once mProducer is always created. // TODO: Remove this once mProducer is always created.
if (mProducer == null) return; if (mProducer == null) return;
...@@ -46,12 +42,12 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea ...@@ -46,12 +42,12 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea
@Override @Override
public void onCloseContents(Tab tab) { public void onCloseContents(Tab tab) {
resetProducer(); resetProducer();
getSearchResultUserData().invalidateData(); SearchResultUserData.getForTab(tab).invalidateData();
} }
@Override @Override
public void onDestroyed(Tab tab) { public void onDestroyed(Tab tab) {
mTab.removeObserver(this); tab.removeObserver(this);
} }
// SearchResultListener // SearchResultListener
...@@ -61,7 +57,7 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea ...@@ -61,7 +57,7 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea
assert metadata != null; assert metadata != null;
mProducer = null; mProducer = null;
getSearchResultUserData().updateData(metadata, mTab.getUrl()); SearchResultUserData.getForTab(mTab).updateData(metadata, mTab.getUrl());
} }
@Override @Override
...@@ -70,15 +66,6 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea ...@@ -70,15 +66,6 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea
mProducer = null; mProducer = null;
} }
private SearchResultUserData getSearchResultUserData() {
UserDataHost host = mTab.getUserDataHost();
SearchResultUserData searchResultUserData =
host.getUserData(SearchResultUserData.USER_DATA_KEY);
assert searchResultUserData != null;
return searchResultUserData;
}
private void resetProducer() { private void resetProducer() {
if (mProducer != null) { if (mProducer != null) {
mProducer.cancel(); mProducer.cancel();
......
// 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.continuous_search;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import org.chromium.base.UserData;
import org.chromium.chrome.browser.continuous_search.SearchResultListProperties.ListItemType;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
/**
* Entry point for the UI component of Continuous Search Navigation.
*/
public class SearchResultListCoordinator implements UserData {
private static final Class<SearchResultListCoordinator> USER_DATA_KEY =
SearchResultListCoordinator.class;
private Tab mTab;
private SearchResultListMediator mMediator;
private RecyclerView mView;
static void createForTab(Tab tab) {
assert tab.getUserDataHost().getUserData(USER_DATA_KEY) == null;
tab.getUserDataHost().setUserData(USER_DATA_KEY, new SearchResultListCoordinator(tab));
}
static SearchResultListCoordinator getForTab(Tab tab) {
return tab.getUserDataHost().getUserData(USER_DATA_KEY);
}
private SearchResultListCoordinator(Tab tab) {
mTab = tab;
mView = new RecyclerView(mTab.getContext());
ModelList listItems = new ModelList();
SimpleRecyclerViewAdapter adapter = new SimpleRecyclerViewAdapter(listItems);
adapter.registerType(ListItemType.GROUP_LABEL,
(parent)
-> inflateItemView(parent, ListItemType.GROUP_LABEL),
SearchResultListViewBinder::bind);
adapter.registerType(ListItemType.SEARCH_RESULT,
(parent)
-> inflateItemView(parent, ListItemType.SEARCH_RESULT),
SearchResultListViewBinder::bind);
adapter.registerType(ListItemType.AD,
(parent)
-> inflateItemView(parent, ListItemType.AD),
SearchResultListViewBinder::bind);
mView.setAdapter(adapter);
mMediator = new SearchResultListMediator(
listItems, (url) -> tab.loadUrl(new LoadUrlParams(url.getSpec())));
SearchResultUserData.getForTab(mTab).addObserver(mMediator);
}
private View inflateItemView(ViewGroup parentView, @ListItemType int listItemType) {
int layoutId = R.layout.continuous_search_list_group_label;
switch (listItemType) {
case ListItemType.SEARCH_RESULT:
layoutId = R.layout.continuous_search_list_result;
break;
case ListItemType.AD:
layoutId = R.layout.continuous_search_list_ad;
break;
}
return LayoutInflater.from(parentView.getContext()).inflate(layoutId, parentView, false);
}
public View getView() {
return mView;
}
@Override
public void destroy() {
SearchResultUserData.getForTab(mTab).removeObserver(mMediator);
}
}
// 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.continuous_search;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.continuous_search.SearchResultListProperties.ListItemType;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.ModelListAdapter;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.url.GURL;
/**
* Business logic for the UI component of Continuous Search Navigation. This class updates the UI on
* search result updates.
*/
public class SearchResultListMediator implements SearchResultUserDataObserver {
private ModelList mModelList;
private Callback<GURL> mUrlClickHandler;
SearchResultListMediator(ModelList modelList, Callback<GURL> urlClickHandler) {
mModelList = modelList;
mUrlClickHandler = urlClickHandler;
}
private void handleResultClick(GURL url) {
if (url == null || mUrlClickHandler == null) return;
mUrlClickHandler.onResult(url);
}
@Override
public void onInvalidate() {
mModelList.clear();
}
@Override
public void onUpdate(SearchResultMetadata metadata, GURL currentUrl) {
mModelList.clear();
for (SearchResultGroup group : metadata.getGroups()) {
if (!group.isAdGroup()) {
mModelList.add(new ModelListAdapter.ListItem(
ListItemType.GROUP_LABEL, generateListItem(group.getLabel(), null)));
}
int itemType = group.isAdGroup() ? ListItemType.AD : ListItemType.SEARCH_RESULT;
for (SearchResult result : group.getResults()) {
mModelList.add(new ListItem(
itemType, generateListItem(result.getTitle(), result.getUrl())));
}
}
}
@Override
public void onUrlChanged(GURL currentUrl) {
for (ListItem listItem : mModelList) {
if (listItem.type == ListItemType.GROUP_LABEL) continue;
boolean isSelected = currentUrl != null
&& currentUrl.equals(listItem.model.get(SearchResultListProperties.URL));
listItem.model.set(SearchResultListProperties.IS_SELECTED, isSelected);
}
}
private PropertyModel generateListItem(String text, GURL url) {
return new PropertyModel.Builder(SearchResultListProperties.ALL_KEYS)
.with(SearchResultListProperties.LABEL, text)
.with(SearchResultListProperties.URL, url)
.with(SearchResultListProperties.IS_SELECTED, false)
.with(SearchResultListProperties.CLICK_LISTENER, (view) -> handleResultClick(url))
.build();
}
}
// 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.continuous_search;
import android.view.View.OnClickListener;
import androidx.annotation.IntDef;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
import org.chromium.url.GURL;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Contains model properties for the UI component of Continuous Search Navigation.
*/
public class SearchResultListProperties {
@IntDef({ListItemType.GROUP_LABEL, ListItemType.SEARCH_RESULT, ListItemType.AD})
@Retention(RetentionPolicy.SOURCE)
public @interface ListItemType {
int GROUP_LABEL = 0;
int SEARCH_RESULT = 1;
int AD = 2;
}
public static final WritableObjectPropertyKey<String> LABEL = new WritableObjectPropertyKey<>();
public static final WritableObjectPropertyKey<GURL> URL = new WritableObjectPropertyKey<>();
public static final WritableBooleanPropertyKey IS_SELECTED = new WritableBooleanPropertyKey();
public static final WritableObjectPropertyKey<OnClickListener> CLICK_LISTENER =
new WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_KEYS = {LABEL, URL, IS_SELECTED, CLICK_LISTENER};
}
// 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.continuous_search;
import android.view.View;
import android.widget.TextView;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
/**
* Responsible for binding the {@link PropertyModel} for a search result item to a View.
*/
public class SearchResultListViewBinder {
public static void bind(PropertyModel model, View view, PropertyKey propertyKey) {
if (SearchResultListProperties.LABEL == propertyKey) {
TextView textView = (TextView) view.findViewById(R.id.continuous_search_list_item_text);
textView.setText(model.get(SearchResultListProperties.LABEL));
} else if (SearchResultListProperties.IS_SELECTED == propertyKey) {
view.setSelected(model.get(SearchResultListProperties.IS_SELECTED));
} else if (SearchResultListProperties.CLICK_LISTENER == propertyKey) {
view.setOnClickListener(model.get(SearchResultListProperties.CLICK_LISTENER));
}
}
}
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package org.chromium.chrome.browser.continuous_search; package org.chromium.chrome.browser.continuous_search;
import org.chromium.base.UserData; import org.chromium.base.UserData;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.url.GURL; import org.chromium.url.GURL;
import java.util.HashSet; import java.util.HashSet;
...@@ -15,7 +16,7 @@ import java.util.HashSet; ...@@ -15,7 +16,7 @@ import java.util.HashSet;
public class SearchResultUserData implements UserData { public class SearchResultUserData implements UserData {
public static final int INVALID_POSITION = -2; public static final int INVALID_POSITION = -2;
public static final int ON_SRP = -1; public static final int ON_SRP = -1;
public static final Class<SearchResultUserData> USER_DATA_KEY = SearchResultUserData.class; private static final Class<SearchResultUserData> USER_DATA_KEY = SearchResultUserData.class;
private SearchResultMetadata mData; private SearchResultMetadata mData;
private HashSet<GURL> mValidUrls; private HashSet<GURL> mValidUrls;
...@@ -23,6 +24,18 @@ public class SearchResultUserData implements UserData { ...@@ -23,6 +24,18 @@ public class SearchResultUserData implements UserData {
private GURL mCurrentUrl; private GURL mCurrentUrl;
private int mCurrentPosition = INVALID_POSITION; private int mCurrentPosition = INVALID_POSITION;
static void createForTab(Tab tab) {
assert tab.getUserDataHost().getUserData(USER_DATA_KEY) == null;
tab.getUserDataHost().setUserData(USER_DATA_KEY, new SearchResultUserData());
}
static SearchResultUserData getForTab(Tab tab) {
assert tab.getUserDataHost().getUserData(USER_DATA_KEY) != null;
return tab.getUserDataHost().getUserData(USER_DATA_KEY);
}
private SearchResultUserData() {}
/** /**
* @return Whether this contains valid data. * @return Whether this contains valid data.
*/ */
......
...@@ -4424,6 +4424,11 @@ Data from your Incognito session will only be cleared from Chrome when you <ph n ...@@ -4424,6 +4424,11 @@ Data from your Incognito session will only be cleared from Chrome when you <ph n
<message name="IDS_AVS_SETTING_ENABLED_DESCRIPTION" desc="Description of the Assistant voice search setting."> <message name="IDS_AVS_SETTING_ENABLED_DESCRIPTION" desc="Description of the Assistant voice search setting.">
Google Assistant provides a better voice experience for searching the web and engaging with sites you have open. Google Assistant will receive the URL and contents of sites you use with it. Google Assistant provides a better voice experience for searching the web and engaging with sites you have open. Google Assistant will receive the URL and contents of sites you use with it.
</message> </message>
<!-- Continuous Search Navigation strings -->
<message name="IDS_CSN_AD_LABEL" desc="Label for identifying an ad in search results." translateable="false">
Ad
</message>
</messages> </messages>
</release> </release>
</grit> </grit>
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