• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
setUp()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
cleanUp()121     public void cleanUp() {
122         DatabaseTestUtils.clearDb(mContext);
123     }
124 
125     @Test
testDatabaseSchema()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
testPerformIndexing_fullIndex_getsDataFromProviders()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
testPerformIndexing_fullIndex_databaseDropped()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
testPerformIndexing_isfullIndex()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
testPerformIndexing_onOta_buildNumberIsCached()205     public void testPerformIndexing_onOta_buildNumberIsCached() {
206         mManager.performIndexing();
207 
208         assertThat(IndexDatabaseHelper.isBuildIndexed(mContext, Build.FINGERPRINT)).isTrue();
209     }
210 
211     @Test
testLocaleUpdated_afterIndexing_localeNotAdded()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
testLocaleUpdated_afterFullIndexing_localeAdded()220     public void testLocaleUpdated_afterFullIndexing_localeAdded() {
221         mManager.performIndexing();
222 
223         assertThat(IndexDatabaseHelper.isLocaleAlreadyIndexed(mContext, localeStr)).isTrue();
224     }
225 
226     @Test
testUpdateDatabase_newEligibleData_addedToDatabase()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
testUpdateDataInDatabase_enabledResultsAreNonIndexable_becomeDisabled()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
testUpdateDataInDatabase_disabledResultsAreIndexable_becomeEnabled()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
testEmptyNonIndexableKeys_emptyDataKeyResources_addedToDatabase()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 
getFakeRaw()329     private SearchIndexableRaw getFakeRaw() {
330         return getFakeRaw(localeStr);
331     }
332 
getFakeRaw(String localeStr)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 
insertSpecialCase(String specialCase, boolean enabled, String key)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 
getPreIndexData(SearchIndexableData fakeData)383     private PreIndexData getPreIndexData(SearchIndexableData fakeData) {
384         PreIndexData data = new PreIndexData();
385         data.dataToUpdate.add(fakeData);
386         return data;
387     }
388 }
389