Remove the the indexing check for the index data's locale to match the locale of the device. We don't require locale for indexables, and we reindex on locale change. It had introduced a bug where when locale changed, the default us-en results would not be added to the db. Change-Id: I43a4284f5c23bc51ee3efdfcabe511eac2d3317d Fixes: 66916397 Fixes: 68380443 Test: robotests
496 lines
19 KiB
Java
496 lines
19 KiB
Java
/*
|
|
* 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.search;
|
|
|
|
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.anyString;
|
|
import static org.mockito.Matchers.anyBoolean;
|
|
import static org.mockito.Mockito.doNothing;
|
|
import static org.mockito.Mockito.doReturn;
|
|
import static org.mockito.Mockito.mock;
|
|
import static org.mockito.Mockito.spy;
|
|
import static org.mockito.Mockito.verify;
|
|
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.ApplicationInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.ProviderInfo;
|
|
import android.content.pm.ResolveInfo;
|
|
import android.database.Cursor;
|
|
import android.database.sqlite.SQLiteDatabase;
|
|
import android.os.Build;
|
|
import android.provider.SearchIndexableData;
|
|
import android.util.ArrayMap;
|
|
|
|
import com.android.settings.TestConfig;
|
|
import com.android.settings.search.indexing.PreIndexData;
|
|
import com.android.settings.testutils.DatabaseTestUtils;
|
|
import com.android.settings.testutils.FakeFeatureFactory;
|
|
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
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.Robolectric;
|
|
import org.robolectric.RuntimeEnvironment;
|
|
import org.robolectric.annotation.Config;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
@RunWith(SettingsRobolectricTestRunner.class)
|
|
@Config(
|
|
manifest = TestConfig.MANIFEST_PATH,
|
|
sdk = TestConfig.SDK_VERSION,
|
|
shadows = {
|
|
ShadowRunnableAsyncTask.class,
|
|
}
|
|
)
|
|
public class DatabaseIndexingManagerTest {
|
|
private final String localeStr = "en_US";
|
|
|
|
private final int rank = 8;
|
|
private final String title = "title\u2011title";
|
|
private final String updatedTitle = "title-title";
|
|
private final String normalizedTitle = "titletitle";
|
|
private final String summaryOn = "summary\u2011on";
|
|
private final String updatedSummaryOn = "summary-on";
|
|
private final String normalizedSummaryOn = "summaryon";
|
|
private final String summaryOff = "summary\u2011off";
|
|
private final String entries = "entries";
|
|
private final String keywords = "keywords, keywordss, keywordsss";
|
|
private final String spaceDelimittedKeywords = "keywords keywordss keywordsss";
|
|
private final String screenTitle = "screen title";
|
|
private final String className = "class name";
|
|
private final int iconResId = 0xff;
|
|
private final String action = "action";
|
|
private final String targetPackage = "target package";
|
|
private final String targetClass = "target class";
|
|
private final String packageName = "package name";
|
|
private final String key = "key";
|
|
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;
|
|
|
|
private final List<ResolveInfo> FAKE_PROVIDER_LIST = new ArrayList<>();
|
|
|
|
@Mock
|
|
private PackageManager mPackageManager;
|
|
|
|
@Before
|
|
public void setUp() {
|
|
MockitoAnnotations.initMocks(this);
|
|
mContext = spy(RuntimeEnvironment.application);
|
|
mManager = spy(new DatabaseIndexingManager(mContext));
|
|
mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
|
|
|
doReturn(mPackageManager).when(mContext).getPackageManager();
|
|
doReturn(FAKE_PROVIDER_LIST).when(mPackageManager)
|
|
.queryIntentContentProviders(any(Intent.class), anyInt());
|
|
FakeFeatureFactory.setupForTest(mContext);
|
|
}
|
|
|
|
@After
|
|
public void cleanUp() {
|
|
DatabaseTestUtils.clearDb(mContext);
|
|
}
|
|
|
|
@Test
|
|
public void testDatabaseSchema() {
|
|
Cursor dbCursor = mDb.query("prefs_index", null, null, null, null, null, null);
|
|
List<String> columnNames = new ArrayList<>(Arrays.asList(dbCursor.getColumnNames()));
|
|
// Note that docid is not included.
|
|
List<String> expColumnNames = Arrays.asList(
|
|
"locale",
|
|
"data_rank",
|
|
"data_title",
|
|
"data_title_normalized",
|
|
"data_summary_on",
|
|
"data_summary_on_normalized",
|
|
"data_summary_off",
|
|
"data_summary_off_normalized",
|
|
"data_entries",
|
|
"data_keywords",
|
|
"class_name",
|
|
"screen_title",
|
|
"intent_action",
|
|
"intent_target_package",
|
|
"intent_target_class",
|
|
"icon",
|
|
"enabled",
|
|
"data_key_reference",
|
|
"user_id",
|
|
"payload_type",
|
|
"payload"
|
|
);
|
|
// Prevent database schema regressions
|
|
assertThat(columnNames).containsAllIn(expColumnNames);
|
|
}
|
|
|
|
// Tests for the flow: IndexOneRaw -> UpdateOneRowWithFilteredData -> UpdateOneRow
|
|
|
|
@Test
|
|
public void testAddResource_withChildFragment_shouldUpdateSiteMapDb() {
|
|
// FIXME: This test was failing. (count = 6 at the end)
|
|
|
|
// SearchIndexableResource resource = getFakeResource(R.xml.network_and_internet);
|
|
// mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
|
|
// new HashMap<>());
|
|
// Cursor query = mDb.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS,
|
|
// null, null, null, null, null);
|
|
// query.moveToPosition(-1);
|
|
// int count = 0;
|
|
// while (query.moveToNext()) {
|
|
// count++;
|
|
// assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_CLASS)))
|
|
// .isEqualTo(className);
|
|
// assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_TITLE)))
|
|
// .isEqualTo(mContext.getString(R.string.network_dashboard_title));
|
|
// assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_CLASS)))
|
|
// .isNotEmpty();
|
|
// assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_TITLE)))
|
|
// .isNotEmpty();
|
|
// }
|
|
// assertThat(count).isEqualTo(5);
|
|
}
|
|
|
|
// Test new public indexing flow
|
|
|
|
@Test
|
|
public void testPerformIndexing_fullIndex_getsDataFromProviders() {
|
|
SearchIndexableRaw rawData = getFakeRaw();
|
|
PreIndexData data = getPreIndexData(rawData);
|
|
doReturn(data).when(mManager).getIndexDataFromProviders(anyList(), anyBoolean());
|
|
doReturn(true).when(mManager).isFullIndex(any(Context.class), anyString(), anyString(),
|
|
anyString());
|
|
|
|
mManager.performIndexing();
|
|
|
|
verify(mManager).updateDatabase(data, true /* isFullIndex */);
|
|
}
|
|
|
|
@Test
|
|
public void testPerformIndexing_fullIndex_databaseDropped() {
|
|
// Initialize the Manager and force rebuild
|
|
DatabaseIndexingManager manager =
|
|
spy(new DatabaseIndexingManager(mContext));
|
|
doReturn(false).when(mManager).isFullIndex(any(Context.class), anyString(), anyString(),
|
|
anyString());
|
|
|
|
// Insert data point which will be dropped
|
|
insertSpecialCase("Ceci n'est pas un pipe", true, "oui oui mon ami");
|
|
|
|
manager.performIndexing();
|
|
|
|
// Assert that the Old Title is no longer in the database, since it was dropped
|
|
final Cursor oldCursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
|
|
|
assertThat(oldCursor.getCount()).isEqualTo(0);
|
|
}
|
|
|
|
@Test
|
|
public void testPerformIndexing_isfullIndex() {
|
|
SearchIndexableRaw rawData = getFakeRaw();
|
|
PreIndexData data = getPreIndexData(rawData);
|
|
doReturn(data).when(mManager).getIndexDataFromProviders(anyList(), anyBoolean());
|
|
doReturn(true).when(mManager).isFullIndex(any(Context.class), anyString(), anyString(),
|
|
anyString());
|
|
|
|
mManager.performIndexing();
|
|
|
|
verify(mManager).updateDatabase(data, true /* isFullIndex */);
|
|
}
|
|
|
|
@Test
|
|
public void testPerformIndexing_onPackageChange_fullIndex() {
|
|
final List<ResolveInfo> providers = getDummyResolveInfo();
|
|
final String buildNumber = Build.FINGERPRINT;
|
|
final String locale = Locale.getDefault().toString();
|
|
skipFullIndex(providers);
|
|
|
|
// This snapshot is already indexed. Should return false
|
|
assertThat(mManager.isFullIndex(
|
|
mContext, locale, buildNumber,
|
|
IndexDatabaseHelper.buildProviderVersionedNames(providers)))
|
|
.isFalse();
|
|
|
|
// Change provider version number, this should trigger full index.
|
|
providers.get(0).providerInfo.applicationInfo.versionCode++;
|
|
|
|
assertThat(mManager.isFullIndex(mContext, locale, buildNumber,
|
|
IndexDatabaseHelper.buildProviderVersionedNames(providers)))
|
|
.isTrue();
|
|
}
|
|
|
|
@Test
|
|
public void testPerformIndexing_onOta_buildNumberIsCached() {
|
|
mManager.performIndexing();
|
|
|
|
assertThat(IndexDatabaseHelper.isBuildIndexed(mContext, Build.FINGERPRINT)).isTrue();
|
|
}
|
|
|
|
@Test
|
|
public void testLocaleUpdated_afterIndexing_localeNotAdded() {
|
|
PreIndexData emptydata = new PreIndexData();
|
|
mManager.updateDatabase(emptydata, true /* isFullIndex */);
|
|
|
|
assertThat(IndexDatabaseHelper.isLocaleAlreadyIndexed(mContext, localeStr)).isFalse();
|
|
}
|
|
|
|
@Test
|
|
public void testLocaleUpdated_afterFullIndexing_localeAdded() {
|
|
mManager.performIndexing();
|
|
|
|
assertThat(IndexDatabaseHelper.isLocaleAlreadyIndexed(mContext, localeStr)).isTrue();
|
|
}
|
|
|
|
@Test
|
|
public void testUpdateDatabase_newEligibleData_addedToDatabase() {
|
|
// Test that addDataToDatabase is called when dataToUpdate is non-empty
|
|
PreIndexData indexData = new PreIndexData();
|
|
indexData.dataToUpdate.add(getFakeRaw());
|
|
mManager.updateDatabase(indexData, true /* isFullIndex */);
|
|
|
|
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
|
cursor.moveToPosition(0);
|
|
|
|
// Locale
|
|
assertThat(cursor.getString(0)).isEqualTo(localeStr);
|
|
// 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);
|
|
// 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
|
|
byte[] payload = cursor.getBlob(20);
|
|
ResultPayload unmarshalledPayload = ResultPayloadUtils.unmarshall(payload,
|
|
ResultPayload.CREATOR);
|
|
assertThat(unmarshalledPayload).isInstanceOf(ResultPayload.class);
|
|
}
|
|
|
|
@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<String, Set<String>> niks = new ArrayMap<>();
|
|
Set<String> 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<String, Set<String>> niks = new ArrayMap<>();
|
|
Set<String> 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);
|
|
}
|
|
|
|
@Test
|
|
public void testEmptyNonIndexableKeys_emptyDataKeyResources_addedToDatabase() {
|
|
insertSpecialCase(TITLE_ONE, true /* enabled */, null /* dataReferenceKey */);
|
|
PreIndexData emptydata = new PreIndexData();
|
|
mManager.updateDatabase(emptydata, false /* needsReindexing */);
|
|
|
|
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
|
|
cursor.moveToPosition(0);
|
|
assertThat(cursor.getCount()).isEqualTo(1);
|
|
assertThat(cursor.getString(2)).isEqualTo(TITLE_ONE);
|
|
}
|
|
|
|
@Test
|
|
public void testUpdateAsyncTask_onPostExecute_performsCallback() {
|
|
IndexingCallback callback = mock(IndexingCallback.class);
|
|
|
|
DatabaseIndexingManager.IndexingTask task = mManager.new IndexingTask(callback);
|
|
task.execute();
|
|
|
|
Robolectric.flushForegroundThreadScheduler();
|
|
|
|
verify(callback).onIndexingFinished();
|
|
}
|
|
|
|
@Test
|
|
public void testUpdateAsyncTask_onPostExecute_setsIndexingComplete() {
|
|
SearchFeatureProviderImpl provider = new SearchFeatureProviderImpl();
|
|
DatabaseIndexingManager manager = spy(provider.getIndexingManager(mContext));
|
|
DatabaseIndexingManager.IndexingTask task = manager.new IndexingTask(null);
|
|
doNothing().when(manager).performIndexing();
|
|
|
|
task.execute();
|
|
Robolectric.flushForegroundThreadScheduler();
|
|
|
|
assertThat(provider.isIndexingComplete(mContext)).isTrue();
|
|
}
|
|
|
|
// Util functions
|
|
|
|
private void skipFullIndex(List<ResolveInfo> providers) {
|
|
IndexDatabaseHelper.setLocaleIndexed(mContext, Locale.getDefault().toString());
|
|
IndexDatabaseHelper.setBuildIndexed(mContext, Build.FINGERPRINT);
|
|
IndexDatabaseHelper.setProvidersIndexed(mContext,
|
|
IndexDatabaseHelper.buildProviderVersionedNames(providers));
|
|
}
|
|
|
|
private SearchIndexableRaw getFakeRaw() {
|
|
return getFakeRaw(localeStr);
|
|
}
|
|
|
|
private SearchIndexableRaw getFakeRaw(String localeStr) {
|
|
SearchIndexableRaw data = new SearchIndexableRaw(mContext);
|
|
data.locale = new Locale(localeStr);
|
|
data.rank = rank;
|
|
data.title = title;
|
|
data.summaryOn = summaryOn;
|
|
data.summaryOff = summaryOff;
|
|
data.entries = entries;
|
|
data.keywords = keywords;
|
|
data.screenTitle = screenTitle;
|
|
data.className = className;
|
|
data.packageName = packageName;
|
|
data.iconResId = iconResId;
|
|
data.intentAction = action;
|
|
data.intentTargetPackage = targetPackage;
|
|
data.intentTargetClass = targetClass;
|
|
data.key = key;
|
|
data.userId = userId;
|
|
data.enabled = enabled;
|
|
return data;
|
|
}
|
|
|
|
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 PreIndexData getPreIndexData(SearchIndexableData fakeData) {
|
|
PreIndexData data = new PreIndexData();
|
|
data.dataToUpdate.add(fakeData);
|
|
return data;
|
|
}
|
|
|
|
private List<ResolveInfo> getDummyResolveInfo() {
|
|
List<ResolveInfo> 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;
|
|
info.providerInfo.applicationInfo = new ApplicationInfo();
|
|
infoList.add(info);
|
|
|
|
return infoList;
|
|
}
|
|
}
|