From ebb5b7f73d24e060ffb838d0fc4c6301eb900923 Mon Sep 17 00:00:00 2001 From: Matthew Fritze Date: Mon, 23 Jan 2017 19:33:34 -0800 Subject: [PATCH] SearchFragment removes stale data from the database When search fragment is created, it will get a list of the non-indexable keys and verify that each of the results are not a part of that list. This CL moves the non-indexable keys logic into a controller for shared use between SearchFragment and DatabaseIndexingManager. Bug:33209418 Test: make RunSettingsRoboTests Change-Id: I4ed3812ecc5ee9e63b75ba6edbc7ff8712e8e9c9 --- .../search2/DatabaseIndexingManager.java | 527 ++++++++++-------- .../search2/DatabaseIndexingManagerTest.java | 402 +++++++++++-- .../shadow/ShadowContentResolver.java | 1 - .../shadow/ShadowDatabaseIndexingUtils.java | 35 ++ .../shadow/ShadowRunnableAsyncTask.java | 39 ++ 5 files changed, 717 insertions(+), 287 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/testutils/shadow/ShadowDatabaseIndexingUtils.java create mode 100644 tests/robotests/src/com/android/settings/testutils/shadow/ShadowRunnableAsyncTask.java diff --git a/src/com/android/settings/search2/DatabaseIndexingManager.java b/src/com/android/settings/search2/DatabaseIndexingManager.java index d6e6959a728..6e91f5c0073 100644 --- a/src/com/android/settings/search2/DatabaseIndexingManager.java +++ b/src/com/android/settings/search2/DatabaseIndexingManager.java @@ -27,7 +27,6 @@ import android.content.res.XmlResourceParser; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteFullException; import android.net.Uri; import android.os.AsyncTask; import android.provider.SearchIndexableData; @@ -53,10 +52,12 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE; @@ -82,9 +83,40 @@ import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INT import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK; import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.CLASS_NAME; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_ENTRIES; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_KEY_REF; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_RANK; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_TITLE; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DOCID; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.ENABLED; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.ICON; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.INTENT_ACTION; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.LOCALE; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.PAYLOAD; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.SCREEN_TITLE; +import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.USER_ID; +import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX; + +import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID; +import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE; +import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_KEY; +import static com.android.settings.search2.DatabaseResultLoader.SELECT_COLUMNS; + /** * Consumes the SearchIndexableProvider content providers. * Updates the Resource, Raw Data and non-indexable data for Search. + * + * TODO this class needs to be refactored by moving most of its methods into controllers */ public class DatabaseIndexingManager { private static final String LOG_TAG = "DatabaseIndexingManager"; @@ -93,51 +125,14 @@ public class DatabaseIndexingManager { private static final String NODE_NAME_CHECK_BOX_PREFERENCE = "CheckBoxPreference"; private static final String NODE_NAME_LIST_PREFERENCE = "ListPreference"; - private static final List EMPTY_LIST = Collections.emptyList(); + private static final List EMPTY_LIST = Collections.emptyList(); private final String mBaseAuthority; - /** - * A private class to describe the update data for the Index database - */ - private static class UpdateData { - public List dataToUpdate; - public List dataToDelete; - public Map> nonIndexableKeys; - - public boolean forceUpdate; - public boolean fullIndex; - - public UpdateData() { - dataToUpdate = new ArrayList<>(); - dataToDelete = new ArrayList<>(); - nonIndexableKeys = new HashMap<>(); - } - - public UpdateData(DatabaseIndexingManager.UpdateData other) { - dataToUpdate = new ArrayList<>(other.dataToUpdate); - dataToDelete = new ArrayList<>(other.dataToDelete); - nonIndexableKeys = new HashMap<>(other.nonIndexableKeys); - forceUpdate = other.forceUpdate; - fullIndex = other.fullIndex; - } - - public DatabaseIndexingManager.UpdateData copy() { - return new DatabaseIndexingManager.UpdateData(this); - } - - public void clear() { - dataToUpdate.clear(); - dataToDelete.clear(); - nonIndexableKeys.clear(); - forceUpdate = false; - fullIndex = false; - } - } - private final AtomicBoolean mIsAvailable = new AtomicBoolean(false); - private final DatabaseIndexingManager.UpdateData mDataToProcess = - new DatabaseIndexingManager.UpdateData(); + + @VisibleForTesting + final UpdateData mDataToProcess = new UpdateData(); private Context mContext; public DatabaseIndexingManager(Context context, String baseAuthority) { @@ -157,31 +152,201 @@ public class DatabaseIndexingManager { AsyncTask.execute(new Runnable() { @Override public void run() { - final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE); - List list = - mContext.getPackageManager().queryIntentContentProviders(intent, 0); - - final int size = list.size(); - for (int n = 0; n < size; n++) { - final ResolveInfo info = list.get(n); - if (!DatabaseIndexingUtils.isWellKnownProvider(info, mContext)) { - continue; - } - final String authority = info.providerInfo.authority; - final String packageName = info.providerInfo.packageName; - - addIndexablesFromRemoteProvider(packageName, authority); - addNonIndexablesKeysFromRemoteProvider(packageName, authority); - } - - mDataToProcess.fullIndex = true; - updateInternal(); + performIndexing(); } }); } - private boolean addIndexablesFromRemoteProvider(String packageName, String authority) { + /** + * Accumulate all data and non-indexable keys from each of the content-providers. + * Only the first indexing for the default language gets static search results - subsequent + * calls will only gather non-indexable keys. + */ + @VisibleForTesting + void performIndexing() { + final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE); + final List list = + mContext.getPackageManager().queryIntentContentProviders(intent, 0); + + final boolean isLocaleIndexed = isLocaleIndexed(); + + for (final ResolveInfo info : list) { + if (!DatabaseIndexingUtils.isWellKnownProvider(info, mContext)) { + continue; + } + final String authority = info.providerInfo.authority; + final String packageName = info.providerInfo.packageName; + + if (!isLocaleIndexed) { + addIndexablesFromRemoteProvider(packageName, authority); + } + addNonIndexablesKeysFromRemoteProvider(packageName, authority); + } + + final String localeStr = Locale.getDefault().toString(); + updateDatabase(isLocaleIndexed, localeStr); + } + + @VisibleForTesting + boolean isLocaleIndexed() { + final String locale = Locale.getDefault().toString(); + return IndexDatabaseHelper.getInstance(mContext).isLocaleAlreadyIndexed(mContext, locale); + } + + /** + * Adds new data to the database and verifies the correctness of the ENABLED column. + * First, the data to be updated and all non-indexable keys are copied locally. + * Then all new data to be added is inserted. + * Then search results are verified to have the correct value of enabled. + * Finally, we record that the locale has been indexed. + * + * @param isIncrementalUpdate true when the language has already been indexed. + * @param localeStr the default locale for the device. + */ + @VisibleForTesting + void updateDatabase(boolean isIncrementalUpdate, String localeStr) { + mIsAvailable.set(false); + final UpdateData copy; + + synchronized (mDataToProcess) { + copy = mDataToProcess.copy(); + mDataToProcess.clear(); + } + + final List dataToUpdate = copy.dataToUpdate; + final Map> nonIndexableKeys = copy.nonIndexableKeys; + + final SQLiteDatabase database = getWritableDatabase(); + if (database == null) { + Log.w(LOG_TAG, "Cannot indexDatabase Index as I cannot get a writable database"); + return; + } + try { + database.beginTransaction(); + + // Add new data from Providers at initial index time, or inserted later. + if (dataToUpdate.size() > 0) { + addDataToDatabase(database, localeStr, dataToUpdate, nonIndexableKeys); + } + + // Only check for non-indexable key updates after initial index. + // Enabled state with non-indexable keys is checked when items are first inserted. + if (isIncrementalUpdate) { + updateDataInDatabase(database, nonIndexableKeys); + } + + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); + } + // TODO Refactor: move the locale out of the helper class + IndexDatabaseHelper.setLocaleIndexed(mContext, localeStr); + + mIsAvailable.set(true); + } + + /** + * Inserts {@link SearchIndexableData} into the database. + * + * @param database where the data will be inserted. + * @param localeStr is the locale of the data to be inserted. + * @param dataToUpdate is a {@link List} of the data to be inserted. + * @param nonIndexableKeys is a {@link Map} from Package Name to a {@link Set} of keys which + * identify search results which should not be surfaced. + */ + @VisibleForTesting + void addDataToDatabase(SQLiteDatabase database, String localeStr, + List dataToUpdate, Map> nonIndexableKeys) { + final long current = System.currentTimeMillis(); + + for (SearchIndexableData data : dataToUpdate) { + try { + indexOneSearchIndexableData(database, localeStr, data, nonIndexableKeys); + } catch (Exception e) { + Log.e(LOG_TAG, "Cannot index: " + (data != null ? data.className : data) + + " for locale: " + localeStr, e); + } + } + + final long now = System.currentTimeMillis(); + Log.d(LOG_TAG, "Indexing locale '" + localeStr + "' took " + + (now - current) + " millis"); + } + + /** + * Upholds the validity of enabled data for the user. + * All rows which are enabled but are now flagged with non-indexable keys will become disabled. + * All rows which are disabled but no longer a non-indexable key will become enabled. + * + * @param database The database to validate. + * @param nonIndexableKeys A map between package name and the set of non-indexable keys for it. + */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + void updateDataInDatabase(SQLiteDatabase database, + Map> nonIndexableKeys) { + final String whereEnabled = ENABLED + " = 1"; + final String whereDisabled = ENABLED + " = 0"; + + final Cursor enabledResults = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, + whereEnabled, null, null, null, null); + + final ContentValues enabledToDisabledValue = new ContentValues(); + enabledToDisabledValue.put(ENABLED, 0); + + String packageName; + // TODO Refactor: Move these two loops into one method. + while (enabledResults.moveToNext()) { + // Package name is the key for remote providers. + // If package name is null, the provider is Settings. + packageName = enabledResults.getString(COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE); + if (packageName == null) { + packageName = mContext.getPackageName(); + } + + final String key = enabledResults.getString(COLUMN_INDEX_KEY); + final Set packageKeys = nonIndexableKeys.get(packageName); + + // The indexed item is set to Enabled but is now non-indexable + if (packageKeys != null && packageKeys.contains(key)) { + final String whereClause = DOCID + " = " + enabledResults.getInt(COLUMN_INDEX_ID); + database.update(TABLE_PREFS_INDEX, enabledToDisabledValue, whereClause, null); + } + } + enabledResults.close(); + + final Cursor disabledResults = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, + whereDisabled, null, null, null, null); + + final ContentValues disabledToEnabledValue = new ContentValues(); + disabledToEnabledValue.put(ENABLED, 1); + + while (disabledResults.moveToNext()) { + // Package name is the key for remote providers. + // If package name is null, the provider is Settings. + packageName = disabledResults.getString(COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE); + if (packageName == null) { + packageName = mContext.getPackageName(); + } + + final String key = disabledResults.getString(COLUMN_INDEX_KEY); + final Set packageKeys = nonIndexableKeys.get(packageName); + + // The indexed item is set to Disabled but is no longer non-indexable. + // We do not enable keys when packageKeys is null because it means the keys came + // from an unrecognized package and therefore should not be surfaced as results. + if (packageKeys != null && !packageKeys.contains(key)) { + String whereClause = DOCID + " = " + disabledResults.getInt(COLUMN_INDEX_ID); + database.update(TABLE_PREFS_INDEX, disabledToEnabledValue, whereClause, null); + } + } + disabledResults.close(); + } + + @VisibleForTesting + boolean addIndexablesFromRemoteProvider(String packageName, String authority) { + try { + // TODO delete base rank. does nothing. final int baseRank = Ranking.getBaseRankForAuthority(authority); final Context context = mBaseAuthority.equals(authority) ? @@ -202,11 +367,12 @@ public class DatabaseIndexingManager { } } - private void addNonIndexablesKeysFromRemoteProvider(String packageName, + @VisibleForTesting + void addNonIndexablesKeysFromRemoteProvider(String packageName, String authority) { final List keys = getNonIndexablesKeysFromRemoteProvider(packageName, authority); - addNonIndexableKeys(packageName, keys); + addNonIndexableKeys(packageName, new HashSet<>(keys)); } private List getNonIndexablesKeysFromRemoteProvider(String packageName, @@ -235,7 +401,7 @@ public class DatabaseIndexingManager { return EMPTY_LIST; } - List result = new ArrayList(); + final List result = new ArrayList<>(); try { final int count = cursor.getCount(); if (count > 0) { @@ -256,31 +422,19 @@ public class DatabaseIndexingManager { } } - public void deleteIndexableData(SearchIndexableData data) { - synchronized (mDataToProcess) { - mDataToProcess.dataToDelete.add(data); - } - } - - public void addNonIndexableKeys(String authority, List keys) { + public void addNonIndexableKeys(String authority, Set keys) { synchronized (mDataToProcess) { mDataToProcess.nonIndexableKeys.put(authority, keys); } } - private void updateFromRemoteProvider(String packageName, String authority) { - if (addIndexablesFromRemoteProvider(packageName, authority)) { - updateInternal(); - } - } - /** * Update the Index for a specific class name resources * - * @param className the class name (typically a fragment name). - * @param rebuild true means that you want to delete the data from the Index first. + * @param className the class name (typically a fragment name). + * @param rebuild true means that you want to delete the data from the Index first. * @param includeInSearchResults true means that you want the bit "enabled" set so that the - * data will be seen included into the search results + * data will be seen included into the search results */ public void updateFromClassNameResource(String className, final boolean rebuild, boolean includeInSearchResults) { @@ -297,12 +451,8 @@ public class DatabaseIndexingManager { AsyncTask.execute(new Runnable() { @Override public void run() { - if (rebuild) { - deleteIndexableData(res); - } addIndexableData(res); - mDataToProcess.forceUpdate = true; - updateInternal(); + performIndexing(); res.enabled = false; } }); @@ -313,8 +463,7 @@ public class DatabaseIndexingManager { @Override public void run() { addIndexableData(data); - mDataToProcess.forceUpdate = true; - updateInternal(); + performIndexing(); } }); } @@ -347,16 +496,6 @@ public class DatabaseIndexingManager { SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH); } - private void updateInternal() { - synchronized (mDataToProcess) { - final DatabaseIndexingManager.UpdateIndexTask task = - new DatabaseIndexingManager.UpdateIndexTask(); - DatabaseIndexingManager.UpdateData copy = mDataToProcess.copy(); - task.execute(copy); - mDataToProcess.clear(); - } - } - private void addIndexablesForXmlResourceUri(Context packageContext, String packageName, Uri uri, String[] projection, int baseRank) { @@ -468,7 +607,7 @@ public class DatabaseIndexingManager { } public void indexOneSearchIndexableData(SQLiteDatabase database, String localeStr, - SearchIndexableData data, Map> nonIndexableKeys) { + SearchIndexableData data, Map> nonIndexableKeys) { if (data instanceof SearchIndexableResource) { indexOneResource(database, localeStr, (SearchIndexableResource) data, nonIndexableKeys); } else if (data instanceof SearchIndexableRaw) { @@ -502,7 +641,7 @@ public class DatabaseIndexingManager { } private void indexOneResource(SQLiteDatabase database, String localeStr, - SearchIndexableResource sir, Map> nonIndexableKeysFromResource) { + SearchIndexableResource sir, Map> nonIndexableKeysFromResource) { if (sir == null) { Log.e(LOG_TAG, "Cannot index a null resource!"); @@ -512,9 +651,9 @@ public class DatabaseIndexingManager { final List nonIndexableKeys = new ArrayList(); if (sir.xmlResId > SearchIndexableResources.NO_DATA_RES_ID) { - List resNonIndxableKeys = nonIndexableKeysFromResource.get(sir.packageName); - if (resNonIndxableKeys != null && resNonIndxableKeys.size() > 0) { - nonIndexableKeys.addAll(resNonIndxableKeys); + Set resNonIndexableKeys = nonIndexableKeysFromResource.get(sir.packageName); + if (resNonIndexableKeys != null && resNonIndexableKeys.size() > 0) { + nonIndexableKeys.addAll(resNonIndexableKeys); } indexFromResource(database, localeStr, sir, nonIndexableKeys); @@ -605,6 +744,7 @@ public class DatabaseIndexingManager { headerKeywords = XmlParserUtils.getDataKeywords(context, attrs); enabled = !nonIndexableKeys.contains(key); + // TODO: Set payload type for header results DatabaseRow.Builder headerBuilder = new DatabaseRow.Builder(); headerBuilder.setLocale(localeStr) .setEntries(null) @@ -799,31 +939,29 @@ public class DatabaseIndexingManager { ContentValues values = new ContentValues(); values.put(IndexDatabaseHelper.IndexColumns.DOCID, row.getDocId()); - values.put(IndexDatabaseHelper.IndexColumns.LOCALE, row.locale); - values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, row.rank); - values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, row.updatedTitle); - values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, row.normalizedTitle); - values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, row.updatedSummaryOn); - values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, - row.normalizedSummaryOn); - values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, row.updatedSummaryOff); - values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, - row.normalizedSummaryOff); - values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, row.entries); - values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, row.spaceDelimitedKeywords); - values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, row.className); - values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, row.screenTitle); - values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, row.intentAction); - values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, row.intentTargetPackage); - values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, row.intentTargetClass); - values.put(IndexDatabaseHelper.IndexColumns.ICON, row.iconResId); - values.put(IndexDatabaseHelper.IndexColumns.ENABLED, row.enabled); - values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, row.key); - values.put(IndexDatabaseHelper.IndexColumns.USER_ID, row.userId); - values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, row.payloadType); - values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, row.payload); + values.put(LOCALE, row.locale); + values.put(DATA_RANK, row.rank); + values.put(DATA_TITLE, row.updatedTitle); + values.put(DATA_TITLE_NORMALIZED, row.normalizedTitle); + values.put(DATA_SUMMARY_ON, row.updatedSummaryOn); + values.put(DATA_SUMMARY_ON_NORMALIZED, row.normalizedSummaryOn); + values.put(DATA_SUMMARY_OFF, row.updatedSummaryOff); + values.put(DATA_SUMMARY_OFF_NORMALIZED, row.normalizedSummaryOff); + values.put(DATA_ENTRIES, row.entries); + values.put(DATA_KEYWORDS, row.spaceDelimitedKeywords); + values.put(CLASS_NAME, row.className); + values.put(SCREEN_TITLE, row.screenTitle); + values.put(INTENT_ACTION, row.intentAction); + values.put(INTENT_TARGET_PACKAGE, row.intentTargetPackage); + values.put(INTENT_TARGET_CLASS, row.intentTargetClass); + values.put(ICON, row.iconResId); + values.put(ENABLED, row.enabled); + values.put(DATA_KEY_REF, row.key); + values.put(USER_ID, row.userId); + values.put(PAYLOAD_TYPE, row.payloadType); + values.put(PAYLOAD, row.payload); - database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values); + database.replaceOrThrow(TABLE_PREFS_INDEX, null, values); if (!TextUtils.isEmpty(row.className) && !TextUtils.isEmpty(row.childClassName)) { ContentValues siteMapPair = new ContentValues(); @@ -839,129 +977,34 @@ public class DatabaseIndexingManager { } /** - * A private class for updating the Index database + * A private class to describe the indexDatabase data for the Index database */ - private class UpdateIndexTask extends AsyncTask { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + static class UpdateData { + public List dataToUpdate; + public List dataToDisable; + public Map> nonIndexableKeys; - @Override - protected void onPreExecute() { - super.onPreExecute(); - mIsAvailable.set(false); + public UpdateData() { + dataToUpdate = new ArrayList<>(); + dataToDisable = new ArrayList<>(); + nonIndexableKeys = new HashMap<>(); } - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - mIsAvailable.set(true); + public UpdateData(UpdateData other) { + dataToUpdate = new ArrayList<>(other.dataToUpdate); + dataToDisable = new ArrayList<>(other.dataToDisable); + nonIndexableKeys = new HashMap<>(other.nonIndexableKeys); } - @Override - protected Void doInBackground(DatabaseIndexingManager.UpdateData... params) { - try { - final List dataToUpdate = params[0].dataToUpdate; - final List dataToDelete = params[0].dataToDelete; - final Map> nonIndexableKeys = params[0].nonIndexableKeys; - - final boolean forceUpdate = params[0].forceUpdate; - final boolean fullIndex = params[0].fullIndex; - - final SQLiteDatabase database = getWritableDatabase(); - if (database == null) { - Log.e(LOG_TAG, "Cannot update Index as I cannot get a writable database"); - return null; - } - final String localeStr = Locale.getDefault().toString(); - - try { - database.beginTransaction(); - if (dataToDelete.size() > 0) { - processDataToDelete(database, localeStr, dataToDelete); - } - if (dataToUpdate.size() > 0) { - processDataToUpdate(database, localeStr, dataToUpdate, nonIndexableKeys, - forceUpdate); - } - database.setTransactionSuccessful(); - } finally { - database.endTransaction(); - } - if (fullIndex) { - IndexDatabaseHelper.setLocaleIndexed(mContext, localeStr); - } - } catch (SQLiteFullException e) { - Log.e(LOG_TAG, "Unable to index search, out of space", e); - } - - return null; + public UpdateData copy() { + return new UpdateData(this); } - private boolean processDataToUpdate(SQLiteDatabase database, String localeStr, - List dataToUpdate, Map> nonIndexableKeys, - boolean forceUpdate) { - - if (!forceUpdate && IndexDatabaseHelper.isLocaleAlreadyIndexed(mContext, localeStr)) { - Log.d(LOG_TAG, "Locale '" + localeStr + "' is already indexed"); - return true; - } - - boolean result = false; - final long current = System.currentTimeMillis(); - - final int count = dataToUpdate.size(); - for (int n = 0; n < count; n++) { - final SearchIndexableData data = dataToUpdate.get(n); - try { - indexOneSearchIndexableData(database, localeStr, data, nonIndexableKeys); - } catch (Exception e) { - Log.e(LOG_TAG, "Cannot index: " + (data != null ? data.className : data) - + " for locale: " + localeStr, e); - } - } - - final long now = System.currentTimeMillis(); - Log.d(LOG_TAG, "Indexing locale '" + localeStr + "' took " + - (now - current) + " millis"); - return result; - } - - private boolean processDataToDelete(SQLiteDatabase database, String localeStr, - List dataToDelete) { - - boolean result = false; - final long current = System.currentTimeMillis(); - - final int count = dataToDelete.size(); - for (int n = 0; n < count; n++) { - final SearchIndexableData data = dataToDelete.get(n); - if (data == null) { - continue; - } - if (!TextUtils.isEmpty(data.className)) { - delete(database, IndexDatabaseHelper.IndexColumns.CLASS_NAME, data.className); - } else { - if (data instanceof SearchIndexableRaw) { - final SearchIndexableRaw raw = (SearchIndexableRaw) data; - if (!TextUtils.isEmpty(raw.title)) { - delete(database, IndexDatabaseHelper.IndexColumns.DATA_TITLE, - raw.title); - } - } - } - } - - final long now = System.currentTimeMillis(); - Log.d(LOG_TAG, "Deleting data for locale '" + localeStr + "' took " + - (now - current) + " millis"); - return result; - } - - private int delete(SQLiteDatabase database, String columName, String value) { - final String whereClause = columName + "=?"; - final String[] whereArgs = new String[]{value}; - - return database.delete(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, whereClause, - whereArgs); + public void clear() { + dataToUpdate.clear(); + dataToDisable.clear(); + nonIndexableKeys.clear(); } } diff --git a/tests/robotests/src/com/android/settings/search2/DatabaseIndexingManagerTest.java b/tests/robotests/src/com/android/settings/search2/DatabaseIndexingManagerTest.java index fdc1052c949..d537bc969a5 100644 --- a/tests/robotests/src/com/android/settings/search2/DatabaseIndexingManagerTest.java +++ b/tests/robotests/src/com/android/settings/search2/DatabaseIndexingManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * 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. @@ -17,37 +17,65 @@ package com.android.settings.search2; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentProvider; +import android.content.ContentValues; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; import android.database.Cursor; +import android.database.MatrixCursor; import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; import android.provider.SearchIndexableResource; - +import android.provider.SearchIndexablesContract; +import android.util.ArrayMap; import com.android.settings.R; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.search.IndexDatabaseHelper; import com.android.settings.search.SearchIndexableRaw; import com.android.settings.testutils.DatabaseTestUtils; - +import com.android.settings.testutils.shadow.ShadowDatabaseIndexingUtils; +import com.android.settings.testutils.shadow.ShadowRunnableAsyncTask; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowApplication; +import org.robolectric.shadows.ShadowContentResolver; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyList; +import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +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) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, + shadows={ShadowRunnableAsyncTask.class}) public class DatabaseIndexingManagerTest { private final String localeStr = "en_US"; @@ -75,15 +103,30 @@ public class DatabaseIndexingManagerTest { private final int userId = -1; private final boolean enabled = true; + private final String AUTHORITY_ONE = "authority"; + private final String PACKAGE_ONE = "com.android.settings"; + + private final String TITLE_ONE = "title one"; + private final String TITLE_TWO = "title two"; + private final String KEY_ONE = "key one"; + private final String KEY_TWO = "key two"; + private Context mContext; + private DatabaseIndexingManager mManager; private SQLiteDatabase mDb; + @Mock + private PackageManager mPackageManager; + @Before public void setUp() { - mContext = ShadowApplication.getInstance().getApplicationContext(); - mManager = spy(new DatabaseIndexingManager(mContext, mContext.getPackageName())); + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mManager = spy(new DatabaseIndexingManager(mContext,"com.android.settings")); mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase(); + + doReturn(mPackageManager).when(mContext).getPackageManager(); } @After @@ -126,7 +169,7 @@ public class DatabaseIndexingManagerTest { // Tests for the flow: IndexOneRaw -> UpdateOneRowWithFilteredData -> UpdateOneRow @Test - public void testInsertRawColumn_RowInserted() { + public void testInsertRawColumn_rowInserted() { SearchIndexableRaw raw = getFakeRaw(); mManager.indexOneSearchIndexableData(mDb, localeStr, raw, null /* Non-indexable keys */); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null); @@ -134,7 +177,7 @@ public class DatabaseIndexingManagerTest { } @Test - public void testInsertRawColumn_RowMatches() { + public void testInsertRawColumn_rowMatches() { SearchIndexableRaw raw = getFakeRaw(); mManager.indexOneSearchIndexableData(mDb, localeStr, raw, null /* Non-indexable keys */); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null); @@ -185,7 +228,7 @@ public class DatabaseIndexingManagerTest { } @Test - public void testInsertRawColumnMismatchedLocale_NoRowInserted() { + public void testInsertRawColumn_mismatchedLocale_noRowInserted() { SearchIndexableRaw raw = getFakeRaw("ca-fr"); mManager.indexOneSearchIndexableData(mDb, localeStr, raw, null /* Non-indexable keys */); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null); @@ -206,18 +249,18 @@ public class DatabaseIndexingManagerTest { @Test public void testAddResource_RowsInserted() { SearchIndexableResource resource = getFakeResource(R.xml.ia_display_settings); - mManager.indexOneSearchIndexableData(mDb, localeStr, resource, - new HashMap<>()); + mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>()); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null); assertThat(cursor.getCount()).isEqualTo(16); } @Test - public void testAddResourceWithNIKs_RowsInsertedDisabled() { + public void testAddResource_withNIKs_rowsInsertedDisabled() { SearchIndexableResource resource = getFakeResource(R.xml.ia_display_settings); // Only add 2 of 16 items to be disabled. String[] keys = {"brightness", "wallpaper"}; - Map> niks = getNonIndexableKeys(keys); + Map> niks = getNonIndexableKeys(keys); + mManager.indexOneSearchIndexableData(mDb, localeStr, resource, niks); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null); @@ -227,10 +270,9 @@ public class DatabaseIndexingManagerTest { } @Test - public void testAddResourceHeader_RowsMatch() { + public void testAddResourceHeader_rowsMatch() { SearchIndexableResource resource = getFakeResource(R.xml.application_settings); - mManager.indexOneSearchIndexableData(mDb, localeStr, resource, - new HashMap<>()); + mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>()); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null); cursor.moveToPosition(1); @@ -280,7 +322,7 @@ public class DatabaseIndexingManagerTest { } @Test - public void testAddResourceWithChildFragment_shouldUpdateSiteMapDb() { + public void testAddResource_withChildFragment_shouldUpdateSiteMapDb() { // FIXME: This test was failing. (count = 6 at the end) // SearchIndexableResource resource = getFakeResource(R.xml.network_and_internet); @@ -305,10 +347,9 @@ public class DatabaseIndexingManagerTest { } @Test - public void testAddResourceCustomSetting_RowsMatch() { + public void testAddResource_customSetting_rowsMatch() { SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings); - mManager.indexOneSearchIndexableData(mDb, localeStr, resource, - new HashMap<>()); + mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>()); final String prefTitle = mContext.getString(R.string.fingerprint_swipe_for_notifications_title); final String prefSummary = @@ -363,10 +404,9 @@ public class DatabaseIndexingManagerTest { } @Test - public void testAddResourceCheckboxPreference_RowsMatch() { + public void testAddResource_checkboxPreference_rowsMatch() { SearchIndexableResource resource = getFakeResource(R.xml.application_settings); - mManager.indexOneSearchIndexableData(mDb, localeStr, resource, - new HashMap<>()); + mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>()); /* Should return 6 results, with the following titles: * Advanced Settings, Apps, Manage Apps, Preferred install location, Running Services @@ -418,10 +458,9 @@ public class DatabaseIndexingManagerTest { } @Test - public void testAddResourceListPreference_RowsMatch() { + public void testAddResource_listPreference_rowsMatch() { SearchIndexableResource resource = getFakeResource(R.xml.application_settings); - mManager.indexOneSearchIndexableData(mDb, localeStr, resource, - new HashMap<>()); + mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>()); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null); cursor.moveToPosition(3); @@ -476,25 +515,23 @@ public class DatabaseIndexingManagerTest { // UpdateOneRowWithFilteredData -> UpdateOneRow @Test - public void testResourceProvider_RowInserted() { + public void testResourceProvider_rowInserted() { SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings); resource.xmlResId = 0; resource.className = "com.android.settings.display.ScreenZoomSettings"; - mManager.indexOneSearchIndexableData(mDb, localeStr, resource, - new HashMap<>()); + mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>()); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null); assertThat(cursor.getCount()).isEqualTo(1); } @Test - public void testResourceProvider_Matches() { + public void testResourceProvider_rowMatches() { SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings); resource.xmlResId = 0; resource.className = "com.android.settings.display.ScreenZoomSettings"; - mManager.indexOneSearchIndexableData(mDb, localeStr, resource, - new HashMap<>()); + mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>()); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null); cursor.moveToPosition(0); @@ -544,23 +581,21 @@ public class DatabaseIndexingManagerTest { } @Test - public void testResourceProvider_ResourceRowInserted() { + public void testResourceProvider_resourceRowInserted() { SearchIndexableResource resource = getFakeResource(0); resource.className = "com.android.settings.LegalSettings"; - mManager.indexOneSearchIndexableData(mDb, localeStr, resource, - new HashMap<>()); + mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>()); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null); assertThat(cursor.getCount()).isEqualTo(6); } @Test - public void testResourceProvider_ResourceRowMatches() { + public void testResourceProvider_resourceRowMatches() { SearchIndexableResource resource = getFakeResource(0); resource.className = "com.android.settings.display.ScreenZoomSettings"; - mManager.indexOneSearchIndexableData(mDb, localeStr, resource, - new HashMap<>()); + mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>()); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null); cursor.moveToPosition(0); @@ -611,12 +646,12 @@ public class DatabaseIndexingManagerTest { } @Test - public void testResourceProvider_DisabledResourceRowsInserted() { + public void testResourceProvider_disabledResource_rowsInserted() { SearchIndexableResource resource = getFakeResource(0); resource.className = "com.android.settings.LegalSettings"; mManager.indexOneSearchIndexableData(mDb, localeStr, resource, - new HashMap>()); + new HashMap>()); Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null); assertThat(cursor.getCount()).isEqualTo(2); @@ -625,7 +660,7 @@ public class DatabaseIndexingManagerTest { } @Test - public void testResourceWithTitleAndSettingName_TitleNotInserted() { + public void testResource_withTitleAndSettingName_titleNotInserted() { SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings); mManager.indexFromResource(mDb, localeStr, resource, new ArrayList()); @@ -634,6 +669,176 @@ public class DatabaseIndexingManagerTest { assertThat(cursor.getCount()).isEqualTo(1); } + // Test new public indexing flow + + @Test + @Config(shadows= { + ShadowDatabaseIndexingUtils.class, + }) + public void testPerformIndexing_fullIndex_getsDataFromProviders() { + DummyProvider provider = new DummyProvider(); + provider.onCreate(); + ShadowContentResolver.registerProvider( + AUTHORITY_ONE, provider + ); + + // Test that Indexables are added for Full indexing + when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt())) + .thenReturn(getDummyResolveInfo()); + + DatabaseIndexingManager manager = + spy(new DatabaseIndexingManager(mContext, "com.android.settings")); + doReturn(false).when(manager).isLocaleIndexed(); + + manager.performIndexing(); + + verify(manager).updateDatabase(false, Locale.getDefault().toString()); + + Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null); + cursor.moveToPosition(0); + + // Data Title + assertThat(cursor.getString(2)).isEqualTo(TITLE_ONE); + } + + @Test + @Config(shadows= { + ShadowDatabaseIndexingUtils.class, + }) + public void testPerformIndexing_incrementalIndex_noDataAdded() { + DummyProvider provider = new DummyProvider(); + provider.onCreate(); + ShadowContentResolver.registerProvider( + AUTHORITY_ONE, provider + ); + + // Test that Indexables are added for Full indexing + when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt())) + .thenReturn(getDummyResolveInfo()); + + DatabaseIndexingManager manager = + spy(new DatabaseIndexingManager(mContext, "com.android.settings")); + doReturn(true).when(manager).isLocaleIndexed(); + + manager.performIndexing(); + + final Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null); + + assertThat(cursor.getCount()).isEqualTo(0); + } + + @Test + public void testFullUpdatedDatabase_noData_addDataToDatabaseNotCalled() { + mManager.updateDatabase(false, localeStr); + mManager.mDataToProcess.dataToUpdate.clear(); + verify(mManager, times(0)).addDataToDatabase(any(SQLiteDatabase.class), anyString(), + anyList(), anyMap()); + } + + @Test + public void testFullUpdatedDatabase_updatedDataInDatabaseNotCalled() { + mManager.updateDatabase(false, localeStr); + verify(mManager, times(0)).updateDataInDatabase(any(SQLiteDatabase.class), anyMap()); + } + + @Test + public void testLocaleUpdated_afterIndexing_localeAdded() { + mManager.updateDatabase(false, localeStr); + assertThat(IndexDatabaseHelper.getInstance(mContext) + .isLocaleAlreadyIndexed(mContext, localeStr)).isTrue(); + } + + @Test + public void testUpdateDatabase_newEligibleData_addedToDatabase() { + // Test that addDataToDatabase is called when dataToUpdate is non-empty + mManager.mDataToProcess.dataToUpdate.add(getFakeRaw()); + mManager.updateDatabase(false, localeStr); + + Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null); + cursor.moveToPosition(0); + + // Locale + assertThat(cursor.getString(0)).isEqualTo(localeStr); + // Data Rank + assertThat(cursor.getInt(1)).isEqualTo(rank); + // Data Title + assertThat(cursor.getString(2)).isEqualTo(updatedTitle); + // Normalized Title + assertThat(cursor.getString(3)).isEqualTo(normalizedTitle); + // Summary On + assertThat(cursor.getString(4)).isEqualTo(updatedSummaryOn); + // Summary On Normalized + assertThat(cursor.getString(5)).isEqualTo(normalizedSummaryOn); + // Summary Off + assertThat(cursor.getString(6)).isEqualTo(updatedSummaryOff); + // Summary off normalized + assertThat(cursor.getString(7)).isEqualTo(normalizedSummaryOff); + // Entries + assertThat(cursor.getString(8)).isEqualTo(entries); + // Keywords + assertThat(cursor.getString(9)).isEqualTo(spaceDelimittedKeywords); + // Screen Title + assertThat(cursor.getString(10)).isEqualTo(screenTitle); + // Class Name + assertThat(cursor.getString(11)).isEqualTo(className); + // Icon + assertThat(cursor.getInt(12)).isEqualTo(iconResId); + // Intent Action + assertThat(cursor.getString(13)).isEqualTo(action); + // Target Package + assertThat(cursor.getString(14)).isEqualTo(targetPackage); + // Target Class + assertThat(cursor.getString(15)).isEqualTo(targetClass); + // Enabled + assertThat(cursor.getInt(16) == 1).isEqualTo(enabled); + // Data ref key + assertThat(cursor.getString(17)).isNotNull(); + // User Id + assertThat(cursor.getInt(18)).isEqualTo(userId); + // Payload Type - default is 0 + assertThat(cursor.getInt(19)).isEqualTo(0); + // Payload + assertThat(cursor.getBlob(20)).isNull(); + } + + @Test + public void testUpdateDataInDatabase_enabledResultsAreNonIndexable_becomeDisabled() { + // Both results are enabled, and then TITLE_ONE gets disabled. + final boolean enabled = true; + insertSpecialCase(TITLE_ONE, enabled, KEY_ONE); + insertSpecialCase(TITLE_TWO, enabled, KEY_TWO); + Map> niks = new ArrayMap<>(); + Set keys = new HashSet<>(); + keys.add(KEY_ONE); + niks.put(targetPackage, keys); + + mManager.updateDataInDatabase(mDb, niks); + + Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null); + cursor.moveToPosition(0); + + assertThat(cursor.getString(2)).isEqualTo(TITLE_ONE); + } + + @Test + public void testUpdateDataInDatabase_DisabledResultsAreIndexable_BecomeEnabled() { + // Both results are initially disabled, and then TITLE_TWO gets enabled. + final boolean enabled = false; + insertSpecialCase(TITLE_ONE, enabled, KEY_ONE); + insertSpecialCase(TITLE_TWO, enabled, KEY_TWO); + Map> niks = new ArrayMap<>(); + Set keys = new HashSet<>(); + keys.add(KEY_ONE); + niks.put(targetPackage, keys); + + mManager.updateDataInDatabase(mDb, niks); + + Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null); + cursor.moveToPosition(0); + + assertThat(cursor.getString(2)).isEqualTo(TITLE_TWO); + } + // Util functions private SearchIndexableRaw getFakeRaw() { @@ -676,10 +881,119 @@ public class DatabaseIndexingManagerTest { return sir; } - private Map> getNonIndexableKeys(String[] keys) { - Map> niks = new HashMap<>(); - List keysList = new ArrayList<>(Arrays.asList(keys)); + private Map> getNonIndexableKeys(String[] keys) { + Map> niks = new HashMap<>(); + Set keysList = new HashSet<>(); + keysList.addAll(Arrays.asList(keys)); niks.put(packageName, keysList); return niks; } + + private List getDummyResolveInfo() { + List infoList = new ArrayList<>(); + ResolveInfo info = new ResolveInfo(); + info.providerInfo = new ProviderInfo(); + info.providerInfo.exported = true; + info.providerInfo.authority = AUTHORITY_ONE; + info.providerInfo.packageName = PACKAGE_ONE; + infoList.add(info); + + return infoList; + } + + // TODO move this method and its counterpart in CursorToSearchResultConverterTest into + // a util class with public fields to assert values. + private Cursor getDummyCursor() { + MatrixCursor cursor = new MatrixCursor(SearchIndexablesContract.INDEXABLES_RAW_COLUMNS); + final String BLANK = ""; + + ArrayList item = + new ArrayList<>(SearchIndexablesContract.INDEXABLES_RAW_COLUMNS.length); + item.add("42"); // Rank + item.add(TITLE_ONE); // Title + item.add(BLANK); // Summary on + item.add(BLANK); // summary off + item.add(BLANK); // entries + item.add(BLANK); // keywords + item.add(BLANK); // screen title + item.add(BLANK); // classname + item.add("123"); // Icon + item.add(BLANK); // Intent action + item.add(BLANK); // target package + item.add(BLANK); // target class + item.add(KEY_ONE); // Key + item.add("-1"); // userId + cursor.addRow(item); + + return cursor; + } + + private void insertSpecialCase(String specialCase, boolean enabled, String key) { + + ContentValues values = new ContentValues(); + values.put(IndexDatabaseHelper.IndexColumns.DOCID, specialCase.hashCode()); + values.put(IndexDatabaseHelper.IndexColumns.LOCALE, localeStr); + values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1); + values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, specialCase); + values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_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, ""); + values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves"); + values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, ""); + values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, targetPackage); + values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, ""); + values.put(IndexDatabaseHelper.IndexColumns.ICON, ""); + values.put(IndexDatabaseHelper.IndexColumns.ENABLED, enabled); + values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, key); + 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 class DummyProvider extends ContentProvider { + + @Override + public boolean onCreate() { + return false; + } + + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] projection, + @Nullable String selection, @Nullable String[] selectionArgs, + @Nullable String sortOrder) { + if (uri.toString().contains("xml")) { + return null; + } + return getDummyCursor(); + } + + @Override + public String getType(@NonNull Uri uri) { + return null; + } + + @Override + public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + return null; + } + + @Override + public int delete(@NonNull Uri uri, @Nullable String selection, + @Nullable String[] selectionArgs) { + return 0; + } + + @Override + public int update(@NonNull Uri uri, @Nullable ContentValues values, + @Nullable String selection, @Nullable String[] selectionArgs) { + return 0; + } + } } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowContentResolver.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowContentResolver.java index bc43fc34e73..6ae695b97f6 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowContentResolver.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowContentResolver.java @@ -29,5 +29,4 @@ public class ShadowContentResolver { public static SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) { return new SyncAdapterType[0]; } - } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDatabaseIndexingUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDatabaseIndexingUtils.java new file mode 100644 index 00000000000..724b9c05ec2 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDatabaseIndexingUtils.java @@ -0,0 +1,35 @@ +/* + * 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.testutils.shadow; + +import android.content.Context; +import android.content.pm.ResolveInfo; +import com.android.settings.search2.DatabaseIndexingUtils; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** + * Shadow of {@link DatabaseIndexingUtils} + */ +@Implements(DatabaseIndexingUtils.class) +public class ShadowDatabaseIndexingUtils { + @Implementation + public static boolean isWellKnownProvider(ResolveInfo info, Context context) { + return true; + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRunnableAsyncTask.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRunnableAsyncTask.java new file mode 100644 index 00000000000..5a71b58ec7a --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRunnableAsyncTask.java @@ -0,0 +1,39 @@ +/* + * 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.testutils.shadow; + +import android.os.AsyncTask; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowAsyncTask; + +import java.util.concurrent.Executor; + +/** + * Shadow async task to handle runnables in roboletric + */ +@Implements(AsyncTask.class) +public class ShadowRunnableAsyncTask extends + ShadowAsyncTask { + + @Implementation + public AsyncTask executeOnExecutor(Executor executor, + Params... params) { + return super.execute(params); + } +} \ No newline at end of file