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
18package com.android.settings.search2;
19
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.content.ContentProvider;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.PackageManager;
27import android.content.pm.ProviderInfo;
28import android.content.pm.ResolveInfo;
29import android.database.Cursor;
30import android.database.MatrixCursor;
31import android.database.sqlite.SQLiteDatabase;
32import android.net.Uri;
33import android.os.Build;
34import android.provider.SearchIndexableResource;
35import android.util.ArrayMap;
36import com.android.settings.R;
37import com.android.settings.SettingsRobolectricTestRunner;
38import com.android.settings.TestConfig;
39import com.android.settings.search.IndexDatabaseHelper;
40import com.android.settings.search.IndexingCallback;
41import com.android.settings.search.SearchIndexableRaw;
42import com.android.settings.testutils.DatabaseTestUtils;
43import com.android.settings.testutils.shadow.ShadowDatabaseIndexingUtils;
44import com.android.settings.testutils.shadow.ShadowRunnableAsyncTask;
45import org.junit.After;
46import org.junit.Before;
47import org.junit.Test;
48import org.junit.runner.RunWith;
49import org.mockito.Mock;
50import org.mockito.MockitoAnnotations;
51import org.robolectric.Robolectric;
52import org.robolectric.RuntimeEnvironment;
53import org.robolectric.annotation.Config;
54import org.robolectric.shadows.ShadowContentResolver;
55
56import java.util.ArrayList;
57import java.util.Arrays;
58import java.util.HashMap;
59import java.util.HashSet;
60import java.util.List;
61import java.util.Locale;
62import java.util.Map;
63import java.util.Set;
64
65import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
66import static com.google.common.truth.Truth.assertThat;
67import static org.mockito.Matchers.any;
68import static org.mockito.Matchers.anyInt;
69import static org.mockito.Matchers.anyList;
70import static org.mockito.Matchers.anyMap;
71import static org.mockito.Matchers.anyString;
72import static org.mockito.Mockito.doNothing;
73import static org.mockito.Mockito.doReturn;
74import static org.mockito.Mockito.mock;
75import static org.mockito.Mockito.spy;
76import static org.mockito.Mockito.times;
77import static org.mockito.Mockito.verify;
78import static org.mockito.Mockito.when;
79
80@RunWith(SettingsRobolectricTestRunner.class)
81@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
82        shadows = {ShadowRunnableAsyncTask.class})
83public class DatabaseIndexingManagerTest {
84    private final String localeStr = "en_US";
85
86    private final int rank = 8;
87    private final String title = "title\u2011title";
88    private final String updatedTitle = "title-title";
89    private final String normalizedTitle = "titletitle";
90    private final String summaryOn = "summary\u2011on";
91    private final String updatedSummaryOn = "summary-on";
92    private final String normalizedSummaryOn = "summaryon";
93    private final String summaryOff = "summary\u2011off";
94    private final String updatedSummaryOff = "summary-off";
95    private final String normalizedSummaryOff = "summaryoff";
96    private final String entries = "entries";
97    private final String keywords = "keywords, keywordss, keywordsss";
98    private final String spaceDelimittedKeywords = "keywords keywordss keywordsss";
99    private final String screenTitle = "screen title";
100    private final String className = "class name";
101    private final int iconResId = 0xff;
102    private final int noIcon = 0;
103    private final String action = "action";
104    private final String targetPackage = "target package";
105    private final String targetClass = "target class";
106    private final String packageName = "package name";
107    private final String key = "key";
108    private final int userId = -1;
109    private final boolean enabled = true;
110
111    private final String AUTHORITY_ONE = "authority";
112    private final String PACKAGE_ONE = "com.android.settings";
113
114    private final String TITLE_ONE = "title one";
115    private final String TITLE_TWO = "title two";
116    private final String KEY_ONE = "key one";
117    private final String KEY_TWO = "key two";
118
119    private Context mContext;
120
121    private DatabaseIndexingManager mManager;
122    private SQLiteDatabase mDb;
123
124    @Mock
125    private PackageManager mPackageManager;
126
127    @Before
128    public void setUp() {
129        MockitoAnnotations.initMocks(this);
130        mContext = spy(RuntimeEnvironment.application);
131        mManager = spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
132        mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
133
134        doReturn(mPackageManager).when(mContext).getPackageManager();
135    }
136
137    @After
138    public void cleanUp() {
139        DatabaseTestUtils.clearDb();
140    }
141
142    @Test
143    public void testDatabaseSchema() {
144        Cursor dbCursor = mDb.query("prefs_index", null, null, null, null, null, null);
145        List<String> columnNames = new ArrayList<>(Arrays.asList(dbCursor.getColumnNames()));
146        // Note that docid is not included.
147        List<String> expColumnNames = new ArrayList<>(Arrays.asList(new String[]{
148                "locale",
149                "data_rank",
150                "data_title",
151                "data_title_normalized",
152                "data_summary_on",
153                "data_summary_on_normalized",
154                "data_summary_off",
155                "data_summary_off_normalized",
156                "data_entries",
157                "data_keywords",
158                "class_name",
159                "screen_title",
160                "intent_action",
161                "intent_target_package",
162                "intent_target_class",
163                "icon",
164                "enabled",
165                "data_key_reference",
166                "user_id",
167                "payload_type",
168                "payload"
169        }));
170        // Prevent database schema regressions
171        assertThat(columnNames).containsAllIn(expColumnNames);
172    }
173
174    // Tests for the flow: IndexOneRaw -> UpdateOneRowWithFilteredData -> UpdateOneRow
175
176    @Test
177    public void testInsertRawColumn_rowInserted() {
178        SearchIndexableRaw raw = getFakeRaw();
179        mManager.indexOneSearchIndexableData(mDb, localeStr, raw, null /* Non-indexable keys */);
180        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
181        assertThat(cursor.getCount()).isEqualTo(1);
182    }
183
184    @Test
185    public void testInsertRawColumn_rowMatches() {
186        SearchIndexableRaw raw = getFakeRaw();
187        mManager.indexOneSearchIndexableData(mDb, localeStr, raw, null /* Non-indexable keys */);
188        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
189        cursor.moveToPosition(0);
190
191        // Locale
192        assertThat(cursor.getString(0)).isEqualTo(localeStr);
193        // Data Rank
194        assertThat(cursor.getInt(1)).isEqualTo(raw.rank);
195        // Data Title
196        assertThat(cursor.getString(2)).isEqualTo(updatedTitle);
197        // Normalized Title
198        assertThat(cursor.getString(3)).isEqualTo(normalizedTitle);
199        // Summary On
200        assertThat(cursor.getString(4)).isEqualTo(updatedSummaryOn);
201        // Summary On Normalized
202        assertThat(cursor.getString(5)).isEqualTo(normalizedSummaryOn);
203        // Summary Off
204        assertThat(cursor.getString(6)).isEqualTo(updatedSummaryOff);
205        // Summary off normalized
206        assertThat(cursor.getString(7)).isEqualTo(normalizedSummaryOff);
207        // Entries
208        assertThat(cursor.getString(8)).isEqualTo(raw.entries);
209        // Keywords
210        assertThat(cursor.getString(9)).isEqualTo(spaceDelimittedKeywords);
211        // Screen Title
212        assertThat(cursor.getString(10)).isEqualTo(raw.screenTitle);
213        // Class Name
214        assertThat(cursor.getString(11)).isEqualTo(raw.className);
215        // Icon
216        assertThat(cursor.getInt(12)).isEqualTo(raw.iconResId);
217        // Intent Action
218        assertThat(cursor.getString(13)).isEqualTo(raw.intentAction);
219        // Target Package
220        assertThat(cursor.getString(14)).isEqualTo(raw.intentTargetPackage);
221        // Target Class
222        assertThat(cursor.getString(15)).isEqualTo(raw.intentTargetClass);
223        // Enabled
224        assertThat(cursor.getInt(16) == 1).isEqualTo(raw.enabled);
225        // Data ref key
226        assertThat(cursor.getString(17)).isNotNull();
227        // User Id
228        assertThat(cursor.getInt(18)).isEqualTo(raw.userId);
229        // Payload Type - default is 0
230        assertThat(cursor.getInt(19)).isEqualTo(0);
231        // Payload
232        assertThat(cursor.getBlob(20)).isNull();
233    }
234
235    @Test
236    public void testInsertRawColumn_mismatchedLocale_noRowInserted() {
237        SearchIndexableRaw raw = getFakeRaw("ca-fr");
238        mManager.indexOneSearchIndexableData(mDb, localeStr, raw, null /* Non-indexable keys */);
239        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
240        assertThat(cursor.getCount()).isEqualTo(0);
241    }
242
243    // Tests for the flow: IndexOneResource -> IndexFromResource ->
244    //                     UpdateOneRowWithFilteredData -> UpdateOneRow
245
246    @Test
247    public void testNullResource_NothingInserted() {
248        mManager.indexOneSearchIndexableData(mDb, localeStr, null /* searchIndexableResource */,
249                new HashMap<>());
250        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
251        assertThat(cursor.getCount()).isEqualTo(0);
252    }
253
254    @Test
255    public void testAddResource_RowsInserted() {
256        SearchIndexableResource resource = getFakeResource(R.xml.display_settings);
257        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
258        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
259        assertThat(cursor.getCount()).isEqualTo(18);
260    }
261
262    @Test
263    public void testAddResource_withNIKs_rowsInsertedDisabled() {
264        SearchIndexableResource resource = getFakeResource(R.xml.display_settings);
265        // Only add 2 of 16 items to be disabled.
266        String[] keys = {"brightness", "wallpaper"};
267        Map<String, Set<String>> niks = getNonIndexableKeys(keys);
268
269        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, niks);
270
271        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null);
272        assertThat(cursor.getCount()).isEqualTo(2);
273        cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
274        assertThat(cursor.getCount()).isEqualTo(16);
275    }
276
277    @Test
278    public void testAddResourceHeader_rowsMatch() {
279        SearchIndexableResource resource = getFakeResource(R.xml.application_settings);
280        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
281
282        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null);
283        cursor.moveToPosition(1);
284
285        // Locale
286        assertThat(cursor.getString(0)).isEqualTo(localeStr);
287        // Data Rank
288        assertThat(cursor.getInt(1)).isEqualTo(rank);
289        // Data Title
290        assertThat(cursor.getString(2)).isEqualTo("App info");
291        // Normalized Title
292        assertThat(cursor.getString(3)).isEqualTo("app info");
293        // Summary On
294        assertThat(cursor.getString(4)).isEqualTo("Manage apps, set up quick launch shortcuts");
295        // Summary On Normalized
296        assertThat(cursor.getString(5)).isEqualTo("manage apps, set up quick launch shortcuts");
297        // Summary Off - only on for checkbox preferences
298        assertThat(cursor.getString(6)).isEmpty();
299        // Summary off normalized - only on for checkbox preferences
300        assertThat(cursor.getString(7)).isEmpty();
301        // Entries - only on for list preferences
302        assertThat(cursor.getString(8)).isNull();
303        // Keywords
304        assertThat(cursor.getString(9)).isEmpty();
305        // Screen Title
306        assertThat(cursor.getString(10)).isEqualTo("App info");
307        // Class Name
308        assertThat(cursor.getString(11)).isEqualTo(className);
309        // Icon
310        assertThat(cursor.getInt(12)).isEqualTo(0);
311        // Intent Action
312        assertThat(cursor.getString(13)).isEqualTo(action);
313        // Target Package
314        assertThat(cursor.getString(14)).isEqualTo(targetPackage);
315        // Target Class
316        assertThat(cursor.getString(15)).isEqualTo(targetClass);
317        // Enabled
318        assertThat(cursor.getInt(16) == 1).isEqualTo(enabled);
319        // Data ref key
320        assertThat(cursor.getString(17)).isEqualTo("applications_settings");
321        // User Id
322        assertThat(cursor.getInt(18)).isEqualTo(userId);
323        // Payload Type - default is 0
324        assertThat(cursor.getInt(19)).isEqualTo(0);
325        // Payload - should be updated to real payloads as controllers are added
326        assertThat(cursor.getBlob(20)).isNull();
327    }
328
329    @Test
330    public void testAddResource_withChildFragment_shouldUpdateSiteMapDb() {
331        // FIXME: This test was failing. (count = 6 at the end)
332
333//        SearchIndexableResource resource = getFakeResource(R.xml.network_and_internet);
334//        mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
335//                new HashMap<>());
336//        Cursor query = mDb.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS,
337//                null, null, null, null, null);
338//        query.moveToPosition(-1);
339//        int count = 0;
340//        while (query.moveToNext()) {
341//            count++;
342//            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_CLASS)))
343//                    .isEqualTo(className);
344//            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_TITLE)))
345//                    .isEqualTo(mContext.getString(R.string.network_dashboard_title));
346//            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_CLASS)))
347//                    .isNotEmpty();
348//            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_TITLE)))
349//                    .isNotEmpty();
350//        }
351//        assertThat(count).isEqualTo(5);
352    }
353
354    @Test
355    public void testAddResource_customSetting_rowsMatch() {
356        SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings);
357        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
358        final String prefTitle =
359                mContext.getString(R.string.fingerprint_swipe_for_notifications_title);
360        final String prefSummary =
361                mContext.getString(R.string.fingerprint_swipe_for_notifications_summary);
362        Cursor cursor = mDb.rawQuery(
363                "SELECT * FROM prefs_index where data_title='" + prefTitle + "'", null);
364        cursor.moveToFirst();
365
366        // Locale
367        assertThat(cursor.getString(0)).isEqualTo(localeStr);
368        // Data Rank
369        assertThat(cursor.getInt(1)).isEqualTo(rank);
370        // Data Title
371        assertThat(cursor.getString(2)).isEqualTo(prefTitle);
372        // Normalized Title
373        assertThat(cursor.getString(3)).isEqualTo(prefTitle.toLowerCase());
374        // Summary On
375        assertThat(cursor.getString(4)).isEqualTo(prefSummary);
376        // Summary On Normalized
377        assertThat(cursor.getString(5)).isEqualTo(prefSummary.toLowerCase());
378        // Summary Off - only on for checkbox preferences
379        assertThat(cursor.getString(6)).isEmpty();
380        // Summary off normalized - only on for checkbox preferences
381        assertThat(cursor.getString(7)).isEmpty();
382        // Entries - only on for list preferences
383        assertThat(cursor.getString(8)).isNull();
384        // Keywords
385        assertThat(cursor.getString(9)).isEmpty();
386        // Screen Title
387        assertThat(cursor.getString(10)).isEqualTo(
388                mContext.getString(R.string.fingerprint_swipe_for_notifications_title));
389        // Class Name
390        assertThat(cursor.getString(11)).isEqualTo(className);
391        // Icon
392        assertThat(cursor.getInt(12)).isEqualTo(noIcon);
393        // Intent Action
394        assertThat(cursor.getString(13)).isEqualTo(action);
395        // Target Package
396        assertThat(cursor.getString(14)).isEqualTo(targetPackage);
397        // Target Class
398        assertThat(cursor.getString(15)).isEqualTo(targetClass);
399        // Enabled
400        assertThat(cursor.getInt(16) == 1).isEqualTo(enabled);
401        // Data ref key
402        assertThat(cursor.getString(17)).isEqualTo("gesture_swipe_down_fingerprint");
403        // User Id
404        assertThat(cursor.getInt(18)).isEqualTo(userId);
405        // Payload Type - default is 0
406        assertThat(cursor.getInt(19)).isEqualTo(0);
407        // Payload - should be updated to real payloads as controllers are added
408        assertThat(cursor.getBlob(20)).isNull();
409    }
410
411    @Test
412    public void testAddResource_checkboxPreference_rowsMatch() {
413        SearchIndexableResource resource = getFakeResource(R.xml.application_settings);
414        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
415
416        /* Should return 6 results, with the following titles:
417         * Advanced Settings, Apps, Manage Apps, Preferred install location, Running Services
418         */
419        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null);
420        cursor.moveToPosition(0);
421        // Locale
422        assertThat(cursor.getString(0)).isEqualTo(localeStr);
423        // Data Rank
424        assertThat(cursor.getInt(1)).isEqualTo(rank);
425        // Data Title
426        assertThat(cursor.getString(2)).isEqualTo("Advanced settings");
427        // Normalized Title
428        assertThat(cursor.getString(3)).isEqualTo("advanced settings");
429        // Summary On
430        assertThat(cursor.getString(4)).isEqualTo("Enable more settings options");
431        // Summary On Normalized
432        assertThat(cursor.getString(5)).isEqualTo("enable more settings options");
433        // Summary Off
434        assertThat(cursor.getString(6)).isEqualTo("Enable more settings options");
435        // Summary Off
436        assertThat(cursor.getString(7)).isEqualTo("enable more settings options");
437        // Entries - only on for list preferences
438        assertThat(cursor.getString(8)).isNull();
439        // Keywords
440        assertThat(cursor.getString(9)).isEmpty();
441        // Screen Title
442        assertThat(cursor.getString(10)).isEqualTo("App info");
443        // Class Name
444        assertThat(cursor.getString(11)).isEqualTo(className);
445        // Icon
446        assertThat(cursor.getInt(12)).isEqualTo(noIcon);
447        // Intent Action
448        assertThat(cursor.getString(13)).isEqualTo(action);
449        // Target Package
450        assertThat(cursor.getString(14)).isEqualTo(targetPackage);
451        // Target Class
452        assertThat(cursor.getString(15)).isEqualTo(targetClass);
453        // Enabled
454        assertThat(cursor.getInt(16) == 1).isEqualTo(enabled);
455        // Data ref key
456        assertThat(cursor.getString(17)).isEqualTo("toggle_advanced_settings");
457        // User Id
458        assertThat(cursor.getInt(18)).isEqualTo(userId);
459        // Payload Type - default is 0
460        assertThat(cursor.getInt(19)).isEqualTo(0);
461        // Payload - should be updated to real payloads as controllers are added
462        assertThat(cursor.getBlob(20)).isNull();
463    }
464
465    @Test
466    public void testAddResource_listPreference_rowsMatch() {
467        SearchIndexableResource resource = getFakeResource(R.xml.application_settings);
468        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
469
470        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null);
471        cursor.moveToPosition(3);
472        // Locale
473        assertThat(cursor.getString(0)).isEqualTo(localeStr);
474        // Data Rank
475        assertThat(cursor.getInt(1)).isEqualTo(rank);
476        // Data Title
477        assertThat(cursor.getString(2)).isEqualTo("Preferred install location");
478        // Normalized Title
479        assertThat(cursor.getString(3)).isEqualTo("preferred install location");
480        // Summary On
481        assertThat(cursor.getString(4)).isEqualTo(
482                "Change the preferred installation location for new apps");
483        // Summary On Normalized
484        assertThat(cursor.getString(5)).isEqualTo(
485                "change the preferred installation location for new apps");
486        // Summary Off - only on for checkbox preferences
487        assertThat(cursor.getString(6)).isEmpty();
488        // Summary off normalized - only on for checkbox preferences
489        assertThat(cursor.getString(7)).isEmpty();
490        // Entries - only on for list preferences
491        assertThat(cursor.getString(8)).isEqualTo("Internal device storage|Removable SD card|" +
492                "Let the system decide|");
493        // Keywords
494        assertThat(cursor.getString(9)).isEmpty();
495        // Screen Title
496        assertThat(cursor.getString(10)).isEqualTo("App info");
497        // Class Name
498        assertThat(cursor.getString(11)).isEqualTo(className);
499        // Icon
500        assertThat(cursor.getInt(12)).isEqualTo(noIcon);
501        // Intent Action
502        assertThat(cursor.getString(13)).isEqualTo(action);
503        // Target Package
504        assertThat(cursor.getString(14)).isEqualTo(targetPackage);
505        // Target Class
506        assertThat(cursor.getString(15)).isEqualTo(targetClass);
507        // Enabled
508        assertThat(cursor.getInt(16) == 1).isEqualTo(enabled);
509        // Data ref key
510        assertThat(cursor.getString(17)).isEqualTo("app_install_location");
511        // User Id
512        assertThat(cursor.getInt(18)).isEqualTo(userId);
513        // Payload Type - default is 0
514        assertThat(cursor.getInt(19)).isEqualTo(0);
515        // Payload - should be updated to real payloads as controllers are added
516        assertThat(cursor.getBlob(20)).isNull();
517    }
518
519    @Test
520    public void testAddResource_iconAddedFromXml() {
521        SearchIndexableResource resource = getFakeResource(R.xml.connected_devices);
522        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
523
524        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null);
525        cursor.moveToPosition(0);
526
527        // Icon
528        assertThat(cursor.getInt(12)).isNotEqualTo(noIcon);
529    }
530
531    // Tests for the flow: IndexOneResource -> IndexFromProvider -> IndexFromResource ->
532    //                     UpdateOneRowWithFilteredData -> UpdateOneRow
533
534    @Test
535    public void testResourceProvider_rowInserted() {
536        SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings);
537        resource.xmlResId = 0;
538        resource.className = "com.android.settings.display.ScreenZoomSettings";
539
540        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
541        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
542        assertThat(cursor.getCount()).isEqualTo(1);
543    }
544
545    @Test
546    public void testResourceProvider_rowMatches() {
547        SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings);
548        resource.xmlResId = 0;
549        resource.className = "com.android.settings.display.ScreenZoomSettings";
550
551        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
552        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
553        cursor.moveToPosition(0);
554
555        // Locale
556        assertThat(cursor.getString(0)).isEqualTo(localeStr);
557        // Data Rank
558        assertThat(cursor.getInt(1)).isEqualTo(rank);
559        // Data Title
560        assertThat(cursor.getString(2)).isEqualTo("Display size");
561        // Normalized Title
562        assertThat(cursor.getString(3)).isEqualTo("display size");
563        // Summary On
564        assertThat(cursor.getString(4)).isEmpty();
565        // Summary On Normalized
566        assertThat(cursor.getString(5)).isEmpty();
567        // Summary Off - only on for checkbox preferences
568        assertThat(cursor.getString(6)).isEmpty();
569        // Summary off normalized - only on for checkbox preferences
570        assertThat(cursor.getString(7)).isEmpty();
571        // Entries - only on for list preferences
572        assertThat(cursor.getString(8)).isNull();
573        // Keywords
574        assertThat(cursor.getString(9)).isEqualTo("display density screen zoom scale scaling");
575        // Screen Title
576        assertThat(cursor.getString(10)).isEqualTo("Display size");
577        // Class Name
578        assertThat(cursor.getString(11))
579                .isEqualTo("com.android.settings.display.ScreenZoomSettings");
580        // Icon
581        assertThat(cursor.getInt(12)).isEqualTo(noIcon);
582        // Intent Action
583        assertThat(cursor.getString(13)).isNull();
584        // Target Package
585        assertThat(cursor.getString(14)).isNull();
586        // Target Class
587        assertThat(cursor.getString(15)).isNull();
588        // Enabled
589        assertThat(cursor.getInt(16) == 1).isEqualTo(enabled);
590        // Data ref key
591        assertThat(cursor.getString(17)).isNull();
592        // User Id
593        assertThat(cursor.getInt(18)).isEqualTo(userId);
594        // Payload Type - default is 0
595        assertThat(cursor.getInt(19)).isEqualTo(0);
596        // Payload - should be updated to real payloads as controllers are added
597        assertThat(cursor.getBlob(20)).isNull();
598    }
599
600    @Test
601    public void testResourceProvider_resourceRowInserted() {
602        SearchIndexableResource resource = getFakeResource(0);
603        resource.className = "com.android.settings.LegalSettings";
604
605        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
606        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
607        assertThat(cursor.getCount()).isEqualTo(6);
608    }
609
610    @Test
611    public void testResourceProvider_resourceRowMatches() {
612        SearchIndexableResource resource = getFakeResource(0);
613        resource.className = "com.android.settings.display.ScreenZoomSettings";
614
615        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
616        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null);
617        cursor.moveToPosition(0);
618
619        // Locale
620        assertThat(cursor.getString(0)).isEqualTo(localeStr);
621        // Data Rank
622        assertThat(cursor.getInt(1)).isEqualTo(rank);
623        // Data Title
624        assertThat(cursor.getString(2)).isEqualTo("Display size");
625        // Normalized Title
626        assertThat(cursor.getString(3)).isEqualTo("display size");
627        // Summary On
628        assertThat(cursor.getString(4)).isEmpty();
629        // Summary On Normalized
630        assertThat(cursor.getString(5)).isEmpty();
631        // Summary Off - only on for checkbox preferences
632        assertThat(cursor.getString(6)).isEmpty();
633        // Summary off normalized - only on for checkbox preferences
634        assertThat(cursor.getString(7)).isEmpty();
635        // Entries - only on for list preferences
636        assertThat(cursor.getString(8)).isNull();
637        // Keywords
638        assertThat(cursor.getString(9)).isEqualTo(
639                "display density screen zoom scale scaling");
640        // Screen Title
641        assertThat(cursor.getString(10)).isEqualTo("Display size");
642        // Class Name
643        assertThat(cursor.getString(11))
644                .isEqualTo("com.android.settings.display.ScreenZoomSettings");
645        // Icon
646        assertThat(cursor.getInt(12)).isEqualTo(noIcon);
647        // Intent Action
648        assertThat(cursor.getString(13)).isNull();
649        // Target Package
650        assertThat(cursor.getString(14)).isNull();
651        // Target Class
652        assertThat(cursor.getString(15)).isNull();
653        // Enabled
654        assertThat(cursor.getInt(16) == 1).isEqualTo(enabled);
655        // Data ref key
656        assertThat(cursor.getString(17)).isNull();
657        // User Id
658        assertThat(cursor.getInt(18)).isEqualTo(userId);
659        // Payload Type - default is 0
660        assertThat(cursor.getInt(19)).isEqualTo(0);
661        // Payload - should be updated to real payloads as controllers are added
662        assertThat(cursor.getBlob(20)).isNull();
663    }
664
665    @Test
666    public void testResourceProvider_disabledResource_rowsInserted() {
667        SearchIndexableResource resource = getFakeResource(0);
668        resource.className = "com.android.settings.LegalSettings";
669
670        mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
671                new HashMap<String, Set<String>>());
672
673        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
674        assertThat(cursor.getCount()).isEqualTo(1);
675        cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null);
676        assertThat(cursor.getCount()).isEqualTo(5);
677    }
678
679    @Test
680    public void testResource_withTitleAndSettingName_titleNotInserted() {
681        SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings);
682        mManager.indexFromResource(mDb, localeStr, resource, new ArrayList<String>());
683
684        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE" +
685                " enabled = 1", null);
686        assertThat(cursor.getCount()).isEqualTo(1);
687    }
688
689    // Test new public indexing flow
690
691    @Test
692    @Config(shadows = {ShadowDatabaseIndexingUtils.class,})
693    public void testPerformIndexing_fullIndex_getsDataFromProviders() {
694        DummyProvider provider = new DummyProvider();
695        provider.onCreate();
696        ShadowContentResolver.registerProvider(AUTHORITY_ONE, provider);
697
698        // Test that Indexables are added for Full indexing
699        when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt()))
700                .thenReturn(getDummyResolveInfo());
701
702        DatabaseIndexingManager manager =
703                spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
704        doReturn(true).when(manager).isFullIndex(anyString(), anyString());
705
706        manager.performIndexing();
707
708        verify(manager).addIndexablesFromRemoteProvider(PACKAGE_ONE, AUTHORITY_ONE);
709        verify(manager).updateDatabase(true /* isFullIndex */, Locale.getDefault().toString());
710    }
711
712    @Test
713    @Config(shadows = {ShadowDatabaseIndexingUtils.class,})
714    public void testPerformIndexing_incrementalIndex_noDataAdded() {
715        DummyProvider provider = new DummyProvider();
716        provider.onCreate();
717        ShadowContentResolver.registerProvider(AUTHORITY_ONE, provider);
718
719        // Test that Indexables are added for Full indexing
720        when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt()))
721                .thenReturn(getDummyResolveInfo());
722
723        DatabaseIndexingManager manager =
724                spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
725        doReturn(false).when(manager).isFullIndex(anyString(), anyString());
726
727        manager.mDataToProcess.dataToUpdate.clear();
728
729        manager.performIndexing();
730
731        verify(manager, times(0)).addDataToDatabase(any(SQLiteDatabase.class), anyString(),
732                anyList(), anyMap());
733        verify(manager, times(0)).addIndexablesFromRemoteProvider(PACKAGE_ONE, AUTHORITY_ONE);
734        verify(manager).updateDataInDatabase(any(SQLiteDatabase.class), anyMap());
735    }
736
737    @Test
738    @Config(shadows = {ShadowDatabaseIndexingUtils.class,})
739    public void testPerformIndexing_localeChanged_databaseDropped() {
740        DummyProvider provider = new DummyProvider();
741        provider.onCreate();
742        ShadowContentResolver.registerProvider(AUTHORITY_ONE, provider);
743
744        // Test that Indexables are added for Full indexing
745        when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt()))
746                .thenReturn(getDummyResolveInfo());
747
748        // Initialize the Manager
749        DatabaseIndexingManager manager =
750                spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
751        doReturn(true).when(manager).isFullIndex(anyString(), anyString());
752
753        // Insert data point which will be dropped
754        final String oldTitle = "This is French";
755        insertSpecialCase(oldTitle, true, "key");
756
757        // Add a data point to be added by the indexing
758        SearchIndexableRaw raw = new SearchIndexableRaw(mContext);
759        final String newTitle = "This is English";
760        raw.title = newTitle;
761        manager.mDataToProcess.dataToUpdate.add(raw);
762
763        manager.performIndexing();
764
765        // Assert that the New Title is inserted
766        final Cursor newCursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE data_title = '" +
767                newTitle + "'", null);
768        assertThat(newCursor.getCount()).isEqualTo(1);
769
770        // Assert that the Old Title is no longer in the database, since it was dropped
771        final Cursor oldCursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE data_title = '" +
772                oldTitle + "'", null);
773        assertThat(oldCursor.getCount()).isEqualTo(0);
774    }
775
776    @Test
777    @Config(shadows = {ShadowDatabaseIndexingUtils.class,})
778    public void testPerformIndexing_onOta_FullIndex() {
779        DummyProvider provider = new DummyProvider();
780        provider.onCreate();
781        ShadowContentResolver.registerProvider(
782                AUTHORITY_ONE, provider
783        );
784
785        // Test that Indexables are added for Full indexing
786        when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt()))
787                .thenReturn(getDummyResolveInfo());
788
789        DatabaseIndexingManager manager =
790                spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
791        doReturn(true).when(manager).isFullIndex(anyString(), anyString());
792
793        manager.performIndexing();
794
795        verify(manager).updateDatabase(true /* isFullIndex */, Locale.getDefault().toString());
796    }
797
798    @Test
799    @Config(shadows = {ShadowDatabaseIndexingUtils.class,})
800    public void testPerformIndexing_onOta_buildNumberIsCached() {
801        DummyProvider provider = new DummyProvider();
802        provider.onCreate();
803        ShadowContentResolver.registerProvider(
804                AUTHORITY_ONE, provider
805        );
806
807        // Test that Indexables are added for Full indexing
808        when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt()))
809                .thenReturn(getDummyResolveInfo());
810
811        DatabaseIndexingManager manager =
812                spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
813        doReturn(true).when(manager).isFullIndex(anyString(), anyString());
814
815        manager.performIndexing();
816
817        assertThat(IndexDatabaseHelper.getInstance(mContext).isBuildIndexed(mContext,
818                Build.FINGERPRINT)).isTrue();
819    }
820
821    @Test
822    public void testFullUpdatedDatabase_noData_addDataToDatabaseNotCalled() {
823        mManager.updateDatabase(true /* isFullIndex */, localeStr);
824        mManager.mDataToProcess.dataToUpdate.clear();
825        verify(mManager, times(0)).addDataToDatabase(any(SQLiteDatabase.class), anyString(),
826                anyList(), anyMap());
827    }
828
829    @Test
830    public void testFullUpdatedDatabase_updatedDataInDatabaseNotCalled() {
831        mManager.updateDatabase(true /* isFullIndex */, localeStr);
832        verify(mManager, times(0)).updateDataInDatabase(any(SQLiteDatabase.class), anyMap());
833    }
834
835    @Test
836    public void testLocaleUpdated_afterIndexing_localeNotAdded() {
837        mManager.updateDatabase(true /* isFullIndex */, localeStr);
838        assertThat(IndexDatabaseHelper.getInstance(mContext)
839                .isLocaleAlreadyIndexed(mContext, localeStr)).isFalse();
840    }
841
842    @Test
843    public void testLocaleUpdated_afterFullIndexing_localeAdded() {
844        mManager.performIndexing();
845        assertThat(IndexDatabaseHelper.getInstance(mContext)
846                .isLocaleAlreadyIndexed(mContext, localeStr)).isTrue();
847    }
848
849    @Test
850    public void testUpdateDatabase_newEligibleData_addedToDatabase() {
851        // Test that addDataToDatabase is called when dataToUpdate is non-empty
852        mManager.mDataToProcess.dataToUpdate.add(getFakeRaw());
853        mManager.updateDatabase(true /* isFullIndex */, localeStr);
854
855        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
856        cursor.moveToPosition(0);
857
858        // Locale
859        assertThat(cursor.getString(0)).isEqualTo(localeStr);
860        // Data Rank
861        assertThat(cursor.getInt(1)).isEqualTo(rank);
862        // Data Title
863        assertThat(cursor.getString(2)).isEqualTo(updatedTitle);
864        // Normalized Title
865        assertThat(cursor.getString(3)).isEqualTo(normalizedTitle);
866        // Summary On
867        assertThat(cursor.getString(4)).isEqualTo(updatedSummaryOn);
868        // Summary On Normalized
869        assertThat(cursor.getString(5)).isEqualTo(normalizedSummaryOn);
870        // Summary Off
871        assertThat(cursor.getString(6)).isEqualTo(updatedSummaryOff);
872        // Summary off normalized
873        assertThat(cursor.getString(7)).isEqualTo(normalizedSummaryOff);
874        // Entries
875        assertThat(cursor.getString(8)).isEqualTo(entries);
876        // Keywords
877        assertThat(cursor.getString(9)).isEqualTo(spaceDelimittedKeywords);
878        // Screen Title
879        assertThat(cursor.getString(10)).isEqualTo(screenTitle);
880        // Class Name
881        assertThat(cursor.getString(11)).isEqualTo(className);
882        // Icon
883        assertThat(cursor.getInt(12)).isEqualTo(iconResId);
884        // Intent Action
885        assertThat(cursor.getString(13)).isEqualTo(action);
886        // Target Package
887        assertThat(cursor.getString(14)).isEqualTo(targetPackage);
888        // Target Class
889        assertThat(cursor.getString(15)).isEqualTo(targetClass);
890        // Enabled
891        assertThat(cursor.getInt(16) == 1).isEqualTo(enabled);
892        // Data ref key
893        assertThat(cursor.getString(17)).isNotNull();
894        // User Id
895        assertThat(cursor.getInt(18)).isEqualTo(userId);
896        // Payload Type - default is 0
897        assertThat(cursor.getInt(19)).isEqualTo(0);
898        // Payload
899        assertThat(cursor.getBlob(20)).isNull();
900    }
901
902    @Test
903    public void testUpdateDataInDatabase_enabledResultsAreNonIndexable_becomeDisabled() {
904        // Both results are enabled, and then TITLE_ONE gets disabled.
905        final boolean enabled = true;
906        insertSpecialCase(TITLE_ONE, enabled, KEY_ONE);
907        insertSpecialCase(TITLE_TWO, enabled, KEY_TWO);
908        Map<String, Set<String>> niks = new ArrayMap<>();
909        Set<String> keys = new HashSet<>();
910        keys.add(KEY_ONE);
911        niks.put(targetPackage, keys);
912
913        mManager.updateDataInDatabase(mDb, niks);
914
915        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null);
916        cursor.moveToPosition(0);
917
918        assertThat(cursor.getString(2)).isEqualTo(TITLE_ONE);
919    }
920
921    @Test
922    public void testUpdateDataInDatabase_disabledResultsAreIndexable_becomeEnabled() {
923        // Both results are initially disabled, and then TITLE_TWO gets enabled.
924        final boolean enabled = false;
925        insertSpecialCase(TITLE_ONE, enabled, KEY_ONE);
926        insertSpecialCase(TITLE_TWO, enabled, KEY_TWO);
927        Map<String, Set<String>> niks = new ArrayMap<>();
928        Set<String> keys = new HashSet<>();
929        keys.add(KEY_ONE);
930        niks.put(targetPackage, keys);
931
932        mManager.updateDataInDatabase(mDb, niks);
933
934        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
935        cursor.moveToPosition(0);
936
937        assertThat(cursor.getString(2)).isEqualTo(TITLE_TWO);
938    }
939
940    @Test
941    @Config(shadows = {ShadowContentResolver.class})
942    public void testEmptyNonIndexableKeys_emptyDataKeyResources_addedToDatabase() {
943        insertSpecialCase(TITLE_ONE, true /* enabled */, null /* dataReferenceKey */);
944
945        mManager.updateDatabase(false, localeStr);
946
947        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
948        cursor.moveToPosition(0);
949        assertThat(cursor.getCount()).isEqualTo(1);
950        assertThat(cursor.getString(2)).isEqualTo(TITLE_ONE);
951    }
952
953    @Test
954    public void testUpdateAsyncTask_onPostExecute_performsCallback() {
955        IndexingCallback callback = mock(IndexingCallback.class);
956
957        DatabaseIndexingManager.IndexingTask task = mManager.new IndexingTask(callback);
958        task.execute();
959
960        Robolectric.flushForegroundThreadScheduler();
961
962        verify(callback).onIndexingFinished();
963    }
964
965    @Test
966    public void testUpdateAsyncTask_onPostExecute_setsIndexingComplete() {
967        SearchFeatureProviderImpl provider = new SearchFeatureProviderImpl();
968        DatabaseIndexingManager manager = spy(provider.getIndexingManager(mContext));
969        DatabaseIndexingManager.IndexingTask task = manager.new IndexingTask(null);
970        doNothing().when(manager).performIndexing();
971
972        task.execute();
973        Robolectric.flushForegroundThreadScheduler();
974
975        assertThat(provider.isIndexingComplete(mContext)).isTrue();
976    }
977
978    // Util functions
979
980    private SearchIndexableRaw getFakeRaw() {
981        return getFakeRaw(localeStr);
982    }
983
984    private SearchIndexableRaw getFakeRaw(String localeStr) {
985        SearchIndexableRaw data = new SearchIndexableRaw(mContext);
986        data.locale = new Locale(localeStr);
987        data.rank = rank;
988        data.title = title;
989        data.summaryOn = summaryOn;
990        data.summaryOff = summaryOff;
991        data.entries = entries;
992        data.keywords = keywords;
993        data.screenTitle = screenTitle;
994        data.className = className;
995        data.packageName = packageName;
996        data.iconResId = iconResId;
997        data.intentAction = action;
998        data.intentTargetPackage = targetPackage;
999        data.intentTargetClass = targetClass;
1000        data.key = key;
1001        data.userId = userId;
1002        data.enabled = enabled;
1003        return data;
1004    }
1005
1006    private SearchIndexableResource getFakeResource(int xml) {
1007        SearchIndexableResource sir = new SearchIndexableResource(mContext);
1008        sir.rank = rank;
1009        sir.xmlResId = xml;
1010        sir.className = className;
1011        sir.packageName = packageName;
1012        sir.iconResId = iconResId;
1013        sir.intentAction = action;
1014        sir.intentTargetPackage = targetPackage;
1015        sir.intentTargetClass = targetClass;
1016        sir.enabled = enabled;
1017        return sir;
1018    }
1019
1020    private Map<String, Set<String>> getNonIndexableKeys(String[] keys) {
1021        Map<String, Set<String>> niks = new HashMap<>();
1022        Set<String> keysList = new HashSet<>();
1023        keysList.addAll(Arrays.asList(keys));
1024        niks.put(packageName, keysList);
1025        return niks;
1026    }
1027
1028    private List<ResolveInfo> getDummyResolveInfo() {
1029        List<ResolveInfo> infoList = new ArrayList<>();
1030        ResolveInfo info = new ResolveInfo();
1031        info.providerInfo = new ProviderInfo();
1032        info.providerInfo.exported = true;
1033        info.providerInfo.authority = AUTHORITY_ONE;
1034        info.providerInfo.packageName = PACKAGE_ONE;
1035        infoList.add(info);
1036
1037        return infoList;
1038    }
1039
1040    // TODO move this method and its counterpart in CursorToSearchResultConverterTest into
1041    // a util class with public fields to assert values.
1042    private Cursor getDummyCursor() {
1043        MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
1044        final String BLANK = "";
1045
1046        ArrayList<String> item =
1047                new ArrayList<>(INDEXABLES_RAW_COLUMNS.length);
1048        item.add("42"); // Rank
1049        item.add(TITLE_ONE); // Title
1050        item.add(BLANK); // Summary on
1051        item.add(BLANK); // summary off
1052        item.add(BLANK); // entries
1053        item.add(BLANK); // keywords
1054        item.add(BLANK); // screen title
1055        item.add(BLANK); // classname
1056        item.add("123"); // Icon
1057        item.add(BLANK); // Intent action
1058        item.add(BLANK); // target package
1059        item.add(BLANK); // target class
1060        item.add(KEY_ONE); // Key
1061        item.add("-1"); // userId
1062        cursor.addRow(item);
1063
1064        return cursor;
1065    }
1066
1067    private void insertSpecialCase(String specialCase, boolean enabled, String key) {
1068
1069        ContentValues values = new ContentValues();
1070        values.put(IndexDatabaseHelper.IndexColumns.DOCID, specialCase.hashCode());
1071        values.put(IndexDatabaseHelper.IndexColumns.LOCALE, localeStr);
1072        values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
1073        values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, specialCase);
1074        values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, "");
1075        values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, "");
1076        values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, "");
1077        values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, "");
1078        values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, "");
1079        values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, "");
1080        values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, "");
1081        values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, "");
1082        values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves");
1083        values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, "");
1084        values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, targetPackage);
1085        values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
1086        values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
1087        values.put(IndexDatabaseHelper.IndexColumns.ENABLED, enabled);
1088        values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, key);
1089        values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
1090        values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
1091        values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, (String) null);
1092
1093        mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
1094    }
1095
1096    private class DummyProvider extends ContentProvider {
1097
1098        @Override
1099        public boolean onCreate() {
1100            return false;
1101        }
1102
1103        @Override
1104        public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
1105                @Nullable String selection, @Nullable String[] selectionArgs,
1106                @Nullable String sortOrder) {
1107            if (uri.toString().contains("xml")) {
1108                return null;
1109            }
1110            return getDummyCursor();
1111        }
1112
1113        @Override
1114        public String getType(@NonNull Uri uri) {
1115            return null;
1116        }
1117
1118        @Override
1119        public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
1120            return null;
1121        }
1122
1123        @Override
1124        public int delete(@NonNull Uri uri, @Nullable String selection,
1125                @Nullable String[] selectionArgs) {
1126            return 0;
1127        }
1128
1129        @Override
1130        public int update(@NonNull Uri uri, @Nullable ContentValues values,
1131                @Nullable String selection, @Nullable String[] selectionArgs) {
1132            return 0;
1133        }
1134    }
1135}
1136