Merge "Refactor SoundSettings to use more preference controller."
diff --git a/src/com/android/settings/applications/PackageManagerWrapper.java b/src/com/android/settings/applications/PackageManagerWrapper.java
index d399115..6c783d8 100644
--- a/src/com/android/settings/applications/PackageManagerWrapper.java
+++ b/src/com/android/settings/applications/PackageManagerWrapper.java
@@ -18,6 +18,7 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import java.util.List;
@@ -29,24 +30,30 @@
* the API version supported by Robolectric.
*/
public interface PackageManagerWrapper {
+
+ /**
+ * Returns the real {@code PackageManager} object.
+ */
+ PackageManager getPackageManager();
+
/**
* Calls {@code PackageManager.getInstalledApplicationsAsUser()}.
*
- * @see android.content.pm.PackageManager.PackageManager#getInstalledApplicationsAsUser
+ * @see android.content.pm.PackageManager#getInstalledApplicationsAsUser
*/
List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId);
/**
* Calls {@code PackageManager.hasSystemFeature()}.
*
- * @see android.content.pm.PackageManager.PackageManager#hasSystemFeature
+ * @see android.content.pm.PackageManager#hasSystemFeature
*/
boolean hasSystemFeature(String name);
/**
* Calls {@code PackageManager.queryIntentActivitiesAsUser()}.
*
- * @see android.content.pm.PackageManager.PackageManager#queryIntentActivitiesAsUser
+ * @see android.content.pm.PackageManager#queryIntentActivitiesAsUser
*/
List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId);
}
diff --git a/src/com/android/settings/applications/PackageManagerWrapperImpl.java b/src/com/android/settings/applications/PackageManagerWrapperImpl.java
index 8966869..db1d30a 100644
--- a/src/com/android/settings/applications/PackageManagerWrapperImpl.java
+++ b/src/com/android/settings/applications/PackageManagerWrapperImpl.java
@@ -24,6 +24,7 @@
import java.util.List;
public class PackageManagerWrapperImpl implements PackageManagerWrapper {
+
private final PackageManager mPm;
public PackageManagerWrapperImpl(PackageManager pm) {
@@ -31,6 +32,11 @@
}
@Override
+ public PackageManager getPackageManager() {
+ return mPm;
+ }
+
+ @Override
public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
return mPm.getInstalledApplicationsAsUser(flags, userId);
}
diff --git a/src/com/android/settings/search2/DatabaseResultLoader.java b/src/com/android/settings/search2/DatabaseResultLoader.java
index aca94b1..a4e614f 100644
--- a/src/com/android/settings/search2/DatabaseResultLoader.java
+++ b/src/com/android/settings/search2/DatabaseResultLoader.java
@@ -23,10 +23,11 @@
import android.database.sqlite.SQLiteDatabase;
import android.graphics.drawable.Drawable;
import android.support.annotation.VisibleForTesting;
+
+import com.android.settings.R;
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;
@@ -107,7 +108,6 @@
icon = mContext.getDrawable(R.drawable.ic_search_history);
}
-
SearchResult.Builder builder = new SearchResult.Builder();
builder.addTitle(title)
.addSummary(summaryOn)
diff --git a/src/com/android/settings/search2/InstalledAppResultLoader.java b/src/com/android/settings/search2/InstalledAppResultLoader.java
new file mode 100644
index 0000000..449e52c
--- /dev/null
+++ b/src/com/android/settings/search2/InstalledAppResultLoader.java
@@ -0,0 +1,139 @@
+/*
+ * 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.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.utils.AsyncLoader;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Search loader for installed apps.
+ */
+public class InstalledAppResultLoader extends AsyncLoader<List<SearchResult>> {
+
+ private static final int NAME_NO_MATCH = -1;
+ private static final int NAME_EXACT_MATCH = 0;
+
+ private final String mQuery;
+ private final UserManager mUserManager;
+ private final PackageManagerWrapper mPackageManager;
+
+ public InstalledAppResultLoader(Context context, PackageManagerWrapper pmWrapper,
+ String query) {
+ super(context);
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mPackageManager = pmWrapper;
+ mQuery = query;
+ }
+
+ @Override
+ public List<SearchResult> loadInBackground() {
+ final List<SearchResult> results = new ArrayList<>();
+ final PackageManager pm = mPackageManager.getPackageManager();
+
+ for (UserInfo user : getUsersToCount()) {
+ final List<ApplicationInfo> apps =
+ mPackageManager.getInstalledApplicationsAsUser(
+ PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ | (user.isAdmin() ? PackageManager.MATCH_ANY_USER : 0),
+ user.id);
+ for (ApplicationInfo info : apps) {
+ if (info.isSystemApp()) {
+ continue;
+ }
+ final CharSequence label = info.loadLabel(pm);
+ final int wordDiff = getWordDifference(label.toString(), mQuery);
+ if (wordDiff == NAME_NO_MATCH) {
+ continue;
+ }
+ final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ .setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ .setData(Uri.fromParts("package", info.packageName, null));
+
+ final SearchResult.Builder builder = new SearchResult.Builder();
+ builder.addIcon(info.loadIcon(pm))
+ .addTitle(info.loadLabel(pm))
+ .addRank(wordDiff)
+ .addPayload(new IntentPayload(intent));
+ results.add(builder.build());
+ }
+ }
+ Collections.sort(results);
+ return results;
+ }
+
+ @Override
+ protected void onDiscardResult(List<SearchResult> result) {
+
+ }
+
+ private List<UserInfo> getUsersToCount() {
+ return mUserManager.getProfiles(UserHandle.myUserId());
+ }
+
+ /**
+ * Returns "difference" between appName and query string. appName must contain all
+ * characters from query, in the same order. If not, returns NAME_NO_MATCH. If they do match,
+ * returns an int value representing how different they are, NAME_EXACT_MATCH means they match
+ * perfectly, and larger values means they are less similar.
+ * <p/>
+ * Example:
+ * appName: Abcde, query: Abcde, Returns NAME_EXACT_MATCH
+ * appName: Abcde, query: ade, Returns 2
+ * appName: Abcde, query: ae, Returns 3
+ * appName: Abcde, query: ea, Returns NAME_NO_MATCH
+ * appName: Abcde, query: xyz, Returns NAME_NO_MATCH
+ */
+ private int getWordDifference(String appName, String query) {
+ if (TextUtils.isEmpty(appName) || TextUtils.isEmpty(query)) {
+ return NAME_NO_MATCH;
+ }
+ final char[] queryTokens = query.toString().toLowerCase().toCharArray();
+ final char[] valueText = appName.toLowerCase().toCharArray();
+ if (queryTokens.length > valueText.length) {
+ return NAME_NO_MATCH;
+ }
+ int i = 0;
+ int j = 0;
+ while (i < valueText.length && j < queryTokens.length) {
+ if (valueText[i++] == queryTokens[j]) {
+ j++;
+ }
+ }
+ if (j != queryTokens.length) {
+ return NAME_NO_MATCH;
+ }
+ // Use the diff in length as a proxy of how close the 2 words match. Value range from 0
+ // to infinity.
+ return valueText.length - queryTokens.length;
+ }
+}
diff --git a/src/com/android/settings/search2/IntentSearchViewHolder.java b/src/com/android/settings/search2/IntentSearchViewHolder.java
index 0b99d6e..0187c1c 100644
--- a/src/com/android/settings/search2/IntentSearchViewHolder.java
+++ b/src/com/android/settings/search2/IntentSearchViewHolder.java
@@ -15,9 +15,11 @@
*/
package com.android.settings.search2;
+import android.app.Fragment;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+
import com.android.settings.R;
/**
@@ -25,6 +27,7 @@
* 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;
@@ -33,12 +36,19 @@
super(view);
titleView = (TextView) view.findViewById(R.id.title);
summaryView = (TextView) view.findViewById(R.id.summary);
- iconView= (ImageView) view.findViewById(R.id.icon);
+ iconView = (ImageView) view.findViewById(R.id.icon);
}
- public void onBind(SearchResult result) {
+ @Override
+ public void onBind(Fragment fragment, SearchResult result) {
titleView.setText(result.title);
summaryView.setText(result.summary);
iconView.setImageDrawable(result.icon);
+ itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ fragment.startActivity(((IntentPayload) result.payload).intent);
+ }
+ });
}
}
diff --git a/src/com/android/settings/search2/SearchFeatureProvider.java b/src/com/android/settings/search2/SearchFeatureProvider.java
index 569a627..da29c85 100644
--- a/src/com/android/settings/search2/SearchFeatureProvider.java
+++ b/src/com/android/settings/search2/SearchFeatureProvider.java
@@ -41,4 +41,9 @@
* Returns a new loader to search in index database.
*/
DatabaseResultLoader getDatabaseSearchLoader(Context context, String query);
+
+ /**
+ * Returns a new loader to search installed apps.
+ */
+ InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query);
}
diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
index 81a41dc..7203049 100644
--- a/src/com/android/settings/search2/SearchFeatureProviderImpl.java
+++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
@@ -19,14 +19,11 @@
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;
-import com.android.settings.utils.AsyncLoader;
-import java.util.List;
+import com.android.settings.R;
+import com.android.settings.applications.PackageManagerWrapperImpl;
/**
* FeatureProvider for the refactored search code.
@@ -51,15 +48,15 @@
}
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;
- }
- });
+ .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);
}
@@ -68,4 +65,10 @@
public DatabaseResultLoader getDatabaseSearchLoader(Context context, String query) {
return new DatabaseResultLoader(context, query);
}
+
+ @Override
+ public InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query) {
+ return new InstalledAppResultLoader(
+ context, new PackageManagerWrapperImpl(context.getPackageManager()), query);
+ }
}
diff --git a/src/com/android/settings/search2/SearchFragment.java b/src/com/android/settings/search2/SearchFragment.java
index 1fb123c..fca52e9 100644
--- a/src/com/android/settings/search2/SearchFragment.java
+++ b/src/com/android/settings/search2/SearchFragment.java
@@ -46,13 +46,13 @@
static final String STATE_QUERY = "query";
// Loader IDs
- private static final int DATABASE_LOADER_ID = 0;
+ private static final int LOADER_ID_DATABASE = 0;
+ private static final int LOADER_ID_INSTALLED_APPS = 1;
@VisibleForTesting
String mQuery;
private SearchFeatureProvider mSearchFeatureProvider;
- private DatabaseResultLoader mSearchLoader;
private SearchResultsAdapter mSearchAdapter;
private RecyclerView mResultsRecyclerView;
@@ -73,10 +73,12 @@
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
- mSearchAdapter = new SearchResultsAdapter();
+ mSearchAdapter = new SearchResultsAdapter(this);
if (savedInstanceState != null) {
mQuery = savedInstanceState.getString(STATE_QUERY);
- getLoaderManager().initLoader(DATABASE_LOADER_ID, null, this);
+ final LoaderManager loaderManager = getLoaderManager();
+ loaderManager.initLoader(LOADER_ID_DATABASE, null, this);
+ loaderManager.initLoader(LOADER_ID_INSTALLED_APPS, null, this);
}
final ActionBar actionBar = getActivity().getActionBar();
actionBar.setCustomView(makeSearchView(actionBar, mQuery));
@@ -109,7 +111,7 @@
mSearchAdapter.clearResults();
if (TextUtils.isEmpty(mQuery)) {
- getLoaderManager().destroyLoader(DATABASE_LOADER_ID);
+ getLoaderManager().destroyLoader(LOADER_ID_DATABASE);
} else {
restartLoaders();
}
@@ -127,9 +129,10 @@
final Activity activity = getActivity();
switch (id) {
- case DATABASE_LOADER_ID:
- mSearchLoader = mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
- return mSearchLoader;
+ case LOADER_ID_DATABASE:
+ return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
+ case LOADER_ID_INSTALLED_APPS:
+ return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery);
default:
return null;
}
@@ -137,10 +140,6 @@
@Override
public void onLoadFinished(Loader<List<SearchResult>> loader, List<SearchResult> data) {
- if (data == null) {
- return;
- }
-
mSearchAdapter.mergeResults(data, loader.getClass().getName());
}
@@ -150,7 +149,8 @@
private void restartLoaders() {
final LoaderManager loaderManager = getLoaderManager();
- loaderManager.restartLoader(DATABASE_LOADER_ID, null /* args */, this /* callback */);
+ loaderManager.restartLoader(LOADER_ID_DATABASE, null /* args */, this /* callback */);
+ loaderManager.restartLoader(LOADER_ID_INSTALLED_APPS, null /* args */, this /* callback */);
}
private SearchView makeSearchView(ActionBar actionBar, String query) {
diff --git a/src/com/android/settings/search2/SearchResult.java b/src/com/android/settings/search2/SearchResult.java
index e483df3..9fb250f 100644
--- a/src/com/android/settings/search2/SearchResult.java
+++ b/src/com/android/settings/search2/SearchResult.java
@@ -19,11 +19,73 @@
import android.graphics.drawable.Drawable;
import java.util.ArrayList;
+import java.util.Objects;
/**
- * Dataclass as an interface for all Search Results.
+ * Data class as an interface for all Search Results.
*/
public class SearchResult implements Comparable<SearchResult> {
+
+ /**
+ * The title of the result and main text displayed.
+ * Intent Results: Displays as the primary
+ */
+ public final CharSequence title;
+
+ /**
+ * Summary / subtitle text
+ * Intent Results: Displays the text underneath the title
+ */
+ final public CharSequence 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;
+
+ /**
+ * Stable id for this object.
+ */
+ public final long stableId;
+
+ 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();
+ stableId = Objects.hash(title, summary, breadcrumbs, rank, icon, payload, viewType);
+
+ }
+
@Override
public int compareTo(SearchResult searchResult) {
if (searchResult == null) {
@@ -33,19 +95,19 @@
}
public static class Builder {
- protected String mTitle;
- protected String mSummary;
+ protected CharSequence mTitle;
+ protected CharSequence mSummary;
protected ArrayList<String> mBreadcrumbs;
protected int mRank = -1;
protected ResultPayload mResultPayload;
protected Drawable mIcon;
- public Builder addTitle(String title) {
+ public Builder addTitle(CharSequence title) {
mTitle = title;
return this;
}
- public Builder addSummary(String summary) {
+ public Builder addSummary(CharSequence summary) {
mSummary = summary;
return this;
}
@@ -77,10 +139,6 @@
// 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) {
@@ -91,56 +149,4 @@
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();
- }
}
diff --git a/src/com/android/settings/search2/SearchResultsAdapter.java b/src/com/android/settings/search2/SearchResultsAdapter.java
index 62b79b3..b588496 100644
--- a/src/com/android/settings/search2/SearchResultsAdapter.java
+++ b/src/com/android/settings/search2/SearchResultsAdapter.java
@@ -33,40 +33,19 @@
public class SearchResultsAdapter extends Adapter<SearchViewHolder> {
private final List<SearchResult> mSearchResults;
private final Map<String, List<SearchResult>> mResultsMap;
+ private final SearchFragment mFragment;
- public SearchResultsAdapter() {
+ public SearchResultsAdapter(SearchFragment fragment) {
+ mFragment = fragment;
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.addAll(mergeMappedResults());
- notifyDataSetChanged();
- }
-
- public void clearResults() {
- mSearchResults.clear();
- mResultsMap.clear();
- 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());
+ final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (viewType) {
case PayloadType.INTENT:
View view = inflater.inflate(R.layout.search_intent_item, parent, false);
@@ -82,13 +61,12 @@
@Override
public void onBindViewHolder(SearchViewHolder holder, int position) {
- SearchResult result = mSearchResults.get(position);
- holder.onBind(result);
+ holder.onBind(mFragment, mSearchResults.get(position));
}
@Override
public long getItemId(int position) {
- return super.getItemId(position);
+ return mSearchResults.get(position).stableId;
}
@Override
@@ -101,6 +79,23 @@
return mSearchResults.size();
}
+ public void mergeResults(List<SearchResult> freshResults, String loaderClassName) {
+ if (freshResults == null) {
+ return;
+ }
+ mResultsMap.put(loaderClassName, freshResults);
+ final int oldSize = mSearchResults.size();
+ mSearchResults.addAll(freshResults);
+ final int newSize = mSearchResults.size();
+ notifyItemRangeInserted(oldSize, newSize - oldSize);
+ }
+
+ public void clearResults() {
+ mSearchResults.clear();
+ mResultsMap.clear();
+ notifyDataSetChanged();
+ }
+
@VisibleForTesting
public List<SearchResult> getSearchResults() {
return mSearchResults;
diff --git a/src/com/android/settings/search2/SearchViewHolder.java b/src/com/android/settings/search2/SearchViewHolder.java
index 2f500fb..45ceb38 100644
--- a/src/com/android/settings/search2/SearchViewHolder.java
+++ b/src/com/android/settings/search2/SearchViewHolder.java
@@ -15,6 +15,7 @@
*/
package com.android.settings.search2;
+import android.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.view.View;
@@ -29,5 +30,5 @@
super(view);
}
- public abstract void onBind(SearchResult result);
+ public abstract void onBind(Fragment fragment, SearchResult result);
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java b/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
index 2534c0b..805c7cb 100644
--- a/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
+++ b/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
@@ -17,40 +17,50 @@
package com.android.settings.search;
+import android.app.Fragment;
import android.content.Context;
+import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
+
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.search2.IntentPayload;
import com.android.settings.search2.IntentSearchViewHolder;
-import com.android.settings.search2.SearchResult.Builder;
import com.android.settings.search2.SearchResult;
+import com.android.settings.search2.SearchResult.Builder;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import java.util.ArrayList;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class IntentSearchViewHolderTest {
- private IntentSearchViewHolder mHolder;
- private static Drawable mIcon;
private static final String TITLE = "title";
private static final String SUMMARY = "summary";
+ @Mock
+ private Fragment mFragment;
+ private IntentSearchViewHolder mHolder;
+ private Drawable mIcon;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
final Context context = ShadowApplication.getInstance().getApplicationContext();
View view = LayoutInflater.from(context).inflate(R.layout.search_intent_item, null);
mHolder = new IntentSearchViewHolder(view);
@@ -68,11 +78,13 @@
@Test
public void testBindViewElements_AllUpdated() {
SearchResult result = getSearchResult();
- mHolder.onBind(result);
+ mHolder.onBind(mFragment, result);
+ mHolder.itemView.performClick();
assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
assertThat(mHolder.iconView.getDrawable()).isEqualTo(mIcon);
+ verify(mFragment).startActivity(any(Intent.class));
}
private SearchResult getSearchResult() {
@@ -81,7 +93,7 @@
.addSummary(SUMMARY)
.addRank(1)
.addPayload(new IntentPayload(null))
- .addBreadcrumbs(new ArrayList<String>())
+ .addBreadcrumbs(new ArrayList<>())
.addIcon(mIcon);
return builder.build();
diff --git a/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java b/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java
index 81e9180..0756d3f 100644
--- a/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java
@@ -27,6 +27,7 @@
import com.android.settings.search2.DatabaseResultLoader;
import com.android.settings.search2.IntentPayload;
import com.android.settings.search2.ResultPayload;
+import com.android.settings.search2.SearchFragment;
import com.android.settings.search2.SearchResult;
import com.android.settings.search2.SearchResult.Builder;
import com.android.settings.search2.SearchResultsAdapter;
@@ -34,6 +35,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
@@ -46,14 +49,17 @@
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SearchAdapterTest {
+ @Mock
+ private SearchFragment mFragment;
private SearchResultsAdapter mAdapter;
private Context mContext;
private String mLoaderClassName;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
mContext = Robolectric.buildActivity(Activity.class).get();
- mAdapter = new SearchResultsAdapter();
+ mAdapter = new SearchResultsAdapter(mFragment);
mLoaderClassName = DatabaseResultLoader.class.getName();
}
@@ -62,8 +68,7 @@
ArrayList<String> breadcrumbs = new ArrayList<>();
final Drawable icon = mContext.getDrawable(R.drawable.ic_search_history);
final ResultPayload payload = new IntentPayload(null);
-
- SearchResult.Builder builder = new Builder();
+ final SearchResult.Builder builder = new Builder();
builder.addTitle("title")
.addSummary("summary")
.addRank(1)
diff --git a/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java b/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java
index c2ec49c..a0f4cc5 100644
--- a/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java
@@ -19,23 +19,23 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
+
+import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.search2.IntentPayload;
import com.android.settings.search2.ResultPayload;
import com.android.settings.search2.SearchResult;
import com.android.settings.search2.SearchResult.Builder;
-import com.android.settings.R;
-
-import java.util.ArrayList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
+import java.util.ArrayList;
+
import static com.google.common.truth.Truth.assertThat;
@RunWith(SettingsRobolectricTestRunner.class)
@@ -100,23 +100,6 @@
}
@Test
- public void testNoSummary_BuildSearchResultException() {
- mBuilder.addTitle(mTitle)
- .addRank(mRank)
- .addBreadcrumbs(mBreadcrumbs)
- .addIcon(mIcon)
- .addPayload(mResultPayload);
-
- SearchResult result = null;
- try {
- result = mBuilder.build();
- } catch (IllegalArgumentException e) {
- // passes.
- }
- assertThat(result).isNull();
- }
-
- @Test
public void testNoRank_BuildSearchResultException() {
mBuilder.addTitle(mTitle)
.addSummary(mSummary)
@@ -134,23 +117,6 @@
}
@Test
- public void testNoBreadcrumbs_BuildSearchResultException() {
- mBuilder.addTitle(mTitle)
- .addSummary(mSummary)
- .addRank(mRank)
- .addIcon(mIcon)
- .addPayload(mResultPayload);
-
- SearchResult result = null;
- try {
- result = mBuilder.build();
- } catch (IllegalArgumentException e) {
- // passes.
- }
- assertThat(result).isNull();
- }
-
- @Test
public void testNoIcon_BuildSearchResultException() {
mBuilder.addTitle(mTitle)
.addSummary(mSummary)
diff --git a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
new file mode 100644
index 0000000..e3c2180
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.pm.UserInfo;
+import android.os.UserManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.testutils.ApplicationTestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class InstalledAppResultLoaderTest {
+
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PackageManagerWrapper mPackageManagerWrapper;
+ @Mock
+ private UserManager mUserManager;
+
+ private InstalledAppResultLoader mLoader;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ final List<UserInfo> infos = new ArrayList<>();
+ infos.add(new UserInfo(1, "user 1", 0));
+ when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
+ .thenReturn(Arrays.asList(
+ ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM),
+ ApplicationTestUtils.buildInfo(0 /* uid */, "app2", FLAG_SYSTEM),
+ ApplicationTestUtils.buildInfo(0 /* uid */, "app3", FLAG_SYSTEM),
+ ApplicationTestUtils.buildInfo(0 /* uid */, "app4", 0 /* flags */),
+ ApplicationTestUtils.buildInfo(0 /* uid */, "app", 0 /* flags */)));
+ }
+
+ @Test
+ public void query_noMatchingQuery_shouldReturnEmptyResult() {
+ final String query = "abc";
+
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+
+ assertThat(mLoader.loadInBackground()).isEmpty();
+ }
+
+ @Test
+ public void query_matchingQuery_shouldReturnNonSystemApps() {
+ final String query = "app";
+
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+
+ assertThat(mLoader.loadInBackground().size()).isEqualTo(2);
+ }
+
+ @Test
+ public void query_matchingQuery_shouldRankBasedOnSimilarity() {
+ final String query = "app";
+
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ final List<SearchResult> results = mLoader.loadInBackground();
+
+ // List is sorted by rank
+ assertThat(results.get(0).rank).isLessThan(results.get(1).rank);
+ // perfect match first
+ assertThat(results.get(0).title).isEqualTo(query);
+ // Then partial match
+ assertThat(results.get(1).title).isNotEqualTo(query);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
index 979b7e5..40d1ae5 100644
--- a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
@@ -47,6 +47,8 @@
private Context mContext;
@Mock
private DatabaseResultLoader mDatabaseResultLoader;
+ @Mock
+ private InstalledAppResultLoader mInstalledAppResultLoader;
private FakeFeatureFactory mFeatureFactory;
@Before
@@ -54,14 +56,16 @@
MockitoAnnotations.initMocks(this);
FakeFeatureFactory.setupForTest(mContext);
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+ when(mFeatureFactory.searchFeatureProvider
+ .getDatabaseSearchLoader(any(Context.class), anyString()))
+ .thenReturn(mDatabaseResultLoader);
+ when(mFeatureFactory.searchFeatureProvider
+ .getInstalledAppSearchLoader(any(Context.class), anyString()))
+ .thenReturn(mInstalledAppResultLoader);
}
@Test
public void screenRotate_shouldPersistQuery() {
- when(mFeatureFactory.searchFeatureProvider
- .getDatabaseSearchLoader(any(Context.class), anyString()))
- .thenReturn(mDatabaseResultLoader);
-
final Bundle bundle = new Bundle();
final String testQuery = "test";
ActivityController<SearchActivity> activityController =
@@ -79,14 +83,12 @@
verify(mFeatureFactory.searchFeatureProvider)
.getDatabaseSearchLoader(any(Context.class), anyString());
+ verify(mFeatureFactory.searchFeatureProvider)
+ .getInstalledAppSearchLoader(any(Context.class), anyString());
}
@Test
public void queryTextChange_shouldTriggerLoader() {
- when(mFeatureFactory.searchFeatureProvider
- .getDatabaseSearchLoader(any(Context.class), anyString()))
- .thenReturn(mDatabaseResultLoader);
-
final String testQuery = "test";
ActivityController<SearchActivity> activityController =
Robolectric.buildActivity(SearchActivity.class);
@@ -98,5 +100,7 @@
verify(mFeatureFactory.searchFeatureProvider)
.getDatabaseSearchLoader(any(Context.class), anyString());
+ verify(mFeatureFactory.searchFeatureProvider)
+ .getInstalledAppSearchLoader(any(Context.class), anyString());
}
}