Home | History | Annotate | Download | only in search
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  *
     16  */
     17 
     18 package com.android.settings.search;
     19 
     20 import static com.google.common.truth.Truth.assertThat;
     21 import static org.mockito.Matchers.any;
     22 import static org.mockito.Matchers.anyBoolean;
     23 import static org.mockito.Matchers.anyInt;
     24 import static org.mockito.Matchers.anyList;
     25 import static org.mockito.Matchers.anyString;
     26 import static org.mockito.Mockito.doReturn;
     27 import static org.mockito.Mockito.spy;
     28 import static org.mockito.Mockito.verify;
     29 
     30 import android.content.ContentValues;
     31 import android.content.Context;
     32 import android.content.Intent;
     33 import android.content.pm.PackageManager;
     34 import android.content.pm.ResolveInfo;
     35 import android.database.Cursor;
     36 import android.database.sqlite.SQLiteDatabase;
     37 import android.os.Build;
     38 import android.provider.SearchIndexableData;
     39 import android.util.ArrayMap;
     40 
     41 import com.android.settings.search.indexing.PreIndexData;
     42 import com.android.settings.testutils.DatabaseTestUtils;
     43 import com.android.settings.testutils.FakeFeatureFactory;
     44 import com.android.settings.testutils.SettingsRobolectricTestRunner;
     45 import com.android.settings.testutils.shadow.ShadowRunnableAsyncTask;
     46 
     47 import org.junit.After;
     48 import org.junit.Before;
     49 import org.junit.Test;
     50 import org.junit.runner.RunWith;
     51 import org.mockito.Mock;
     52 import org.mockito.MockitoAnnotations;
     53 import org.robolectric.RuntimeEnvironment;
     54 import org.robolectric.annotation.Config;
     55 
     56 import java.util.ArrayList;
     57 import java.util.Arrays;
     58 import java.util.HashSet;
     59 import java.util.List;
     60 import java.util.Locale;
     61 import java.util.Map;
     62 import java.util.Set;
     63 
     64 @RunWith(SettingsRobolectricTestRunner.class)
     65 @Config(shadows = ShadowRunnableAsyncTask.class)
     66 public class DatabaseIndexingManagerTest {
     67 
     68     private final String localeStr = "en_US";
     69 
     70     private final int rank = 8;
     71     private final String title = "title\u2011title";
     72     private final String updatedTitle = "title-title";
     73     private final String normalizedTitle = "titletitle";
     74     private final String summaryOn = "summary\u2011on";
     75     private final String updatedSummaryOn = "summary-on";
     76     private final String normalizedSummaryOn = "summaryon";
     77     private final String summaryOff = "summary\u2011off";
     78     private final String entries = "entries";
     79     private final String keywords = "keywords, keywordss, keywordsss";
     80     private final String spaceDelimittedKeywords = "keywords keywordss keywordsss";
     81     private final String screenTitle = "screen title";
     82     private final String className = "class name";
     83     private final int iconResId = 0xff;
     84     private final String action = "action";
     85     private final String targetPackage = "target package";
     86     private final String targetClass = "target class";
     87     private final String packageName = "package name";
     88     private final String key = "key";
     89     private final int userId = -1;
     90     private final boolean enabled = true;
     91 
     92     private final String TITLE_ONE = "title one";
     93     private final String TITLE_TWO = "title two";
     94     private final String KEY_ONE = "key one";
     95     private final String KEY_TWO = "key two";
     96 
     97     private Context mContext;
     98 
     99     private DatabaseIndexingManager mManager;
    100     private SQLiteDatabase mDb;
    101 
    102     private final List<ResolveInfo> FAKE_PROVIDER_LIST = new ArrayList<>();
    103 
    104     @Mock
    105     private PackageManager mPackageManager;
    106 
    107     @Before
    108     public void setUp() {
    109         MockitoAnnotations.initMocks(this);
    110         mContext = spy(RuntimeEnvironment.application);
    111         mManager = spy(new DatabaseIndexingManager(mContext));
    112         mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
    113 
    114         doReturn(mPackageManager).when(mContext).getPackageManager();
    115         doReturn(FAKE_PROVIDER_LIST).when(mPackageManager)
    116                 .queryIntentContentProviders(any(Intent.class), anyInt());
    117         FakeFeatureFactory.setupForTest();
    118     }
    119 
    120     @After
    121     public void cleanUp() {
    122         DatabaseTestUtils.clearDb(mContext);
    123     }
    124 
    125     @Test
    126     public void testDatabaseSchema() {
    127         Cursor dbCursor = mDb.query("prefs_index", null, null, null, null, null, null);
    128         List<String> columnNames = new ArrayList<>(Arrays.asList(dbCursor.getColumnNames()));
    129         // Note that docid is not included.
    130         List<String> expColumnNames = Arrays.asList(
    131                 "locale",
    132                 "data_rank",
    133                 "data_title",
    134                 "data_title_normalized",
    135                 "data_summary_on",
    136                 "data_summary_on_normalized",
    137                 "data_summary_off",
    138                 "data_summary_off_normalized",
    139                 "data_entries",
    140                 "data_keywords",
    141                 "class_name",
    142                 "screen_title",
    143                 "intent_action",
    144                 "intent_target_package",
    145                 "intent_target_class",
    146                 "icon",
    147                 "enabled",
    148                 "data_key_reference",
    149                 "user_id",
    150                 "payload_type",
    151                 "payload"
    152         );
    153         // Prevent database schema regressions
    154         assertThat(columnNames).containsAllIn(expColumnNames);
    155     }
    156 
    157     // Test new public indexing flow
    158 
    159     @Test
    160     public void testPerformIndexing_fullIndex_getsDataFromProviders() {
    161         SearchIndexableRaw rawData = getFakeRaw();
    162         PreIndexData data = getPreIndexData(rawData);
    163         doReturn(data).when(mManager).getIndexDataFromProviders(anyList(), anyBoolean());
    164         doReturn(true).when(mManager)
    165             .isFullIndex(any(Context.class), anyString(), anyString(), anyString());
    166 
    167         mManager.performIndexing();
    168 
    169         verify(mManager).updateDatabase(data, true /* isFullIndex */);
    170     }
    171 
    172     @Test
    173     public void testPerformIndexing_fullIndex_databaseDropped() {
    174         // Initialize the Manager and force rebuild
    175         DatabaseIndexingManager manager =
    176                 spy(new DatabaseIndexingManager(mContext));
    177         doReturn(false).when(mManager)
    178             .isFullIndex(any(Context.class), anyString(), anyString(), anyString());
    179 
    180         // Insert data point which will be dropped
    181         insertSpecialCase("Ceci n'est pas un pipe", true, "oui oui mon ami");
    182 
    183         manager.performIndexing();
    184 
    185         // Assert that the Old Title is no longer in the database, since it was dropped
    186         final Cursor oldCursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
    187 
    188         assertThat(oldCursor.getCount()).isEqualTo(0);
    189     }
    190 
    191     @Test
    192     public void testPerformIndexing_isfullIndex() {
    193         SearchIndexableRaw rawData = getFakeRaw();
    194         PreIndexData data = getPreIndexData(rawData);
    195         doReturn(data).when(mManager).getIndexDataFromProviders(anyList(), anyBoolean());
    196         doReturn(true).when(mManager)
    197             .isFullIndex(any(Context.class), anyString(), anyString(), anyString());
    198 
    199         mManager.performIndexing();
    200 
    201         verify(mManager).updateDatabase(data, true /* isFullIndex */);
    202     }
    203 
    204     @Test
    205     public void testPerformIndexing_onOta_buildNumberIsCached() {
    206         mManager.performIndexing();
    207 
    208         assertThat(IndexDatabaseHelper.isBuildIndexed(mContext, Build.FINGERPRINT)).isTrue();
    209     }
    210 
    211     @Test
    212     public void testLocaleUpdated_afterIndexing_localeNotAdded() {
    213         PreIndexData emptydata = new PreIndexData();
    214         mManager.updateDatabase(emptydata, true /* isFullIndex */);
    215 
    216         assertThat(IndexDatabaseHelper.isLocaleAlreadyIndexed(mContext, localeStr)).isFalse();
    217     }
    218 
    219     @Test
    220     public void testLocaleUpdated_afterFullIndexing_localeAdded() {
    221         mManager.performIndexing();
    222 
    223         assertThat(IndexDatabaseHelper.isLocaleAlreadyIndexed(mContext, localeStr)).isTrue();
    224     }
    225 
    226     @Test
    227     public void testUpdateDatabase_newEligibleData_addedToDatabase() {
    228         // Test that addDataToDatabase is called when dataToUpdate is non-empty
    229         PreIndexData indexData = new PreIndexData();
    230         indexData.dataToUpdate.add(getFakeRaw());
    231         mManager.updateDatabase(indexData, true /* isFullIndex */);
    232 
    233         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
    234         cursor.moveToPosition(0);
    235 
    236         // Locale
    237         assertThat(cursor.getString(0)).isEqualTo(localeStr);
    238         // Data Title
    239         assertThat(cursor.getString(2)).isEqualTo(updatedTitle);
    240         // Normalized Title
    241         assertThat(cursor.getString(3)).isEqualTo(normalizedTitle);
    242         // Summary On
    243         assertThat(cursor.getString(4)).isEqualTo(updatedSummaryOn);
    244         // Summary On Normalized
    245         assertThat(cursor.getString(5)).isEqualTo(normalizedSummaryOn);
    246         // Entries
    247         assertThat(cursor.getString(8)).isEqualTo(entries);
    248         // Keywords
    249         assertThat(cursor.getString(9)).isEqualTo(spaceDelimittedKeywords);
    250         // Screen Title
    251         assertThat(cursor.getString(10)).isEqualTo(screenTitle);
    252         // Class Name
    253         assertThat(cursor.getString(11)).isEqualTo(className);
    254         // Icon
    255         assertThat(cursor.getInt(12)).isEqualTo(iconResId);
    256         // Intent Action
    257         assertThat(cursor.getString(13)).isEqualTo(action);
    258         // Target Package
    259         assertThat(cursor.getString(14)).isEqualTo(targetPackage);
    260         // Target Class
    261         assertThat(cursor.getString(15)).isEqualTo(targetClass);
    262         // Enabled
    263         assertThat(cursor.getInt(16) == 1).isEqualTo(enabled);
    264         // Data ref key
    265         assertThat(cursor.getString(17)).isNotNull();
    266         // User Id
    267         assertThat(cursor.getInt(18)).isEqualTo(userId);
    268         // Payload Type - default is 0
    269         assertThat(cursor.getInt(19)).isEqualTo(0);
    270         // Payload
    271         byte[] payload = cursor.getBlob(20);
    272         ResultPayload unmarshalledPayload = ResultPayloadUtils.unmarshall(payload,
    273                 ResultPayload.CREATOR);
    274         assertThat(unmarshalledPayload).isInstanceOf(ResultPayload.class);
    275     }
    276 
    277     @Test
    278     public void testUpdateDataInDatabase_enabledResultsAreNonIndexable_becomeDisabled() {
    279         // Both results are enabled, and then TITLE_ONE gets disabled.
    280         final boolean enabled = true;
    281         insertSpecialCase(TITLE_ONE, enabled, KEY_ONE);
    282         insertSpecialCase(TITLE_TWO, enabled, KEY_TWO);
    283         Map<String, Set<String>> niks = new ArrayMap<>();
    284         Set<String> keys = new HashSet<>();
    285         keys.add(KEY_ONE);
    286         niks.put(targetPackage, keys);
    287 
    288         mManager.updateDataInDatabase(mDb, niks);
    289 
    290         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null);
    291         cursor.moveToPosition(0);
    292 
    293         assertThat(cursor.getString(2)).isEqualTo(TITLE_ONE);
    294     }
    295 
    296     @Test
    297     public void testUpdateDataInDatabase_disabledResultsAreIndexable_becomeEnabled() {
    298         // Both results are initially disabled, and then TITLE_TWO gets enabled.
    299         final boolean enabled = false;
    300         insertSpecialCase(TITLE_ONE, enabled, KEY_ONE);
    301         insertSpecialCase(TITLE_TWO, enabled, KEY_TWO);
    302         Map<String, Set<String>> niks = new ArrayMap<>();
    303         Set<String> keys = new HashSet<>();
    304         keys.add(KEY_ONE);
    305         niks.put(targetPackage, keys);
    306 
    307         mManager.updateDataInDatabase(mDb, niks);
    308 
    309         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
    310         cursor.moveToPosition(0);
    311 
    312         assertThat(cursor.getString(2)).isEqualTo(TITLE_TWO);
    313     }
    314 
    315     @Test
    316     public void testEmptyNonIndexableKeys_emptyDataKeyResources_addedToDatabase() {
    317         insertSpecialCase(TITLE_ONE, true /* enabled */, null /* dataReferenceKey */);
    318         PreIndexData emptydata = new PreIndexData();
    319         mManager.updateDatabase(emptydata, false /* needsReindexing */);
    320 
    321         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
    322         cursor.moveToPosition(0);
    323         assertThat(cursor.getCount()).isEqualTo(1);
    324         assertThat(cursor.getString(2)).isEqualTo(TITLE_ONE);
    325     }
    326 
    327     // Util functions
    328 
    329     private SearchIndexableRaw getFakeRaw() {
    330         return getFakeRaw(localeStr);
    331     }
    332 
    333     private SearchIndexableRaw getFakeRaw(String localeStr) {
    334         SearchIndexableRaw data = new SearchIndexableRaw(mContext);
    335         data.locale = new Locale(localeStr);
    336         data.rank = rank;
    337         data.title = title;
    338         data.summaryOn = summaryOn;
    339         data.summaryOff = summaryOff;
    340         data.entries = entries;
    341         data.keywords = keywords;
    342         data.screenTitle = screenTitle;
    343         data.className = className;
    344         data.packageName = packageName;
    345         data.iconResId = iconResId;
    346         data.intentAction = action;
    347         data.intentTargetPackage = targetPackage;
    348         data.intentTargetClass = targetClass;
    349         data.key = key;
    350         data.userId = userId;
    351         data.enabled = enabled;
    352         return data;
    353     }
    354 
    355     private void insertSpecialCase(String specialCase, boolean enabled, String key) {
    356         ContentValues values = new ContentValues();
    357         values.put(IndexDatabaseHelper.IndexColumns.DOCID, specialCase.hashCode());
    358         values.put(IndexDatabaseHelper.IndexColumns.LOCALE, localeStr);
    359         values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
    360         values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, specialCase);
    361         values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, "");
    362         values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, "");
    363         values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, "");
    364         values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, "");
    365         values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, "");
    366         values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, "");
    367         values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, "");
    368         values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, "");
    369         values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves");
    370         values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, "");
    371         values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, targetPackage);
    372         values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
    373         values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
    374         values.put(IndexDatabaseHelper.IndexColumns.ENABLED, enabled);
    375         values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, key);
    376         values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
    377         values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
    378         values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, (String) null);
    379 
    380         mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
    381     }
    382 
    383     private PreIndexData getPreIndexData(SearchIndexableData fakeData) {
    384         PreIndexData data = new PreIndexData();
    385         data.dataToUpdate.add(fakeData);
    386         return data;
    387     }
    388 }
    389