diff --git a/src/com/android/settings/search2/DatabaseResultLoader.java b/src/com/android/settings/search2/DatabaseResultLoader.java index a4e614f3de2..b268f6a1b6e 100644 --- a/src/com/android/settings/search2/DatabaseResultLoader.java +++ b/src/com/android/settings/search2/DatabaseResultLoader.java @@ -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> { + private static final String LOG = "DatabaseResultLoader"; private final String mQueryText; private final Context mContext; protected final SQLiteDatabase mDatabase; @@ -65,7 +70,7 @@ public class DatabaseResultLoader extends AsyncLoader> { } String query = getSQLQuery(); - Cursor result = mDatabase.rawQuery(query, null); + Cursor result = mDatabase.rawQuery(query, null); return parseCursorForSearch(result); } @@ -78,10 +83,12 @@ public class DatabaseResultLoader extends AsyncLoader> { 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 FROM prefs_index WHERE prefs_index MATCH 'data_title:%s* " + - "OR data_title_normalized:%s* OR data_keywords:%s*' AND locale = 'en_US'", + "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* " + + "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> { if (cursorResults == null) { return null; } + final Map contextMap = new HashMap<>(); final ArrayList 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 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 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 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; + } + } diff --git a/src/com/android/settings/search2/IntentSearchViewHolder.java b/src/com/android/settings/search2/IntentSearchViewHolder.java index 0187c1c1dea..0ef27d01f7b 100644 --- a/src/com/android/settings/search2/IntentSearchViewHolder.java +++ b/src/com/android/settings/search2/IntentSearchViewHolder.java @@ -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) { diff --git a/src/com/android/settings/search2/ResultPayload.java b/src/com/android/settings/search2/ResultPayload.java index 3a4e47793d0..84df7b6f65b 100644 --- a/src/com/android/settings/search2/ResultPayload.java +++ b/src/com/android/settings/search2/ResultPayload.java @@ -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; } diff --git a/src/com/android/settings/search2/SearchResult.java b/src/com/android/settings/search2/SearchResult.java index 9fb250f5d48..5bf757f947f 100644 --- a/src/com/android/settings/search2/SearchResult.java +++ b/src/com/android/settings/search2/SearchResult.java @@ -83,7 +83,6 @@ public class SearchResult implements Comparable { 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 { protected CharSequence mTitle; protected CharSequence mSummary; protected ArrayList mBreadcrumbs; - protected int mRank = -1; + protected int mRank = 42; protected ResultPayload mResultPayload; protected Drawable mIcon; @@ -118,10 +117,9 @@ public class SearchResult implements Comparable { } public Builder addRank(int rank) { - if (rank < 0 || rank > 9) { - rank = 42; + if (rank >= 0 && rank <= 9) { + mRank = rank; } - mRank = rank; return this; } @@ -139,10 +137,6 @@ public class SearchResult implements Comparable { // 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"); } diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java index a744bb7aea9..1df7b1f09ef 100644 --- a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java +++ b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java @@ -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,7 +50,11 @@ import static com.google.common.truth.Truth.assertThat; public class DatabaseResultLoaderTest { private DatabaseResultLoader mLoader; - private static final String[] TITLES = new String[] {"title1", "title2", "title3"}; + 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; private static final Intent mIntent = new Intent("com.android.settings"); @@ -107,6 +114,16 @@ public class DatabaseResultLoaderTest { } } + @Test + public void testParseCursor_NoIcon() { + List 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 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 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 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 item = new ArrayList<>(columns.length); + ArrayList 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 diff --git a/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java b/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java index a0f4cc52c48..5649a95e5cd 100644 --- a/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java +++ b/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java @@ -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