blob: e99b0ea4aaa4063fee51d024cf2c6676d8755a00 [file] [log] [blame]
/*
* 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.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
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.PackageManager;
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.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.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(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 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();
}
@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);
}
// 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_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);
}
// Util functions
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;
}
}