Merge "First pass at removing duplicate results"

This commit is contained in:
TreeHugger Robot
2017-02-23 18:57:13 +00:00
committed by Android (Google) Code Review
2 changed files with 242 additions and 13 deletions

View File

@@ -144,7 +144,7 @@ public class DatabaseResultLoader extends AsyncLoader<List<? extends SearchResul
results.addAll(secondaryResults);
results.addAll(tertiaryResults);
return results;
return removeDuplicates(results);
}
@Override
@@ -300,4 +300,55 @@ public class DatabaseResultLoader extends AsyncLoader<List<? extends SearchResul
}
return selection;
}
/**
* Goes through the list of search results and verifies that none of the results are duplicates.
* A duplicate is quantified by a result with the same Title and the same non-empty Summary.
*
* The method walks through the results starting with the highest priority result. It removes
* the duplicates by doing the first rule that applies below:
* - If a result is inline, remove the intent result.
* - Remove the lower rank item.
* @param results A list of results with potential duplicates
* @return The list of results with duplicates removed.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
List<SearchResult> removeDuplicates(List<SearchResult> results) {
SearchResult primaryResult, secondaryResult;
// We accept the O(n^2) solution because the number of results is small.
for (int i = results.size() - 1; i >= 0; i--) {
secondaryResult = results.get(i);
for (int j = i - 1; j >= 0; j--) {
primaryResult = results.get(j);
if (areDuplicateResults(primaryResult, secondaryResult)) {
if (primaryResult.viewType != ResultPayload.PayloadType.INTENT) {
// Case where both payloads are inline
results.remove(i);
break;
} else if (secondaryResult.viewType != ResultPayload.PayloadType.INTENT) {
// Case where only second result is inline
results.remove(j);
i--; // shift the top index to reflect the lower element being removed
} else {
// Case where both payloads are intent
results.remove(i);
}
}
}
}
return results;
}
/**
* @return True when the two {@link SearchResult SearchResults} have the same title, and the same
* non-empty summary.
*/
private boolean areDuplicateResults(SearchResult primary, SearchResult secondary) {
return TextUtils.equals(primary.title, secondary.title)
&& TextUtils.equals(primary.summary, secondary.summary)
&& !TextUtils.isEmpty(primary.summary);
}
}

View File

