Update DatabaseLoader querying.

Change the way the queries are built and sorted for the
database loader. It now constructs 3 queries that match
different columns. Each query returns their results
and gets sorted, then merged into the master list of
results.

Change-Id: Id643422ddfe26bfec89e04ee9f5832b99148e22a
Test: make RunSettingsRoboTests
Bug: 32904057, 32903623
This commit is contained in:
Matthew Fritze
2016-12-20 15:50:56 -08:00
parent d5e2bfcc7a
commit 7fda314980
7 changed files with 462 additions and 110 deletions

View File

@@ -35,10 +35,12 @@ import com.android.settings.search.Index;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_RANK;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_TITLE;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_CLASS_NAME;
@@ -63,21 +65,28 @@ class CursorToSearchResultConverter {
private final String TAG = "CursorConverter";
private final String mQueryText;
private final Context mContext;
public CursorToSearchResultConverter(Context context) {
private final Set<String> mKeys;
public CursorToSearchResultConverter(Context context, String queryText) {
mContext = context;
mKeys = new HashSet<>();
mQueryText = queryText;
}
public List<SearchResult> convertCursor(Cursor cursorResults) {
public List<SearchResult> convertCursor(Cursor cursorResults, int baseRank) {
if (cursorResults == null) {
return null;
}
final Map<String, Context> contextMap = new HashMap<>();
final ArrayList<SearchResult> results = new ArrayList<>();
final List<SearchResult> results = new ArrayList<>();
while (cursorResults.moveToNext()) {
SearchResult result = buildSingleSearchResultFromCursor(contextMap, cursorResults);
SearchResult result = buildSingleSearchResultFromCursor(contextMap, cursorResults,
baseRank);
if (result != null) {
results.add(result);
}
@@ -87,13 +96,22 @@ class CursorToSearchResultConverter {
}
private SearchResult buildSingleSearchResultFromCursor(Map<String, Context> contextMap,
Cursor cursor) {
Cursor cursor, int baseRank) {
final String docId = cursor.getString(COLUMN_INDEX_ID);
/* Make sure that this result has not yet been added as a result. Checking the docID
covers the case of multiple queries matching the same row, but we need to also to check
for potentially the same named or slightly varied names pointing to the same page.
*/
if (mKeys.contains(docId)) {
return null;
}
mKeys.add(docId);
final String pkgName = cursor.getString(COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE);
final String action = cursor.getString(COLUMN_INDEX_INTENT_ACTION);
final String title = cursor.getString(COLUMN_INDEX_TITLE);
final String summaryOn = cursor.getString(COLUMN_INDEX_SUMMARY_ON);
final String className = cursor.getString(COLUMN_INDEX_CLASS_NAME);
final int rank = cursor.getInt(COLUMN_INDEX_RANK);
final String key = cursor.getString(COLUMN_INDEX_KEY);
final String iconResStr = cursor.getString(COLUMN_INDEX_ICON);
final int payloadType = cursor.getInt(COLUMN_INDEX_PAYLOAD_TYPE);
@@ -109,9 +127,13 @@ class CursorToSearchResultConverter {
return null;
}
final List<String> breadcrumbs = getBreadcrumbs(cursor);
final int rank = getRank(breadcrumbs, baseRank);
final SearchResult.Builder builder = new SearchResult.Builder();
builder.addTitle(title)
.addSummary(summaryOn)
.addBreadcrumbs(breadcrumbs)
.addRank(rank)
.addIcon(getIconForPackage(contextMap, pkgName, className, iconResStr))
.addPayload(payload);
@@ -187,4 +209,24 @@ class CursorToSearchResultConverter {
}
return null;
}
private List<String> getBreadcrumbs(Cursor cursor) {
return new ArrayList<>();
}
/** Uses the breadcrumbs to determine the offset to the base rank.
* There are two checks
* A) If the query matches the highest level menu title
* B) If the query matches a subsequent menu title
*
* If the query matches A and B, the offset is 0.
* If the query matches A only, the offset is 1.
* If the query matches neither A nor B, the offset is 2.
* @param crumbs from the Information Architecture
* @param baseRank of the result. Lower if it's a better result.
* @return
*/
private int getRank(List<String> crumbs, int baseRank) {
return baseRank;
}
}

View File

@@ -108,15 +108,15 @@ public class DatabaseIndexingManager {
public boolean fullIndex;
public UpdateData() {
dataToUpdate = new ArrayList<SearchIndexableData>();
dataToDelete = new ArrayList<SearchIndexableData>();
nonIndexableKeys = new HashMap<String, List<String>>();
dataToUpdate = new ArrayList<>();
dataToDelete = new ArrayList<>();
nonIndexableKeys = new HashMap<>();
}
public UpdateData(DatabaseIndexingManager.UpdateData other) {
dataToUpdate = new ArrayList<SearchIndexableData>(other.dataToUpdate);
dataToDelete = new ArrayList<SearchIndexableData>(other.dataToDelete);
nonIndexableKeys = new HashMap<String, List<String>>(other.nonIndexableKeys);
dataToUpdate = new ArrayList<>(other.dataToUpdate);
dataToDelete = new ArrayList<>(other.dataToDelete);
nonIndexableKeys = new HashMap<>(other.nonIndexableKeys);
forceUpdate = other.forceUpdate;
fullIndex = other.fullIndex;
}

View File

@@ -16,31 +16,19 @@
package com.android.settings.search2;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.search.Index;
import com.android.settings.search.IndexDatabaseHelper;
import com.android.settings.utils.AsyncLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns;
import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX;
/**
* AsyncTask to retrieve Settings, First party app and any intent based results.
@@ -54,30 +42,68 @@ public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
private final CursorToSearchResultConverter mConverter;
/* These indices are used to match the columns of the this loader's SELECT statement.
These are not necessarily the same order or coverage as the schema defined in
These are not necessarily the same order nor similar coverage as the schema defined in
IndexDatabaseHelper */
public static final int COLUMN_INDEX_RANK = 0;
public static final int COLUMN_INDEX_TITLE = 1;
public static final int COLUMN_INDEX_SUMMARY_ON = 2;
public static final int COLUMN_INDEX_SUMMARY_OFF = 3;
public static final int COLUMN_INDEX_ENTRIES = 4;
public static final int COLUMN_INDEX_KEYWORDS = 5;
public static final int COLUMN_INDEX_CLASS_NAME = 6;
public static final int COLUMN_INDEX_SCREEN_TITLE = 7;
public static final int COLUMN_INDEX_ICON = 8;
public static final int COLUMN_INDEX_INTENT_ACTION = 9;
public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE = 10;
public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS = 11;
public static final int COLUMN_INDEX_ENABLED = 12;
public static final int COLUMN_INDEX_KEY = 13;
public static final int COLUMN_INDEX_PAYLOAD_TYPE = 14;
public static final int COLUMN_INDEX_PAYLOAD = 15;
static final int COLUMN_INDEX_ID = 0;
static final int COLUMN_INDEX_TITLE = 1;
static final int COLUMN_INDEX_SUMMARY_ON = 2;
static final int COLUMN_INDEX_SUMMARY_OFF = 3;
static final int COLUMN_INDEX_CLASS_NAME = 4;
static final int COLUMN_INDEX_SCREEN_TITLE = 5;
static final int COLUMN_INDEX_ICON = 6;
static final int COLUMN_INDEX_INTENT_ACTION = 7;
static final int COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE = 8;
static final int COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS = 9;
static final int COLUMN_INDEX_KEY = 10;
static final int COLUMN_INDEX_PAYLOAD_TYPE = 11;
static final int COLUMN_INDEX_PAYLOAD = 12;
public static final String[] SELECT_COLUMNS = {
IndexColumns.DOCID,
IndexColumns.DATA_TITLE,
IndexColumns.DATA_SUMMARY_ON,
IndexColumns.DATA_SUMMARY_OFF,
IndexColumns.CLASS_NAME,
IndexColumns.SCREEN_TITLE,
IndexColumns.ICON,
IndexColumns.INTENT_ACTION,
IndexColumns.INTENT_TARGET_PACKAGE,
IndexColumns.INTENT_TARGET_CLASS,
IndexColumns.DATA_KEY_REF,
IndexColumns.PAYLOAD_TYPE,
IndexColumns.PAYLOAD
};
public static final String[] MATCH_COLUMNS_PRIMARY = {
IndexColumns.DATA_TITLE,
IndexColumns.DATA_TITLE_NORMALIZED,
};
public static final String[] MATCH_COLUMNS_SECONDARY = {
IndexColumns.DATA_SUMMARY_ON,
IndexColumns.DATA_SUMMARY_ON_NORMALIZED,
IndexColumns.DATA_SUMMARY_OFF,
IndexColumns.DATA_SUMMARY_OFF_NORMALIZED,
};
public static final String[] MATCH_COLUMNS_TERTIARY = {
IndexColumns.DATA_KEYWORDS,
IndexColumns.DATA_ENTRIES
};
/**
* Base ranks defines the best possible rank based on what the query matches.
* If the query matches the title, the best rank it can be is 1
* If the query only matches the summary, the best rank it can be is 4
* If the query only matches keywords or entries, the best rank it can be is 7
*/
private static final int[] BASE_RANKS = {1, 4, 7};
public DatabaseResultLoader(Context context, String queryText) {
super(context);
mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
mQueryText = queryText;
mConverter = new CursorToSearchResultConverter(context);
mQueryText = cleanQuery(queryText);
mConverter = new CursorToSearchResultConverter(context, mQueryText);
}
@Override
@@ -91,10 +117,34 @@ public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
return null;
}
String query = getSQLQuery();
Cursor result = mDatabase.rawQuery(query, null);
final List<SearchResult> primaryResults;
final List<SearchResult> secondaryResults;
final List<SearchResult> tertiaryResults;
return mConverter.convertCursor(result);
primaryResults = query(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]);
secondaryResults = query(MATCH_COLUMNS_SECONDARY, BASE_RANKS[1]);
tertiaryResults = query(MATCH_COLUMNS_TERTIARY, BASE_RANKS[2]);
final List<SearchResult> results = new ArrayList<>(primaryResults.size()
+ secondaryResults.size()
+ tertiaryResults.size());
results.addAll(primaryResults);
results.addAll(secondaryResults);
results.addAll(tertiaryResults);
return results;
}
private List<SearchResult> query(String[] matchColumns, int baseRank) {
final String whereClause = buildWhereClause(matchColumns);
final String[] selection = new String[matchColumns.length];
final String query = "%" + mQueryText + "%";
Arrays.fill(selection, query);
final Cursor resultCursor = mDatabase.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause,
selection, null, null, null);
return mConverter.convertCursor(resultCursor, baseRank);
}
@Override
@@ -103,17 +153,24 @@ public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
return super.onCancelLoad();
}
protected String getSQLQuery() {
return String.format("SELECT data_rank, data_title, data_summary_on, " +
"data_summary_off, data_entries, data_keywords, class_name, screen_title,"
+ " icon, " +
"intent_action, intent_target_package, intent_target_class, enabled, " +
"data_key_reference, payload_type, payload FROM prefs_index WHERE prefs_index MATCH "
+ "'data_title:%s* " +
"OR data_title_normalized:%s* OR data_keywords:%s*' AND locale = 'en_US'",
mQueryText, mQueryText, mQueryText);
/**
* A generic method to make the query suitable for searching the database.
* @return the cleaned query string
*/
private static String cleanQuery(String query) {
return query.trim();
}
private static String buildWhereClause(String[] matchColumns) {
StringBuilder sb = new StringBuilder(" ");
final int count = matchColumns.length;
for (int n = 0; n < count; n++) {
sb.append(matchColumns[n]);
sb.append(" like ?");
if (n < count - 1) {
sb.append(" OR ");
}
}
return sb.toString();
}
}

View File

@@ -18,7 +18,7 @@ package com.android.settings.search2;
import android.graphics.drawable.Drawable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
@@ -42,7 +42,7 @@ public class SearchResult implements Comparable<SearchResult> {
* An ordered list of the information hierarchy.
* Intent Results: Displayed a hierarchy of selections to reach the setting from the home screen
*/
public final ArrayList<String> breadcrumbs;
public final List<String> breadcrumbs;
/**
* A suggestion for the ranking of the result.
@@ -96,7 +96,7 @@ public class SearchResult implements Comparable<SearchResult> {
public static class Builder {
protected CharSequence mTitle;
protected CharSequence mSummary;
protected ArrayList<String> mBreadcrumbs;
protected List<String> mBreadcrumbs;
protected int mRank = 42;
protected ResultPayload mResultPayload;
protected Drawable mIcon;
@@ -111,7 +111,7 @@ public class SearchResult implements Comparable<SearchResult> {
return this;
}
public Builder addBreadcrumbs(ArrayList<String> breadcrumbs) {
public Builder addBreadcrumbs(List<String> breadcrumbs) {
mBreadcrumbs = breadcrumbs;
return this;
}