Merge "Handle tap on intent based search results."
This commit is contained in:
committed by
Android (Google) Code Review
commit
1a71c05c7c
@@ -16,32 +16,37 @@
|
||||
|
||||
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.R;
|
||||
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.List;
|
||||
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* AsyncTask to retrieve Settings, First party app and any intent based results.
|
||||
*/
|
||||
public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
|
||||
private static final String LOG = "DatabaseResultLoader";
|
||||
private final String mQueryText;
|
||||
private final Context mContext;
|
||||
protected final SQLiteDatabase mDatabase;
|
||||
@@ -78,9 +83,11 @@ public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
|
||||
|
||||
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, " +
|
||||
"data_summary_off, data_entries, data_keywords, class_name, screen_title,"
|
||||
+ " icon, " +
|
||||
"intent_action, intent_target_package, intent_target_class, enabled, " +
|
||||
"data_key_reference FROM prefs_index WHERE prefs_index MATCH 'data_title:%s* " +
|
||||
"data_key_reference 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);
|
||||
}
|
||||
@@ -90,35 +97,89 @@ public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
|
||||
if (cursorResults == null) {
|
||||
return null;
|
||||
}
|
||||
final Map<String, Context> contextMap = new HashMap<>();
|
||||
final ArrayList<SearchResult> results = new ArrayList<>();
|
||||
|
||||
while (cursorResults.moveToNext()) {
|
||||
final String title = cursorResults.getString(Index.COLUMN_INDEX_TITLE);
|
||||
final String summaryOn = cursorResults.getString(COLUMN_INDEX_RAW_SUMMARY_ON);
|
||||
final ArrayList<String> breadcrumbs = new ArrayList<>();
|
||||
final int rank = cursorResults.getInt(COLUMN_INDEX_XML_RES_RANK);
|
||||
|
||||
final String intentString = cursorResults.getString(Index.COLUMN_INDEX_INTENT_ACTION);
|
||||
final IntentPayload intentPayload = new IntentPayload(new Intent(intentString));
|
||||
final int iconID = cursorResults.getInt(COLUMN_INDEX_RAW_ICON_RESID);
|
||||
Drawable icon;
|
||||
try {
|
||||
icon = mContext.getDrawable(iconID);
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
icon = mContext.getDrawable(R.drawable.ic_search_history);
|
||||
SearchResult result = buildSingleSearchResultFromCursor(contextMap, cursorResults);
|
||||
if (result != null) {
|
||||
results.add(result);
|
||||
}
|
||||
|
||||
SearchResult.Builder builder = new SearchResult.Builder();
|
||||
builder.addTitle(title)
|
||||
.addSummary(summaryOn)
|
||||
.addBreadcrumbs(breadcrumbs)
|
||||
.addRank(rank)
|
||||
.addIcon(icon)
|
||||
.addPayload(intentPayload);
|
||||
results.add(builder.build());
|
||||
}
|
||||
Collections.sort(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private SearchResult buildSingleSearchResultFromCursor(Map<String, Context> contextMap,
|
||||
Cursor cursor) {
|
||||
final String pkgName = cursor.getString(Index.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE);
|
||||
final String action = cursor.getString(Index.COLUMN_INDEX_INTENT_ACTION);
|
||||
final String title = cursor.getString(Index.COLUMN_INDEX_TITLE);
|
||||
final String summaryOn = cursor.getString(Index.COLUMN_INDEX_SUMMARY_ON);
|
||||
final String className = cursor.getString(Index.COLUMN_INDEX_CLASS_NAME);
|
||||
final int rank = cursor.getInt(Index.COLUMN_INDEX_RANK);
|
||||
final String key = cursor.getString(Index.COLUMN_INDEX_KEY);
|
||||
final String iconResStr = cursor.getString(Index.COLUMN_INDEX_ICON);
|
||||
|
||||
final ResultPayload payload;
|
||||
if (TextUtils.isEmpty(action)) {
|
||||
final String screenTitle = cursor.getString(Index.COLUMN_INDEX_SCREEN_TITLE);
|
||||
// Action is null, we will launch it as a sub-setting
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);
|
||||
final Intent intent = Utils.onBuildStartFragmentIntent(mContext,
|
||||
className, args, null, 0, screenTitle, false);
|
||||
payload = new IntentPayload(intent);
|
||||
} else {
|
||||
final Intent intent = new Intent(action);
|
||||
final String targetClass = cursor.getString(
|
||||
Index.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS);
|
||||
if (!TextUtils.isEmpty(pkgName) && !TextUtils.isEmpty(targetClass)) {
|
||||
final ComponentName component = new ComponentName(pkgName, targetClass);
|
||||
intent.setComponent(component);
|
||||
}
|
||||
intent.putExtra(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);
|
||||
payload = new IntentPayload(intent);
|
||||
}
|
||||
SearchResult.Builder builder = new SearchResult.Builder();
|
||||
builder.addTitle(title)
|
||||
.addSummary(summaryOn)
|
||||
.addRank(rank)
|
||||
.addIcon(getIconForPackage(contextMap, pkgName, className, iconResStr))
|
||||
.addPayload(payload);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private Drawable getIconForPackage(Map<String, Context> contextMap, String pkgName,
|
||||
String className, String iconResStr) {
|
||||
final int iconId = TextUtils.isEmpty(iconResStr)
|
||||
? 0 : Integer.parseInt(iconResStr);
|
||||
Drawable icon;
|
||||
Context packageContext;
|
||||
if (iconId == 0) {
|
||||
icon = null;
|
||||
} else {
|
||||
if (TextUtils.isEmpty(className) && !TextUtils.isEmpty(pkgName)) {
|
||||
packageContext = contextMap.get(pkgName);
|
||||
if (packageContext == null) {
|
||||
try {
|
||||
packageContext = mContext.createPackageContext(pkgName, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(LOG, "Cannot create Context for package: " + pkgName);
|
||||
return null;
|
||||
}
|
||||
contextMap.put(pkgName, packageContext);
|
||||
}
|
||||
} else {
|
||||
packageContext = mContext;
|
||||
}
|
||||
try {
|
||||
icon = packageContext.getDrawable(iconId);
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
icon = null;
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -44,6 +44,9 @@ public class IntentSearchViewHolder extends SearchViewHolder {
|
||||
titleView.setText(result.title);
|
||||
summaryView.setText(result.summary);
|
||||
iconView.setImageDrawable(result.icon);
|
||||
if (result.icon == null) {
|
||||
iconView.setBackgroundResource(R.drawable.empty_icon);
|
||||
}
|
||||
itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
@@ -31,8 +31,19 @@ public abstract class ResultPayload implements Parcelable {
|
||||
@IntDef({PayloadType.INLINE_SLIDER, PayloadType.INLINE_SWITCH, PayloadType.INTENT})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface PayloadType {
|
||||
/**
|
||||
* Resulting page will be started using an intent
|
||||
*/
|
||||
int INTENT = 0;
|
||||
|
||||
/**
|
||||
* Result is a inline widget, using a slider widget as UI.
|
||||
*/
|
||||
int INLINE_SLIDER = 1;
|
||||
|
||||
/**
|
||||
* Result is a inline widget, using a toggle widget as UI.
|
||||
*/
|
||||
int INLINE_SWITCH = 2;
|
||||
}
|
||||
|
||||
|
@@ -83,7 +83,6 @@ public class SearchResult implements Comparable<SearchResult> {
|
||||
payload = builder.mResultPayload;
|
||||
viewType = payload.getType();
|
||||
stableId = Objects.hash(title, summary, breadcrumbs, rank, icon, payload, viewType);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,7 +97,7 @@ public class SearchResult implements Comparable<SearchResult> {
|
||||
protected CharSequence mTitle;
|
||||
protected CharSequence mSummary;
|
||||
protected ArrayList<String> mBreadcrumbs;
|
||||
protected int mRank = -1;
|
||||
protected int mRank = 42;
|
||||
protected ResultPayload mResultPayload;
|
||||
protected Drawable mIcon;
|
||||
|
||||
@@ -118,10 +117,9 @@ public class SearchResult implements Comparable<SearchResult> {
|
||||
}
|
||||
|
||||
public Builder addRank(int rank) {
|
||||
if (rank < 0 || rank > 9) {
|
||||
rank = 42;
|
||||
}
|
||||
if (rank >= 0 && rank <= 9) {
|
||||
mRank = rank;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -139,10 +137,6 @@ public class SearchResult implements Comparable<SearchResult> {
|
||||
// Check that all of the mandatory fields are set.
|
||||
if (mTitle == null) {
|
||||
throw new IllegalArgumentException("SearchResult missing title argument");
|
||||
} else if (mRank == -1) {
|
||||
throw new IllegalArgumentException("SearchResult missing rank argument");
|
||||
} else if (mIcon == null) {
|
||||
throw new IllegalArgumentException("SearchResult missing icon argument");
|
||||
} else if (mResultPayload == null) {
|
||||
throw new IllegalArgumentException("SearchResult missing Payload argument");
|
||||
}
|
||||
|
@@ -22,20 +22,23 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsRobolectricTestRunner;
|
||||
import com.android.settings.SubSettings;
|
||||
import com.android.settings.TestConfig;
|
||||
import com.android.settings.gestures.GestureSettings;
|
||||
import com.android.settings.search2.DatabaseResultLoader;
|
||||
import com.android.settings.search2.IntentPayload;
|
||||
import com.android.settings.search2.ResultPayload;
|
||||
import com.android.settings.search2.ResultPayload.PayloadType;
|
||||
import com.android.settings.search2.SearchResult;
|
||||
import com.android.settings.R;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -47,6 +50,10 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
public class DatabaseResultLoaderTest {
|
||||
private DatabaseResultLoader mLoader;
|
||||
|
||||
private static final String[] COLUMNS = new String[]{"rank", "title", "summary_on",
|
||||
"summary off", "entries", "keywords", "class name", "screen title", "icon",
|
||||
"intent action", "target package", "target class", "enabled", "key", "user id"};
|
||||
|
||||
private static final String[] TITLES = new String[]{"title1", "title2", "title3"};
|
||||
private static final String SUMMARY = "SUMMARY";
|
||||
private static final int EXAMPLES = 3;
|
||||
@@ -107,6 +114,16 @@ public class DatabaseResultLoaderTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseCursor_NoIcon() {
|
||||
List<SearchResult> results = mLoader.parseCursorForSearch(
|
||||
getDummyCursor(false /* hasIcon */));
|
||||
for (int i = 0; i < EXAMPLES; i++) {
|
||||
Drawable resultDrawable = results.get(i).icon;
|
||||
assertThat(resultDrawable).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseCursor_MatchesPayloadType() {
|
||||
List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
|
||||
@@ -117,6 +134,33 @@ public class DatabaseResultLoaderTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseCursor_MatchesIntentForSubSettings() {
|
||||
MatrixCursor cursor = new MatrixCursor(COLUMNS);
|
||||
final String BLANK = "";
|
||||
cursor.addRow(new Object[]{
|
||||
0, // rank
|
||||
TITLES[0],
|
||||
SUMMARY,
|
||||
SUMMARY, // summary off
|
||||
BLANK, // entries
|
||||
BLANK, // Keywords
|
||||
GestureSettings.class.getName(),
|
||||
BLANK, // screen title
|
||||
null, // icon
|
||||
BLANK, // action
|
||||
null, // target package
|
||||
BLANK, // target class
|
||||
BLANK, // enabled
|
||||
BLANK, // key
|
||||
BLANK // user id
|
||||
});
|
||||
List<SearchResult> results = mLoader.parseCursorForSearch(cursor);
|
||||
IntentPayload payload = (IntentPayload) results.get(0).payload;
|
||||
Intent intent = payload.intent;
|
||||
assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseCursor_MatchesIntentPayload() {
|
||||
List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
|
||||
@@ -129,14 +173,15 @@ public class DatabaseResultLoaderTest {
|
||||
}
|
||||
|
||||
private MatrixCursor getDummyCursor() {
|
||||
String[] columns = new String[] {"rank", "title", "summary_on", "summary off", "entries",
|
||||
"keywords", "class name", "screen title", "icon", "intent action",
|
||||
"target package", "target class", "enabled", "key", "user id"};
|
||||
MatrixCursor cursor = new MatrixCursor(columns);
|
||||
return getDummyCursor(true /* hasIcon */);
|
||||
}
|
||||
|
||||
private MatrixCursor getDummyCursor(boolean hasIcon) {
|
||||
MatrixCursor cursor = new MatrixCursor(COLUMNS);
|
||||
final String BLANK = "";
|
||||
|
||||
for (int i = 0; i < EXAMPLES; i++) {
|
||||
ArrayList<String> item = new ArrayList<>(columns.length);
|
||||
ArrayList<String> item = new ArrayList<>(COLUMNS.length);
|
||||
item.add(Integer.toString(i));
|
||||
item.add(TITLES[i]);
|
||||
item.add(SUMMARY);
|
||||
@@ -145,7 +190,7 @@ public class DatabaseResultLoaderTest {
|
||||
item.add(BLANK); // keywords
|
||||
item.add(BLANK); // classname
|
||||
item.add(BLANK); // screen title
|
||||
item.add(Integer.toString(mIcon));
|
||||
item.add(hasIcon ? Integer.toString(mIcon) : null);
|
||||
item.add(mIntent.getAction());
|
||||
item.add(BLANK); // target package
|
||||
item.add(BLANK); // target class
|
||||
|
@@ -124,13 +124,7 @@ public class SearchResultBuilderTest {
|
||||
.addBreadcrumbs(mBreadcrumbs)
|
||||
.addPayload(mResultPayload);
|
||||
|
||||
SearchResult result = null;
|
||||
try {
|
||||
result = mBuilder.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// passes.
|
||||
}
|
||||
assertThat(result).isNull();
|
||||
assertThat(mBuilder.build()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Reference in New Issue
Block a user