@@ -19,6 +19,7 @@ package com.android.settings.search2;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import com.android.settings.SettingsRobolectricTestRunner;
@@ -38,6 +39,7 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.truth.Truth.assertThat;
@@ -58,6 +60,15 @@ public class DatabaseResultLoaderTest {
private Context mContext;
private DatabaseResultLoader loader;
private final String titleOne = "titleOne";
private final String titleTwo = "titleTwo";
private final String titleThree = "titleThree";
private final String titleFour = "titleFour";
private final String summaryOne = "summaryOne";
private final String summaryTwo = "summaryTwo";
private final String summaryThree = "summaryThree";
private final String summaryFour = "summaryFour";
SQLiteDatabase mDb;
@Before
@@ -104,49 +115,49 @@ public class DatabaseResultLoaderTest {
}
@Test
public void testSpecialCaseWord_MatchesNonPrefix() {
public void testSpecialCaseWord_matchesNonPrefix() {
insertSpecialCase("Data usage");
loader = new DatabaseResultLoader(mContext, "usage", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseSpace_Matches() {
public void testSpecialCaseSpace_matches() {
insertSpecialCase("space");
loader = new DatabaseResultLoader(mContext, " space ", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseDash_MatchesWordNoDash() {
public void testSpecialCaseDash_matchesWordNoDash() {
insertSpecialCase("wi-fi calling");
loader = new DatabaseResultLoader(mContext, "wifi", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseDash_MatchesWordWithDash() {
public void testSpecialCaseDash_matchesWordWithDash() {
insertSpecialCase("priorités seulment");
loader = new DatabaseResultLoader(mContext, "priorités", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseDash_MatchesWordWithoutDash() {
public void testSpecialCaseDash_matchesWordWithoutDash() {
insertSpecialCase("priorités seulment");
loader = new DatabaseResultLoader(mContext, "priorites", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseDash_MatchesEntireQueryWithoutDash() {
public void testSpecialCaseDash_matchesEntireQueryWithoutDash() {
insertSpecialCase("wi-fi calling");
loader = new DatabaseResultLoader(mContext, "wifi calling", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCasePrefix_MatchesPrefixOfEntry() {
public void testSpecialCasePrefix_matchesPrefixOfEntry() {
insertSpecialCase("Photos");
loader = new DatabaseResultLoader(mContext, "pho", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
@@ -160,14 +171,14 @@ public class DatabaseResultLoaderTest {
}
@Test
public void testSpecialCaseMultiWordPrefix_MatchesPrefixOfEntry() {
public void testSpecialCaseMultiWordPrefix_matchesPrefixOfEntry() {
insertSpecialCase("Apps Notifications");
loader = new DatabaseResultLoader(mContext, "Apps", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseMultiWordPrefix_MatchesSecondWordPrefixOfEntry() {
public void testSpecialCaseMultiWordPrefix_matchesSecondWordPrefixOfEntry() {
insertSpecialCase("Apps Notifications");
loader = new DatabaseResultLoader(mContext, "Not", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
@@ -188,21 +199,188 @@ public class DatabaseResultLoaderTest {
}
@Test
public void testSpecialCaseMultiWordPrefixWithSpecial_MatchesPrefixOfEntry() {
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfEntry() {
insertSpecialCase("Apps & Notifications");
loader = new DatabaseResultLoader(mContext, "App", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseMultiWordPrefixWithSpecial_MatchesPrefixOfSecondEntry() {
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfSecondEntry() {
insertSpecialCase("Apps & Notifications");
loader = new DatabaseResultLoader(mContext, "No", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseTwoWords_FirstWordMatches_RanksHigher() {
public void testDeDupe_noDuplicates_originalListReturn() {
// Three elements with unique titles and summaries
List<SearchResult> results = new ArrayList();
IntentPayload intentPayload = new IntentPayload(new Intent());
SearchResult.Builder builder = new SearchResult.Builder();
builder.addTitle(titleOne)
.addSummary(summaryOne)
.addPayload(intentPayload);
SearchResult resultOne = builder.build();
results.add(resultOne);
builder.addTitle(titleTwo)
.addSummary(summaryTwo);
SearchResult resultTwo = builder.build();
results.add(resultTwo);
builder.addTitle(titleThree)
.addSummary(summaryThree);
SearchResult resultThree = builder.build();
results.add(resultThree);
loader = new DatabaseResultLoader(mContext, "", null);
loader.removeDuplicates(results);
assertThat(results.size()).isEqualTo(3);
assertThat(results.get(0)).isEqualTo(resultOne);
assertThat(results.get(1)).isEqualTo(resultTwo);
assertThat(results.get(2)).isEqualTo(resultThree);
}
@Test
public void testDeDupe_oneDuplicate_duplicateRemoved() {
List<SearchResult> results = new ArrayList();
IntentPayload intentPayload = new IntentPayload(new Intent());
SearchResult.Builder builder = new SearchResult.Builder();
builder.addTitle(titleOne)
.addSummary(summaryOne)
.addRank(0)
.addPayload(intentPayload);
SearchResult resultOne = builder.build();
results.add(resultOne);
// Duplicate of the first element
builder.addTitle(titleOne)
.addSummary(summaryOne)
.addRank(1);
SearchResult resultTwo = builder.build();
results.add(resultTwo);
// Unique
builder.addTitle(titleThree)
.addSummary(summaryThree);
SearchResult resultThree = builder.build();
results.add(resultThree);
loader = new DatabaseResultLoader(mContext, "", null);
loader.removeDuplicates(results);
assertThat(results.size()).isEqualTo(2);
assertThat(results.get(0)).isEqualTo(resultOne);
assertThat(results.get(1)).isEqualTo(resultThree);
}
@Test
public void testDeDupe_firstDupeInline_secondDuplicateRemoved() {
List<SearchResult> results = new ArrayList();
InlineSwitchPayload inlinePayload = new InlineSwitchPayload("", 0,
null);
IntentPayload intentPayload = new IntentPayload(new Intent());
SearchResult.Builder builder = new SearchResult.Builder();
// Inline result
builder.addTitle(titleOne)
.addSummary(summaryOne)
.addRank(0)
.addPayload(inlinePayload);
SearchResult resultOne = builder.build();
results.add(resultOne);
// Duplicate of first result, but Intent Result. Should be removed.
builder.addTitle(titleOne)
.addSummary(summaryOne)
.addRank(1)
.addPayload(intentPayload);
SearchResult resultTwo = builder.build();
results.add(resultTwo);
// Unique
builder.addTitle(titleThree)
.addSummary(summaryThree);
SearchResult resultThree = builder.build();
results.add(resultThree);
loader = new DatabaseResultLoader(mContext, "", null);
loader.removeDuplicates(results);
assertThat(results.size()).isEqualTo(2);
assertThat(results.get(0)).isEqualTo(resultOne);
assertThat(results.get(1)).isEqualTo(resultThree);
}
@Test
public void testDeDupe_secondDupeInline_firstDuplicateRemoved() {
/*
* Create a list as follows:
* (5) Intent Four
* (4) Inline Two
* (3) Intent Three
* (2) Intent Two
* (1) Intent One
*
* After removing duplicates:
* (4) Intent Four
* (3) Inline Two
* (2) Intent Three
* (1) Intent One
*/
List<SearchResult> results = new ArrayList();
InlineSwitchPayload inlinePayload = new InlineSwitchPayload("", 0,
null);
IntentPayload intentPayload = new IntentPayload(new Intent());
SearchResult.Builder builder = new SearchResult.Builder();
// Intent One
builder.addTitle(titleOne)
.addSummary(summaryOne)
.addPayload(intentPayload);
SearchResult resultOne = builder.build();
results.add(resultOne);
// Intent Two
builder.addTitle(titleTwo)
.addSummary(summaryTwo)
.addPayload(intentPayload);
SearchResult resultTwo = builder.build();
results.add(resultTwo);
// Intent Three
builder.addTitle(titleThree)
.addSummary(summaryThree);
SearchResult resultThree = builder.build();
results.add(resultThree);
// Inline Two
builder.addTitle(titleTwo)
.addSummary(summaryTwo)
.addPayload(inlinePayload);
SearchResult resultFour = builder.build();
results.add(resultFour);
// Intent Four
builder.addTitle(titleFour)
.addSummary(summaryOne)
.addPayload(intentPayload);
SearchResult resultFive = builder.build();
results.add(resultFive);
loader = new DatabaseResultLoader(mContext, "", null);
loader.removeDuplicates(results);
assertThat(results.size()).isEqualTo(4);
assertThat(results.get(0)).isEqualTo(resultOne);
assertThat(results.get(1)).isEqualTo(resultThree);
assertThat(results.get(2)).isEqualTo(resultFour);
assertThat(results.get(3)).isEqualTo(resultFive);
}
@Test
public void testSpecialCaseTwoWords_firstWordMatches_ranksHigher() {
final String caseOne = "Apple pear";
final String caseTwo = "Banana apple";
insertSpecialCase(caseOne);