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 {
return;
}
ContinuousSearchTabObserver observer = new ContinuousSearchTabObserver(tab);
tab.addObserver(observer);
SearchResultUserData.createForTab(tab);
SearchResultListCoordinator.createForTab(tab);
new ContinuousSearchTabObserver(tab);
}
}
......@@ -140,8 +140,7 @@ public class ContinuousSearchTabHelperTest {
TestThreadUtils.runOnUiThreadBlocking(() -> {
Tab tab = mActivityTestRule.getActivity().getActivityTab();
SearchResultUserData searchResultUserData =
tab.getUserDataHost().getUserData(SearchResultUserData.USER_DATA_KEY);
SearchResultUserData searchResultUserData = SearchResultUserData.getForTab(tab);
Assert.assertNotNull(searchResultUserData);
searchResultUserData.addObserver(observer);
tab.loadUrl(new LoadUrlParams(
......@@ -153,8 +152,7 @@ public class ContinuousSearchTabHelperTest {
TestThreadUtils.runOnUiThreadBlocking(() -> {
Tab tab = mActivityTestRule.getActivity().getActivityTab();
SearchResultUserData searchResultUserData =
tab.getUserDataHost().getUserData(SearchResultUserData.USER_DATA_KEY);
SearchResultUserData searchResultUserData = SearchResultUserData.getForTab(tab);
Assert.assertTrue(searchResultUserData.isValid());
String url = mServer.getURLWithHostName("www.google.com", TEST_URL + "?q=cat+dog");
Assert.assertTrue(observer.mMetadata.getResultUrl().getSpec().startsWith(url));
......
......@@ -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/SearchResult.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/SearchResultMetadata.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultProducer.java",
......@@ -25,13 +29,29 @@ android_library("java") {
]
deps = [
":java_resources",
"//base:base_java",
"//base:jni_java",
"//chrome/browser/tab:java",
"//chrome/browser/tabmodel:java",
"//content/public/android:content_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",
]
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") {
......
<?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 @@
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.Tab;
import org.chromium.url.GURL;
......@@ -18,15 +17,12 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea
public ContinuousSearchTabObserver(Tab tab) {
mTab = tab;
UserDataHost host = mTab.getUserDataHost();
assert host.getUserData(SearchResultUserData.USER_DATA_KEY) == null;
SearchResultUserData searchResultUserData = new SearchResultUserData();
host.setUserData(SearchResultUserData.USER_DATA_KEY, searchResultUserData);
mTab.addObserver(this);
}
@Override
public void onPageLoadFinished(Tab tab, GURL url) {
SearchResultUserData searchResultUserData = getSearchResultUserData();
SearchResultUserData searchResultUserData = SearchResultUserData.getForTab(tab);
searchResultUserData.updateCurrentUrl(url);
// Cancel any existing requests.
......@@ -35,7 +31,7 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea
String query = SearchUrlHelper.getQueryIfSrpUrl(url);
if (query == null) return;
mProducer = SearchResultProducerFactory.create(mTab, this);
mProducer = SearchResultProducerFactory.create(tab, this);
// TODO: Remove this once mProducer is always created.
if (mProducer == null) return;
......@@ -46,12 +42,12 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea
@Override
public void onCloseContents(Tab tab) {
resetProducer();
getSearchResultUserData().invalidateData();
SearchResultUserData.getForTab(tab).invalidateData();
}
@Override
public void onDestroyed(Tab tab) {
mTab.removeObserver(this);
tab.removeObserver(this);
}
// SearchResultListener
......@@ -61,7 +57,7 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea
assert metadata != null;
mProducer = null;
getSearchResultUserData().updateData(metadata, mTab.getUrl());
SearchResultUserData.getForTab(mTab).updateData(metadata, mTab.getUrl());
}
@Override
......@@ -70,15 +66,6 @@ public class ContinuousSearchTabObserver extends EmptyTabObserver implements Sea
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() {
if (mProducer != null) {
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 @@
package org.chromium.chrome.browser.continuous_search;
import org.chromium.base.UserData;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.url.GURL;
import java.util.HashSet;
......@@ -15,7 +16,7 @@ import java.util.HashSet;
public class SearchResultUserData implements UserData {
public static final int INVALID_POSITION = -2;
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 HashSet<GURL> mValidUrls;
......@@ -23,6 +24,18 @@ public class SearchResultUserData implements UserData {
private GURL mCurrentUrl;
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.
*/
......
......@@ -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.">
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>
<!-- Continuous Search Navigation strings -->
<message name="IDS_CSN_AD_LABEL" desc="Label for identifying an ad in search results." translateable="false">
Ad
</message>
</messages>
</release>
</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