Fork Search code to independantly develop and test search.

This is the start of the new search in Settings. It is a nearly complete
replacement of the old search code in a more modular and flexible
architecture. It is expanding the datasources that it queries, including
the same Settings database, which will now include more first party apps
and be extended to support inline results where the user can change
settings directly from the search view. Search will also fan out to
query new sources (local or remote), and is built in a way
such that adding additional sources is roughly the same amount of work
had they been added in the initial writing of this code.

Query interpretation will now be source-dependant, allowing for future
upgrades to fuzzy search where it is applicable.

Change-Id: Ib0bac1fe92bf8a662d33abf9a99bb6ee2090ec8f
Fixes: 32115225, 32378927
Test: make RunSettingsRoboTests
This commit is contained in:
Matthew Fritze
2016-10-24 10:12:49 -07:00
parent c26f6d3ab7
commit 0ed37c3513
29 changed files with 1861 additions and 8 deletions

View File

@@ -138,6 +138,8 @@ import com.android.settings.print.PrintSettingsFragment;
import com.android.settings.qstile.DevelopmentTiles;
import com.android.settings.search.DynamicIndexableContentMonitor;
import com.android.settings.search.Index;
import com.android.settings.search2.SearchFeatureProvider;
import com.android.settings.search2.SearchFragment;
import com.android.settings.sim.SimSettings;
import com.android.settings.system.SystemDashboardFragment;
import com.android.settings.tts.TextToSpeechSettings;
@@ -479,6 +481,8 @@ public class SettingsActivity extends SettingsDrawerActivity
private SearchResultsSummary mSearchResultsFragment;
private String mSearchQuery;
private SearchFeatureProvider mSearchFeatureProvider;
// Categories
private ArrayList<DashboardCategory> mCategories = new ArrayList<DashboardCategory>();
@@ -528,9 +532,14 @@ public class SettingsActivity extends SettingsDrawerActivity
}
MenuInflater inflater = getMenuInflater();
if (mSearchFeatureProvider.isEnabled()) {
mSearchFeatureProvider.setUpSearchMenu(menu, this);
return true;
}
inflater.inflate(R.menu.options_menu, menu);
// Cache the search query (can be overriden by the OnQueryTextListener)
// Cache the search query (can be overridden by the OnQueryTextListener)
final String query = mSearchQuery;
mSearchMenuItem = menu.findItem(R.id.search);
@@ -553,7 +562,6 @@ public class SettingsActivity extends SettingsDrawerActivity
mSearchMenuItem.expandActionView();
}
mSearchView.setQuery(query, true /* submit */);
return true;
}
@@ -596,8 +604,12 @@ public class SettingsActivity extends SettingsDrawerActivity
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
long startTime = System.currentTimeMillis();
mDashboardFeatureProvider =
FeatureFactory.getFactory(this).getDashboardFeatureProvider(this);
final FeatureFactory factory = FeatureFactory.getFactory(this);
mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
mSearchFeatureProvider = factory.getSearchFeatureProvider(this);
// Should happen before any call to getIntent()
getMetaData();
@@ -1274,19 +1286,24 @@ public class SettingsActivity extends SettingsDrawerActivity
return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
}
@Deprecated
@Override
public boolean onQueryTextSubmit(String query) {
switchToSearchResultsFragmentIfNeeded();
if (mSearchFeatureProvider.isEnabled()) {
return false;
}
mSearchQuery = query;
switchToSearchResultsFragmentIfNeeded();
return mSearchResultsFragment.onQueryTextSubmit(query);
}
@Deprecated
@Override
public boolean onQueryTextChange(String newText) {
mSearchQuery = newText;
if (mSearchResultsFragment == null) {
if (mSearchFeatureProvider.isEnabled() || mSearchResultsFragment == null) {
return false;
}
mSearchQuery = newText;
return mSearchResultsFragment.onQueryTextChange(newText);
}
@@ -1330,6 +1347,7 @@ public class SettingsActivity extends SettingsDrawerActivity
}
}
@Deprecated
private void switchToSearchResultsFragmentIfNeeded() {
if (mSearchResultsFragment != null) {
return;
@@ -1347,10 +1365,12 @@ public class SettingsActivity extends SettingsDrawerActivity
mSearchMenuItemExpanded = true;
}
@Deprecated
public void needToRevertToInitialFragment() {
mNeedToRevertToInitialFragment = true;
}
@Deprecated
private void revertToInitialFragment() {
mNeedToRevertToInitialFragment = false;
mSearchResultsFragment = null;

View File

@@ -27,6 +27,7 @@ import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.localepicker.LocaleFeatureProvider;
import com.android.settings.search2.SearchFeatureProvider;
/**
* Abstract class for creating feature controllers. Allows OEM implementations to define their own
@@ -80,6 +81,8 @@ public abstract class FeatureFactory {
public abstract EnterprisePrivacyFeatureProvider getEnterprisePrivacyFeatureProvider(
Context context);
public abstract SearchFeatureProvider getSearchFeatureProvider(Context context);
public static final class FactoryNotFoundException extends RuntimeException {
public FactoryNotFoundException(Throwable throwable) {
super("Unable to create factory. Did you misconfigure Proguard?", throwable);

View File

@@ -32,6 +32,8 @@ import com.android.settings.enterprise.EnterprisePrivacyFeatureProviderImpl;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.localepicker.LocaleFeatureProvider;
import com.android.settings.localepicker.LocaleFeatureProviderImpl;
import com.android.settings.search2.SearchFeatureProvider;
import com.android.settings.search2.SearchFeatureProviderImpl;
/**
* {@link FeatureFactory} implementation for AOSP Settings.
@@ -44,6 +46,7 @@ public final class FeatureFactoryImpl extends FeatureFactory {
private DashboardFeatureProviderImpl mDashboardFeatureProvider;
private LocaleFeatureProvider mLocaleFeatureProvider;
private EnterprisePrivacyFeatureProvider mEnterprisePrivacyFeatureProvider;
private SearchFeatureProvider mSearchFeatureProvider;
@Override
public SupportFeatureProvider getSupportFeatureProvider(Context context) {
@@ -91,9 +94,17 @@ public final class FeatureFactoryImpl extends FeatureFactory {
public EnterprisePrivacyFeatureProvider getEnterprisePrivacyFeatureProvider(Context context) {
if (mEnterprisePrivacyFeatureProvider == null) {
mEnterprisePrivacyFeatureProvider = new EnterprisePrivacyFeatureProviderImpl(context,
new DevicePolicyManagerWrapperImpl((DevicePolicyManager)context
new DevicePolicyManagerWrapperImpl((DevicePolicyManager) context
.getSystemService(Context.DEVICE_POLICY_SERVICE)));
}
return mEnterprisePrivacyFeatureProvider;
}
@Override
public SearchFeatureProvider getSearchFeatureProvider(Context context) {
if (mSearchFeatureProvider == null) {
mSearchFeatureProvider = new SearchFeatureProviderImpl(context);
}
return mSearchFeatureProvider;
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2016 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.search2;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.drawable.Drawable;
import android.support.annotation.VisibleForTesting;
import com.android.settings.search.Index;
import com.android.settings.search.IndexDatabaseHelper;
import com.android.settings.utils.AsyncLoader;
import com.android.settings.R;
import java.util.ArrayList;
import java.util.Collections;
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;
/**
* AsyncTask to retrieve Settings, First party app and any intent based results.
*/
public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
private final String mQueryText;
private final Context mContext;
protected final SQLiteDatabase mDatabase;
public DatabaseResultLoader(Context context, String queryText) {
super(context);
mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
mQueryText = queryText;
mContext = context;
}
@Override
protected void onDiscardResult(List<SearchResult> result) {
// TODO Search
}
@Override
public List<SearchResult> loadInBackground() {
if (mQueryText == null || mQueryText.isEmpty()) {
return null;
}
String query = getSQLQuery();
Cursor result = mDatabase.rawQuery(query, null);
return parseCursorForSearch(result);
}
@Override
protected boolean onCancelLoad() {
// TODO
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 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);
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public ArrayList<SearchResult> parseCursorForSearch(Cursor cursorResults) {
if (cursorResults == null) {
return null;
}
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.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;
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2016 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.search2;
import android.net.Uri;
import android.os.Parcel;
/**
* Payload for Inline Settings results represented by a Slider.
*/
public class InlineSliderPayload extends ResultPayload {
public final Uri uri;
private InlineSliderPayload(Parcel in) {
uri = in.readParcelable(InlineSliderPayload.class.getClassLoader());
}
public InlineSliderPayload(Uri newUri) {
uri = newUri;
}
@Override
public int getType() {
return PayloadType.INLINE_SLIDER;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(uri, flags);
}
public static final Creator<InlineSliderPayload> CREATOR = new Creator<InlineSliderPayload>() {
@Override
public InlineSliderPayload createFromParcel(Parcel in) {
return new InlineSliderPayload(in);
}
@Override
public InlineSliderPayload[] newArray(int size) {
return new InlineSliderPayload[size];
}
};
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2016 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.search2;
import android.content.Intent;
import android.os.Parcel;
import com.android.internal.annotations.VisibleForTesting;
/**
* Encapsulates the standard intent based results as seen in first party apps and Settings results.
*/
public class IntentPayload extends ResultPayload {
public final Intent intent;
private IntentPayload(Parcel in) {
intent = in.readParcelable(IntentPayload.class.getClassLoader());
}
public IntentPayload(Intent newIntent) {
intent = newIntent;
}
@ResultPayload.PayloadType public int getType() {
return PayloadType.INTENT;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(intent, flags);
}
public static final Creator<IntentPayload> CREATOR = new Creator<IntentPayload>() {
@Override
public IntentPayload createFromParcel(Parcel in) {
return new IntentPayload(in);
}
@Override
public IntentPayload[] newArray(int size) {
return new IntentPayload[size];
}
};
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2016 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.search2;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.settings.R;
/**
* ViewHolder for intent based search results.
* The DatabaseResultLoader is the primary use case for this ViewHolder.
*/
public class IntentSearchViewHolder extends SearchViewHolder {
public final TextView titleView;
public final TextView summaryView;
public final ImageView iconView;
public IntentSearchViewHolder(View view) {
super(view);
titleView = (TextView) view.findViewById(R.id.title);
summaryView = (TextView) view.findViewById(R.id.summary);
iconView= (ImageView) view.findViewById(R.id.icon);
}
public void onBind(SearchResult result) {
titleView.setText(result.title);
summaryView.setText(result.summary);
iconView.setImageDrawable(result.icon);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2016 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.search2;
import android.annotation.IntDef;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* A interface for search results types. Examples include Inline results, third party apps
* or any future possibilities.
*/
public abstract class ResultPayload implements Parcelable {
@IntDef({PayloadType.INLINE_SLIDER, PayloadType.INLINE_SWITCH, PayloadType.INTENT})
@Retention(RetentionPolicy.SOURCE)
public @interface PayloadType {
int INTENT = 0;
int INLINE_SLIDER = 1;
int INLINE_SWITCH = 2;
}
@ResultPayload.PayloadType public abstract int getType();
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2016 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.search2;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import com.android.settings.R;
public class SearchActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search_main);
FragmentManager fragmentManager = getFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.main_content);
if (fragment == null) {
fragmentManager.beginTransaction()
.add(R.id.main_content, new SearchFragment())
.commit();
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2016 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.search2;
import android.app.Activity;
import android.widget.SearchView;
import android.view.Menu;
/**
* FeatureProvider for Settings Search
*/
public interface SearchFeatureProvider {
/**
* @return true to use the new version of search
*/
boolean isEnabled();
/**
* Inserts the Menu items into Settings activity.
* @param menu Items will be inserted into this menu.
* @param activity The activity that precedes SearchActivity.
*/
void setUpSearchMenu(Menu menu, Activity activity);
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2016 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.search2;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.widget.SearchView;
import android.view.Menu;
import android.view.MenuItem;
import com.android.settings.R;
/**
* FeatureProvider for the refactored search code.
*/
public class SearchFeatureProviderImpl implements SearchFeatureProvider {
protected Context mContext;
public SearchFeatureProviderImpl(Context context) {
mContext = context;
}
@Override
public boolean isEnabled() {
return false;
}
@Override
public void setUpSearchMenu(Menu menu, final Activity activity) {
if (menu == null || activity == null) {
return;
}
String menuTitle = mContext.getString(R.string.search_menu);
MenuItem menuItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, menuTitle)
.setIcon(R.drawable.abc_ic_search_api_material)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
Intent intent = new Intent(activity, SearchActivity.class);
activity.startActivity(intent);
return true;
}
});
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright (C) 2016 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.search2;
import android.app.Activity;
import android.content.Loader;
import android.os.Bundle;
import android.app.LoaderManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Menu;
import android.view.MenuInflater;
import android.widget.SearchView;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import java.util.List;
public class SearchFragment extends InstrumentedFragment implements
SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener,
LoaderManager.LoaderCallbacks<List<SearchResult>> {
private static final int DATABASE_LOADER_ID = 0;
private SearchResultsAdapter mSearchAdapter;
private DatabaseResultLoader mSearchLoader;
private RecyclerView mResultsRecyclerView;
private SearchView mSearchView;
private MenuItem mSearchMenuItem;
private String mQuery;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mSearchAdapter = new SearchResultsAdapter();
final LoaderManager loaderManager = getLoaderManager();
loaderManager.initLoader(DATABASE_LOADER_ID, null, this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.search_panel_2, container, false);
mResultsRecyclerView = (RecyclerView) view.findViewById(R.id.list_results);
mResultsRecyclerView.setAdapter(mSearchAdapter);
mResultsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.search_options_menu, menu);
mSearchMenuItem = menu.findItem(R.id.search);
mSearchView = (SearchView) mSearchMenuItem.getActionView();
mSearchView.setOnQueryTextListener(this);
mSearchView.setMaxWidth(Integer.MAX_VALUE);
mSearchMenuItem.expandActionView();
}
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
// Return false to prevent the search box from collapsing.
return false;
}
@Override
public boolean onQueryTextChange(String query) {
if (query == null || query.equals(mQuery)) {
return false;
}
mQuery = query;
clearLoaders();
final LoaderManager loaderManager = getLoaderManager();
loaderManager.restartLoader(DATABASE_LOADER_ID, null, this);
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public Loader<List<SearchResult>> onCreateLoader(int id, Bundle args) {
final Activity activity = getActivity();
switch (id) {
case DATABASE_LOADER_ID:
mSearchLoader = new DatabaseResultLoader(activity, mQuery);
return mSearchLoader;
default:
return null;
}
}
@Override
public void onLoadFinished(Loader<List<SearchResult>> loader, List<SearchResult> data) {
if (data == null) {
return;
}
mSearchAdapter.mergeResults(data, loader.getClass().getName());
}
@Override
public void onLoaderReset(Loader<List<SearchResult>> loader) { }
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS;
}
private void clearLoaders() {
if (mSearchLoader != null) {
mSearchLoader.cancelLoad();
mSearchLoader = null;
}
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2016 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.search2;
import android.graphics.drawable.Drawable;
import java.util.ArrayList;
/**
* Dataclass as an interface for all Search Results.
*/
public class SearchResult implements Comparable<SearchResult> {
@Override
public int compareTo(SearchResult searchResult) {
if (searchResult == null) {
return -1;
}
return this.rank - searchResult.rank;
}
public static class Builder {
protected String mTitle;
protected String mSummary;
protected ArrayList<String> mBreadcrumbs;
protected int mRank = -1;
protected ResultPayload mResultPayload;
protected Drawable mIcon;
public Builder addTitle(String title) {
mTitle = title;
return this;
}
public Builder addSummary(String summary) {
mSummary = summary;
return this;
}
public Builder addBreadcrumbs(ArrayList<String> breadcrumbs) {
mBreadcrumbs = breadcrumbs;
return this;
}
public Builder addRank(int rank) {
if (rank < 0 || rank > 9) {
rank = 42;
}
mRank = rank;
return this;
}
public Builder addIcon(Drawable icon) {
mIcon = icon;
return this;
}
public Builder addPayload(ResultPayload payload) {
mResultPayload = payload;
return this;
}
public SearchResult build() {
// Check that all of the mandatory fields are set.
if (mTitle == null) {
throw new IllegalArgumentException("SearchResult missing title argument");
} else if (mSummary == null ) {
throw new IllegalArgumentException("SearchResult missing summary argument");
} else if (mBreadcrumbs == null){
throw new IllegalArgumentException("SearchResult missing breadcrumbs 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");
}
return new SearchResult(this);
}
}
/**
* The title of the result and main text displayed.
* Intent Results: Displays as the primary
*/
public final String title;
/**
* Summary / subtitle text
* Intent Results: Displays the text underneath the title
*/
final public String summary;
/**
* 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;
/**
* A suggestion for the ranking of the result.
* Based on Settings Rank:
* 1 is a near perfect match
* 9 is the weakest match
* TODO subject to change
*/
public final int rank;
/**
* Identifier for the recycler view adapter.
*/
@ResultPayload.PayloadType public final int viewType;
/**
* Metadata for the specific result types.
*/
public final ResultPayload payload;
/**
* Result's icon.
*/
public final Drawable icon;
private SearchResult(Builder builder) {
title = builder.mTitle;
summary = builder.mSummary;
breadcrumbs = builder.mBreadcrumbs;
rank = builder.mRank;
icon = builder.mIcon;
payload = builder.mResultPayload;
viewType = payload.getType();
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright (C) 2016 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.search2;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView.Adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.settings.R;
import com.android.settings.search2.ResultPayload.PayloadType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class SearchResultsAdapter extends Adapter<SearchViewHolder> {
private ArrayList<SearchResult> mSearchResults;
private HashMap<String, List<SearchResult>> mResultsMap;
public SearchResultsAdapter() {
mSearchResults = new ArrayList<>();
mResultsMap = new HashMap<>();
setHasStableIds(true);
}
public void mergeResults(List<SearchResult> freshResults, String loaderClassName) {
if (freshResults == null) {
return;
}
mResultsMap.put(loaderClassName, freshResults);
mSearchResults = mergeMappedResults();
notifyDataSetChanged();
}
private ArrayList<SearchResult> mergeMappedResults() {
ArrayList<SearchResult> mergedResults = new ArrayList<>();
for(String key : mResultsMap.keySet()) {
mergedResults.addAll(mResultsMap.get(key));
}
return mergedResults;
}
@Override
public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch(viewType) {
case PayloadType.INTENT:
View view = inflater.inflate(R.layout.search_intent_item, parent, false);
return new IntentSearchViewHolder(view);
case PayloadType.INLINE_SLIDER:
return null;
case PayloadType.INLINE_SWITCH:
return null;
default:
return null;
}
}
@Override
public void onBindViewHolder(SearchViewHolder holder, int position) {
SearchResult result = mSearchResults.get(position);
holder.onBind(result);
}
@Override
public long getItemId(int position) {
return super.getItemId(position);
}
@Override
public int getItemViewType(int position) {
return mSearchResults.get(position).viewType;
}
@Override
public int getItemCount() {
return mSearchResults.size();
}
@VisibleForTesting
public ArrayList<SearchResult> getSearchResults() {
return mSearchResults;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2016 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.search2;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* The ViewHolder for the Search RecyclerView.
* There are multiple search result types in the same Recycler view with different UI requirements.
* Some examples include Intent results, Inline results, and Help articles.
*/
public abstract class SearchViewHolder extends RecyclerView.ViewHolder {
public SearchViewHolder(View view) {
super(view);
}
public abstract void onBind(SearchResult result);
}