/* * 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.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.sqlite.SQLiteDatabase; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.dashboard.SiteMapManager; import com.android.settings.search.IndexDatabaseHelper; import com.android.settings.testutils.DatabaseTestUtils; import com.android.settings.testutils.FakeFeatureFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; 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; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class DatabaseResultLoaderTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mMockContext; @Mock private SiteMapManager mSiteMapManager; 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"; SQLiteDatabase mDb; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; FakeFeatureFactory.setupForTest(mMockContext); FakeFeatureFactory factory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext); when(factory.searchFeatureProvider.getSiteMapManager()) .thenReturn(mSiteMapManager); mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase(); setUpDb(); } @After public void cleanUp() { DatabaseTestUtils.clearDb(); } @Test public void testMatchTitle() { loader = new DatabaseResultLoader(mContext, "title", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(2); verify(mSiteMapManager, times(2)).buildBreadCrumb(eq(mContext), anyString(), anyString()); } @Test public void testMatchSummary() { loader = new DatabaseResultLoader(mContext, "summary", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(2); } @Test public void testMatchKeywords() { loader = new DatabaseResultLoader(mContext, "keywords", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(2); } @Test public void testMatchEntries() { loader = new DatabaseResultLoader(mContext, "entries", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(2); } @Test public void testSpecialCaseWord_matchesNonPrefix() { insertSpecialCase("Data usage"); loader = new DatabaseResultLoader(mContext, "usage", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(1); } @Test public void testSpecialCaseSpace_matches() { insertSpecialCase("space"); loader = new DatabaseResultLoader(mContext, " space ", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(1); } @Test 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() { insertSpecialCase("priorités seulment"); loader = new DatabaseResultLoader(mContext, "priorités", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(1); } @Test 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() { insertSpecialCase("wi-fi calling"); loader = new DatabaseResultLoader(mContext, "wifi calling", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(1); } @Test public void testSpecialCasePrefix_matchesPrefixOfEntry() { insertSpecialCase("Photos"); loader = new DatabaseResultLoader(mContext, "pho", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(1); } @Test public void testSpecialCasePrefix_DoesNotMatchNonPrefixSubstring() { insertSpecialCase("Photos"); loader = new DatabaseResultLoader(mContext, "hot", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(0); } @Test public void testSpecialCaseMultiWordPrefix_matchesPrefixOfEntry() { insertSpecialCase("Apps Notifications"); loader = new DatabaseResultLoader(mContext, "Apps", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(1); } @Test public void testSpecialCaseMultiWordPrefix_matchesSecondWordPrefixOfEntry() { insertSpecialCase("Apps Notifications"); loader = new DatabaseResultLoader(mContext, "Not", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(1); } @Test public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfFirstEntry() { insertSpecialCase("Apps Notifications"); loader = new DatabaseResultLoader(mContext, "pp", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(0); } @Test public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfSecondEntry() { insertSpecialCase("Apps Notifications"); loader = new DatabaseResultLoader(mContext, "tion", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(0); } @Test public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfEntry() { insertSpecialCase("Apps & Notifications"); loader = new DatabaseResultLoader(mContext, "App", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(1); } @Test public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfSecondEntry() { insertSpecialCase("Apps & Notifications"); loader = new DatabaseResultLoader(mContext, "No", mSiteMapManager); assertThat(loader.loadInBackground().size()).isEqualTo(1); } @Test public void testDeDupe_noDuplicates_originalListReturn() { // Three elements with unique titles and summaries List 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 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 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 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 testDeDupe_threeDuplicates_onlyOneStays() { /* * Create a list as follows: * (3) Intent One * (2) Intent One * (1) Intent One * * After removing duplicates: * (1) Intent One */ List results = new ArrayList(); 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(titleOne) .addSummary(summaryOne) .addPayload(intentPayload); SearchResult resultTwo = builder.build(); results.add(resultTwo); // Intent Three builder.addTitle(titleOne) .addSummary(summaryOne) .addPayload(intentPayload); SearchResult resultThree = builder.build(); results.add(resultThree); loader = new DatabaseResultLoader(mContext, "", null); loader.removeDuplicates(results); assertThat(results.size()).isEqualTo(1); } @Test public void testSpecialCaseTwoWords_firstWordMatches_ranksHigher() { final String caseOne = "Apple pear"; final String caseTwo = "Banana apple"; insertSpecialCase(caseOne); insertSpecialCase(caseTwo); loader = new DatabaseResultLoader(mContext, "App", null); List results = loader.loadInBackground(); assertThat(results.get(0).title).isEqualTo(caseOne); assertThat(results.get(1).title).isEqualTo(caseTwo); assertThat(results.get(0).rank).isLessThan(results.get(1).rank); } private void insertSpecialCase(String specialCase) { String normalized = DatabaseIndexingUtils.normalizeHyphen(specialCase); normalized = DatabaseIndexingUtils.normalizeString(normalized); ContentValues values = new ContentValues(); values.put(IndexDatabaseHelper.IndexColumns.DOCID, normalized.hashCode()); values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us"); values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1); values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, specialCase); values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, normalized); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, ""); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, ""); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, ""); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, ""); values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, ""); values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, ""); values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, "com.android.settings.gestures.GestureSettings"); values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves"); values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, ""); values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, ""); values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, ""); values.put(IndexDatabaseHelper.IndexColumns.ICON, ""); values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true); values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power"); values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0); values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0); values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, (String) null); mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values); } private void setUpDb() { ContentValues values = new ContentValues(); values.put(IndexDatabaseHelper.IndexColumns.DOCID, 0); values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us"); values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1); values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, "alpha_title"); values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, "alpha title"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, "alpha_summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, "alpha summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, "alpha_summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, "alpha summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, "alpha entries"); values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, "alpha keywords"); values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, "com.android.settings.gestures.GestureSettings"); values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves"); values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, ""); values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, ""); values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, ""); values.put(IndexDatabaseHelper.IndexColumns.ICON, ""); values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true); values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power"); values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0); values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0); values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, (String) null); mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values); values = new ContentValues(); values.put(IndexDatabaseHelper.IndexColumns.DOCID, 1); values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us"); values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1); values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, "bravo_title"); values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, "bravo title"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, "bravo_summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, "bravo summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, "bravo_summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, "bravo summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, "bravo entries"); values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, "bravo keywords"); values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, "com.android.settings.gestures.GestureSettings"); values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves"); values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, ""); values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, ""); values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, ""); values.put(IndexDatabaseHelper.IndexColumns.ICON, ""); values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true); values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power"); values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0); values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0); values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, (String) null); mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values); values = new ContentValues(); values.put(IndexDatabaseHelper.IndexColumns.DOCID, 2); values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us"); values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1); values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, "charlie_title"); values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, "charlie title"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, "charlie_summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, "charlie summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, "charlie_summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, "charlie summary"); values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, "charlie entries"); values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, "charlie keywords"); values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, "com.android.settings.gestures.GestureSettings"); values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves"); values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, ""); values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, ""); values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, ""); values.put(IndexDatabaseHelper.IndexColumns.ICON, ""); values.put(IndexDatabaseHelper.IndexColumns.ENABLED, false); values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power"); values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0); values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0); values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, (String) null); mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values); } }