--- /dev/null
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.settings.search2;
+
+import android.support.v7.util.DiffUtil;
+
+import java.util.List;
+
+/**
+ * Callback for DiffUtil to elegantly update search data when the query changes.
+ */
+public class SearchResultDiffCallback extends DiffUtil.Callback {
+
+ private List<SearchResult> mOldList;
+ private List<SearchResult> mNewList;
+
+ public SearchResultDiffCallback(List<SearchResult> oldList, List<SearchResult> newList) {
+ mOldList = oldList;
+ mNewList = newList;
+ }
+
+ @Override
+ public int getOldListSize() {
+ return mOldList.size();
+ }
+
+ @Override
+ public int getNewListSize() {
+ return mNewList.size();
+ }
+
+ @Override
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+ return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition));
+ }
+
+ @Override
+ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+ return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition));
+ }
+}
import android.content.Context;
import android.support.annotation.MainThread;
import android.support.annotation.VisibleForTesting;
+import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.util.ArrayMap;
import android.view.LayoutInflater;
public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder> {
- private final List<SearchResult> mSearchResults;
private final SearchFragment mFragment;
+
+ private List<SearchResult> mSearchResults;
private Map<String, List<? extends SearchResult>> mResultsMap;
private final SearchFeatureProvider mSearchFeatureProvider;
.get(InstalledAppResultLoader.class.getName());
final int dbSize = (databaseResults != null) ? databaseResults.size() : 0;
final int appSize = (installedAppResults != null) ? installedAppResults.size() : 0;
- final List<SearchResult> results = new ArrayList<>(dbSize + appSize);
+ final List<SearchResult> newResults = new ArrayList<>(dbSize + appSize);
int dbIndex = 0;
int appIndex = 0;
while (rank <= BOTTOM_RANK) {
while ((dbIndex < dbSize) && (databaseResults.get(dbIndex).rank == rank)) {
- results.add(databaseResults.get(dbIndex++));
+ newResults.add(databaseResults.get(dbIndex++));
}
while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) {
- results.add(installedAppResults.get(appIndex++));
+ newResults.add(installedAppResults.get(appIndex++));
}
rank++;
}
while (dbIndex < dbSize) {
- results.add(databaseResults.get(dbIndex++));
+ newResults.add(databaseResults.get(dbIndex++));
}
while (appIndex < appSize) {
- results.add(installedAppResults.get(appIndex++));
+ newResults.add(installedAppResults.get(appIndex++));
}
- if (mSearchFeatureProvider
- .isSmartSearchRankingEnabled(mFragment.getContext().getApplicationContext())) {
+ final boolean isSmartSearchRankingEnabled = mSearchFeatureProvider
+ .isSmartSearchRankingEnabled(mFragment.getContext().getApplicationContext());
+
+ if (isSmartSearchRankingEnabled) {
// TODO: run this in parallel to loading the results if takes too long
- mSearchFeatureProvider.rankSearchResults(query, results);
+ mSearchFeatureProvider.rankSearchResults(query, newResults);
}
- mSearchResults.addAll(results);
- notifyDataSetChanged();
+ final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
+ new SearchResultDiffCallback(mSearchResults, newResults),
+ isSmartSearchRankingEnabled);
+ mSearchResults = newResults;
+ diffResult.dispatchUpdatesTo(this);
return mSearchResults.size();
}
import static org.mockito.Mockito.when;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import static com.google.common.truth.Truth.assertThat;
private Context mContext;
private String mLoaderClassName;
+ private String[] TITLES = {"alpha", "bravo", "charlie", "appAlpha", "appBravo", "appCharlie"};
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
- public void testNoResultsAdded_EmptyListReturned() {
+ public void testNoResultsAdded_emptyListReturned() {
List<SearchResult> updatedResults = mAdapter.getSearchResults();
assertThat(updatedResults).isEmpty();
}
@Test
- public void testSingleSourceMerge_ExactCopyReturned() {
+ public void testSingleSourceMerge_exactCopyReturned() {
ArrayList<SearchResult> intentResults = getIntentSampleResults();
mAdapter.addSearchResults(intentResults, mLoaderClassName);
mAdapter.displaySearchResults("");
}
@Test
- public void testCreateViewHolder_ReturnsIntentResult() {
+ public void testCreateViewHolder_returnsIntentResult() {
ViewGroup group = new FrameLayout(mContext);
SearchViewHolder view = mAdapter.onCreateViewHolder(group,
ResultPayload.PayloadType.INTENT);
}
@Test
- public void testCreateViewHolder_ReturnsInlineSwitchResult() {
+ public void testCreateViewHolder_returnsInlineSwitchResult() {
ViewGroup group = new FrameLayout(mContext);
SearchViewHolder view = mAdapter.onCreateViewHolder(group,
ResultPayload.PayloadType.INLINE_SWITCH);
}
@Test
- public void testEndToEndSearch_ProperResultsMerged() {
- mAdapter.addSearchResults(getDummyAppResults(),
+ public void testEndToEndSearch_properResultsMerged_correctOrder() {
+ mAdapter.addSearchResults(getDummyAppResults(), InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(getDummyDbResults(), DatabaseResultLoader.class.getName());
+ int count = mAdapter.displaySearchResults("");
+
+ List<SearchResult> results = mAdapter.getSearchResults();
+ assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
+ assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
+ assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
+ assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
+ assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
+ assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
+ assertThat(count).isEqualTo(6);
+ }
+
+ @Test
+ public void testEndToEndSearch_addResults_resultsAddedInOrder() {
+ List<AppSearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ // Add two individual items
+ mAdapter.addSearchResults(appResults.subList(0,1),
InstalledAppResultLoader.class.getName());
- mAdapter.addSearchResults(getDummyDbResults(),
- DatabaseResultLoader.class.getName());
+ mAdapter.addSearchResults(dbResults.subList(0,1), DatabaseResultLoader.class.getName());
+ mAdapter.displaySearchResults("");
+ // Add super-set of items
+ mAdapter.addSearchResults(appResults, InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(dbResults, DatabaseResultLoader.class.getName());
int count = mAdapter.displaySearchResults("");
List<SearchResult> results = mAdapter.getSearchResults();
- assertThat(results.get(0).title).isEqualTo("alpha");
- assertThat(results.get(1).title).isEqualTo("appAlpha");
- assertThat(results.get(2).title).isEqualTo("appBravo");
- assertThat(results.get(3).title).isEqualTo("bravo");
- assertThat(results.get(4).title).isEqualTo("appCharlie");
- assertThat(results.get(5).title).isEqualTo("Charlie");
+ assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
+ assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
+ assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
+ assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
+ assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
+ assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
assertThat(count).isEqualTo(6);
}
@Test
public void testDisplayResults_ShouldRunSmartRankingIfEnabled() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any()))
- .thenReturn(true);
+ .thenReturn(true);
mAdapter.displaySearchResults("");
verify(mSearchFeatureProvider, times(1)).rankSearchResults(anyString(), anyList());
}
+ @Test
+ public void testEndToEndSearch_removeResults_resultsAdded() {
+ List<AppSearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ // Add list of items
+ mAdapter.addSearchResults(appResults, InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(dbResults, DatabaseResultLoader.class.getName());
+ mAdapter.displaySearchResults("");
+ // Add subset of items
+ mAdapter.addSearchResults(appResults.subList(0,1),
+ InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(dbResults.subList(0,1), DatabaseResultLoader.class.getName());
+ int count = mAdapter.displaySearchResults("");
+
+ List<SearchResult> results = mAdapter.getSearchResults();
+ assertThat(results.get(0).title).isEqualTo(TITLES[0]);
+ assertThat(results.get(1).title).isEqualTo(TITLES[3]);
+ assertThat(count).isEqualTo(2);
+ }
+
private List<SearchResult> getDummyDbResults() {
List<SearchResult> results = new ArrayList<>();
ResultPayload payload = new ResultPayload(new Intent());
SearchResult.Builder builder = new SearchResult.Builder();
builder.addPayload(payload);
- builder.addTitle("alpha")
+ builder.addTitle(TITLES[0])
.addRank(1);
results.add(builder.build());
- builder.addTitle("bravo")
+ builder.addTitle(TITLES[1])
.addRank(3);
results.add(builder.build());
- builder.addTitle("Charlie")
+ builder.addTitle(TITLES[2])
.addRank(6);
results.add(builder.build());
AppSearchResult.Builder builder = new AppSearchResult.Builder();
builder.addPayload(payload);
- builder.addTitle("appAlpha")
+ builder.addTitle(TITLES[3])
.addRank(1);
results.add(builder.build());
- builder.addTitle("appBravo")
+ builder.addTitle(TITLES[4])
.addRank(2);
results.add(builder.build());
- builder.addTitle("appCharlie")
+ builder.addTitle(TITLES[5])
.addRank(4);
results.add(builder.build());