Add search loader for installed apps.
- The loader filters out system apps. - Loader performs case-insensitive match with app names. - SearchResultAdapter combines results from multiple loaders into a single list. Fixes: 33347966 Test: make RunSettingsRoboTests Change-Id: I228ca6fb82f0ac5151b2346c079c2de41104a4df
This commit is contained in:
@@ -18,6 +18,7 @@ package com.android.settings.applications;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -29,24 +30,30 @@ import java.util.List;
|
|||||||
* the API version supported by Robolectric.
|
* the API version supported by Robolectric.
|
||||||
*/
|
*/
|
||||||
public interface PackageManagerWrapper {
|
public interface PackageManagerWrapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the real {@code PackageManager} object.
|
||||||
|
*/
|
||||||
|
PackageManager getPackageManager();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls {@code PackageManager.getInstalledApplicationsAsUser()}.
|
* Calls {@code PackageManager.getInstalledApplicationsAsUser()}.
|
||||||
*
|
*
|
||||||
* @see android.content.pm.PackageManager.PackageManager#getInstalledApplicationsAsUser
|
* @see android.content.pm.PackageManager#getInstalledApplicationsAsUser
|
||||||
*/
|
*/
|
||||||
List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId);
|
List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls {@code PackageManager.hasSystemFeature()}.
|
* Calls {@code PackageManager.hasSystemFeature()}.
|
||||||
*
|
*
|
||||||
* @see android.content.pm.PackageManager.PackageManager#hasSystemFeature
|
* @see android.content.pm.PackageManager#hasSystemFeature
|
||||||
*/
|
*/
|
||||||
boolean hasSystemFeature(String name);
|
boolean hasSystemFeature(String name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls {@code PackageManager.queryIntentActivitiesAsUser()}.
|
* 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);
|
List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId);
|
||||||
}
|
}
|
||||||
|
@@ -24,12 +24,18 @@ import android.content.pm.ResolveInfo;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PackageManagerWrapperImpl implements PackageManagerWrapper {
|
public class PackageManagerWrapperImpl implements PackageManagerWrapper {
|
||||||
|
|
||||||
private final PackageManager mPm;
|
private final PackageManager mPm;
|
||||||
|
|
||||||
public PackageManagerWrapperImpl(PackageManager pm) {
|
public PackageManagerWrapperImpl(PackageManager pm) {
|
||||||
mPm = pm;
|
mPm = pm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PackageManager getPackageManager() {
|
||||||
|
return mPm;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
|
public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
|
||||||
return mPm.getInstalledApplicationsAsUser(flags, userId);
|
return mPm.getInstalledApplicationsAsUser(flags, userId);
|
||||||
|
@@ -23,10 +23,11 @@ import android.database.Cursor;
|
|||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.support.annotation.VisibleForTesting;
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
import com.android.settings.search.Index;
|
import com.android.settings.search.Index;
|
||||||
import com.android.settings.search.IndexDatabaseHelper;
|
import com.android.settings.search.IndexDatabaseHelper;
|
||||||
import com.android.settings.utils.AsyncLoader;
|
import com.android.settings.utils.AsyncLoader;
|
||||||
import com.android.settings.R;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -107,7 +108,6 @@ public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
|
|||||||
icon = mContext.getDrawable(R.drawable.ic_search_history);
|
icon = mContext.getDrawable(R.drawable.ic_search_history);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SearchResult.Builder builder = new SearchResult.Builder();
|
SearchResult.Builder builder = new SearchResult.Builder();
|
||||||
builder.addTitle(title)
|
builder.addTitle(title)
|
||||||
.addSummary(summaryOn)
|
.addSummary(summaryOn)
|
||||||
|
139
src/com/android/settings/search2/InstalledAppResultLoader.java
Normal file
139
src/com/android/settings/search2/InstalledAppResultLoader.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -15,9 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.settings.search2;
|
package com.android.settings.search2;
|
||||||
|
|
||||||
|
import android.app.Fragment;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,6 +27,7 @@ import com.android.settings.R;
|
|||||||
* The DatabaseResultLoader is the primary use case for this ViewHolder.
|
* The DatabaseResultLoader is the primary use case for this ViewHolder.
|
||||||
*/
|
*/
|
||||||
public class IntentSearchViewHolder extends SearchViewHolder {
|
public class IntentSearchViewHolder extends SearchViewHolder {
|
||||||
|
|
||||||
public final TextView titleView;
|
public final TextView titleView;
|
||||||
public final TextView summaryView;
|
public final TextView summaryView;
|
||||||
public final ImageView iconView;
|
public final ImageView iconView;
|
||||||
@@ -33,12 +36,19 @@ public class IntentSearchViewHolder extends SearchViewHolder {
|
|||||||
super(view);
|
super(view);
|
||||||
titleView = (TextView) view.findViewById(R.id.title);
|
titleView = (TextView) view.findViewById(R.id.title);
|
||||||
summaryView = (TextView) view.findViewById(R.id.summary);
|
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);
|
titleView.setText(result.title);
|
||||||
summaryView.setText(result.summary);
|
summaryView.setText(result.summary);
|
||||||
iconView.setImageDrawable(result.icon);
|
iconView.setImageDrawable(result.icon);
|
||||||
|
itemView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
fragment.startActivity(((IntentPayload) result.payload).intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -41,4 +41,9 @@ public interface SearchFeatureProvider {
|
|||||||
* Returns a new loader to search in index database.
|
* Returns a new loader to search in index database.
|
||||||
*/
|
*/
|
||||||
DatabaseResultLoader getDatabaseSearchLoader(Context context, String query);
|
DatabaseResultLoader getDatabaseSearchLoader(Context context, String query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new loader to search installed apps.
|
||||||
|
*/
|
||||||
|
InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query);
|
||||||
}
|
}
|
||||||
|
@@ -19,14 +19,11 @@ package com.android.settings.search2;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.widget.SearchView;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
|
||||||
import android.view.MenuItem;
|
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.
|
* FeatureProvider for the refactored search code.
|
||||||
@@ -51,15 +48,15 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
|
|||||||
}
|
}
|
||||||
String menuTitle = mContext.getString(R.string.search_menu);
|
String menuTitle = mContext.getString(R.string.search_menu);
|
||||||
MenuItem menuItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, menuTitle)
|
MenuItem menuItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, menuTitle)
|
||||||
.setIcon(R.drawable.abc_ic_search_api_material)
|
.setIcon(R.drawable.abc_ic_search_api_material)
|
||||||
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
Intent intent = new Intent(activity, SearchActivity.class);
|
Intent intent = new Intent(activity, SearchActivity.class);
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
}
|
}
|
||||||
@@ -68,4 +65,10 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
|
|||||||
public DatabaseResultLoader getDatabaseSearchLoader(Context context, String query) {
|
public DatabaseResultLoader getDatabaseSearchLoader(Context context, String query) {
|
||||||
return new DatabaseResultLoader(context, query);
|
return new DatabaseResultLoader(context, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query) {
|
||||||
|
return new InstalledAppResultLoader(
|
||||||
|
context, new PackageManagerWrapperImpl(context.getPackageManager()), query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -46,13 +46,13 @@ public class SearchFragment extends InstrumentedFragment implements
|
|||||||
static final String STATE_QUERY = "query";
|
static final String STATE_QUERY = "query";
|
||||||
|
|
||||||
// Loader IDs
|
// 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
|
@VisibleForTesting
|
||||||
String mQuery;
|
String mQuery;
|
||||||
|
|
||||||
private SearchFeatureProvider mSearchFeatureProvider;
|
private SearchFeatureProvider mSearchFeatureProvider;
|
||||||
private DatabaseResultLoader mSearchLoader;
|
|
||||||
|
|
||||||
private SearchResultsAdapter mSearchAdapter;
|
private SearchResultsAdapter mSearchAdapter;
|
||||||
private RecyclerView mResultsRecyclerView;
|
private RecyclerView mResultsRecyclerView;
|
||||||
@@ -73,10 +73,12 @@ public class SearchFragment extends InstrumentedFragment implements
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
mSearchAdapter = new SearchResultsAdapter();
|
mSearchAdapter = new SearchResultsAdapter(this);
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
mQuery = savedInstanceState.getString(STATE_QUERY);
|
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();
|
final ActionBar actionBar = getActivity().getActionBar();
|
||||||
actionBar.setCustomView(makeSearchView(actionBar, mQuery));
|
actionBar.setCustomView(makeSearchView(actionBar, mQuery));
|
||||||
@@ -109,7 +111,7 @@ public class SearchFragment extends InstrumentedFragment implements
|
|||||||
mSearchAdapter.clearResults();
|
mSearchAdapter.clearResults();
|
||||||
|
|
||||||
if (TextUtils.isEmpty(mQuery)) {
|
if (TextUtils.isEmpty(mQuery)) {
|
||||||
getLoaderManager().destroyLoader(DATABASE_LOADER_ID);
|
getLoaderManager().destroyLoader(LOADER_ID_DATABASE);
|
||||||
} else {
|
} else {
|
||||||
restartLoaders();
|
restartLoaders();
|
||||||
}
|
}
|
||||||
@@ -127,9 +129,10 @@ public class SearchFragment extends InstrumentedFragment implements
|
|||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case DATABASE_LOADER_ID:
|
case LOADER_ID_DATABASE:
|
||||||
mSearchLoader = mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
|
return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
|
||||||
return mSearchLoader;
|
case LOADER_ID_INSTALLED_APPS:
|
||||||
|
return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -137,10 +140,6 @@ public class SearchFragment extends InstrumentedFragment implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(Loader<List<SearchResult>> loader, List<SearchResult> data) {
|
public void onLoadFinished(Loader<List<SearchResult>> loader, List<SearchResult> data) {
|
||||||
if (data == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mSearchAdapter.mergeResults(data, loader.getClass().getName());
|
mSearchAdapter.mergeResults(data, loader.getClass().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +149,8 @@ public class SearchFragment extends InstrumentedFragment implements
|
|||||||
|
|
||||||
private void restartLoaders() {
|
private void restartLoaders() {
|
||||||
final LoaderManager loaderManager = getLoaderManager();
|
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) {
|
private SearchView makeSearchView(ActionBar actionBar, String query) {
|
||||||
|
@@ -19,11 +19,73 @@ package com.android.settings.search2;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
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> {
|
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
|
@Override
|
||||||
public int compareTo(SearchResult searchResult) {
|
public int compareTo(SearchResult searchResult) {
|
||||||
if (searchResult == null) {
|
if (searchResult == null) {
|
||||||
@@ -33,19 +95,19 @@ public class SearchResult implements Comparable<SearchResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
protected String mTitle;
|
protected CharSequence mTitle;
|
||||||
protected String mSummary;
|
protected CharSequence mSummary;
|
||||||
protected ArrayList<String> mBreadcrumbs;
|
protected ArrayList<String> mBreadcrumbs;
|
||||||
protected int mRank = -1;
|
protected int mRank = -1;
|
||||||
protected ResultPayload mResultPayload;
|
protected ResultPayload mResultPayload;
|
||||||
protected Drawable mIcon;
|
protected Drawable mIcon;
|
||||||
|
|
||||||
public Builder addTitle(String title) {
|
public Builder addTitle(CharSequence title) {
|
||||||
mTitle = title;
|
mTitle = title;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder addSummary(String summary) {
|
public Builder addSummary(CharSequence summary) {
|
||||||
mSummary = summary;
|
mSummary = summary;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -77,10 +139,6 @@ public class SearchResult implements Comparable<SearchResult> {
|
|||||||
// Check that all of the mandatory fields are set.
|
// Check that all of the mandatory fields are set.
|
||||||
if (mTitle == null) {
|
if (mTitle == null) {
|
||||||
throw new IllegalArgumentException("SearchResult missing title argument");
|
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) {
|
} else if (mRank == -1) {
|
||||||
throw new IllegalArgumentException("SearchResult missing rank argument");
|
throw new IllegalArgumentException("SearchResult missing rank argument");
|
||||||
} else if (mIcon == null) {
|
} else if (mIcon == null) {
|
||||||
@@ -91,56 +149,4 @@ public class SearchResult implements Comparable<SearchResult> {
|
|||||||
return new SearchResult(this);
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -33,40 +33,19 @@ import java.util.Map;
|
|||||||
public class SearchResultsAdapter extends Adapter<SearchViewHolder> {
|
public class SearchResultsAdapter extends Adapter<SearchViewHolder> {
|
||||||
private final List<SearchResult> mSearchResults;
|
private final List<SearchResult> mSearchResults;
|
||||||
private final Map<String, List<SearchResult>> mResultsMap;
|
private final Map<String, List<SearchResult>> mResultsMap;
|
||||||
|
private final SearchFragment mFragment;
|
||||||
|
|
||||||
public SearchResultsAdapter() {
|
public SearchResultsAdapter(SearchFragment fragment) {
|
||||||
|
mFragment = fragment;
|
||||||
mSearchResults = new ArrayList<>();
|
mSearchResults = new ArrayList<>();
|
||||||
mResultsMap = new HashMap<>();
|
mResultsMap = new HashMap<>();
|
||||||
|
|
||||||
setHasStableIds(true);
|
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
|
@Override
|
||||||
public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||||
switch (viewType) {
|
switch (viewType) {
|
||||||
case PayloadType.INTENT:
|
case PayloadType.INTENT:
|
||||||
View view = inflater.inflate(R.layout.search_intent_item, parent, false);
|
View view = inflater.inflate(R.layout.search_intent_item, parent, false);
|
||||||
@@ -82,13 +61,12 @@ public class SearchResultsAdapter extends Adapter<SearchViewHolder> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(SearchViewHolder holder, int position) {
|
public void onBindViewHolder(SearchViewHolder holder, int position) {
|
||||||
SearchResult result = mSearchResults.get(position);
|
holder.onBind(mFragment, mSearchResults.get(position));
|
||||||
holder.onBind(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
public long getItemId(int position) {
|
||||||
return super.getItemId(position);
|
return mSearchResults.get(position).stableId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -101,6 +79,23 @@ public class SearchResultsAdapter extends Adapter<SearchViewHolder> {
|
|||||||
return mSearchResults.size();
|
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
|
@VisibleForTesting
|
||||||
public List<SearchResult> getSearchResults() {
|
public List<SearchResult> getSearchResults() {
|
||||||
return mSearchResults;
|
return mSearchResults;
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.settings.search2;
|
package com.android.settings.search2;
|
||||||
|
|
||||||
|
import android.app.Fragment;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
@@ -29,5 +30,5 @@ public abstract class SearchViewHolder extends RecyclerView.ViewHolder {
|
|||||||
super(view);
|
super(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void onBind(SearchResult result);
|
public abstract void onBind(Fragment fragment, SearchResult result);
|
||||||
}
|
}
|
@@ -17,40 +17,50 @@
|
|||||||
|
|
||||||
package com.android.settings.search;
|
package com.android.settings.search;
|
||||||
|
|
||||||
|
import android.app.Fragment;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.SettingsRobolectricTestRunner;
|
import com.android.settings.SettingsRobolectricTestRunner;
|
||||||
import com.android.settings.TestConfig;
|
import com.android.settings.TestConfig;
|
||||||
import com.android.settings.search2.IntentPayload;
|
import com.android.settings.search2.IntentPayload;
|
||||||
import com.android.settings.search2.IntentSearchViewHolder;
|
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;
|
||||||
|
import com.android.settings.search2.SearchResult.Builder;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
import org.robolectric.shadows.ShadowApplication;
|
import org.robolectric.shadows.ShadowApplication;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
@RunWith(SettingsRobolectricTestRunner.class)
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||||
public class IntentSearchViewHolderTest {
|
public class IntentSearchViewHolderTest {
|
||||||
private IntentSearchViewHolder mHolder;
|
|
||||||
private static Drawable mIcon;
|
|
||||||
|
|
||||||
private static final String TITLE = "title";
|
private static final String TITLE = "title";
|
||||||
private static final String SUMMARY = "summary";
|
private static final String SUMMARY = "summary";
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Fragment mFragment;
|
||||||
|
private IntentSearchViewHolder mHolder;
|
||||||
|
private Drawable mIcon;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
final Context context = ShadowApplication.getInstance().getApplicationContext();
|
final Context context = ShadowApplication.getInstance().getApplicationContext();
|
||||||
View view = LayoutInflater.from(context).inflate(R.layout.search_intent_item, null);
|
View view = LayoutInflater.from(context).inflate(R.layout.search_intent_item, null);
|
||||||
mHolder = new IntentSearchViewHolder(view);
|
mHolder = new IntentSearchViewHolder(view);
|
||||||
@@ -68,11 +78,13 @@ public class IntentSearchViewHolderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testBindViewElements_AllUpdated() {
|
public void testBindViewElements_AllUpdated() {
|
||||||
SearchResult result = getSearchResult();
|
SearchResult result = getSearchResult();
|
||||||
mHolder.onBind(result);
|
mHolder.onBind(mFragment, result);
|
||||||
|
mHolder.itemView.performClick();
|
||||||
|
|
||||||
assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
|
assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
|
||||||
assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
|
assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
|
||||||
assertThat(mHolder.iconView.getDrawable()).isEqualTo(mIcon);
|
assertThat(mHolder.iconView.getDrawable()).isEqualTo(mIcon);
|
||||||
|
verify(mFragment).startActivity(any(Intent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
private SearchResult getSearchResult() {
|
private SearchResult getSearchResult() {
|
||||||
@@ -81,7 +93,7 @@ public class IntentSearchViewHolderTest {
|
|||||||
.addSummary(SUMMARY)
|
.addSummary(SUMMARY)
|
||||||
.addRank(1)
|
.addRank(1)
|
||||||
.addPayload(new IntentPayload(null))
|
.addPayload(new IntentPayload(null))
|
||||||
.addBreadcrumbs(new ArrayList<String>())
|
.addBreadcrumbs(new ArrayList<>())
|
||||||
.addIcon(mIcon);
|
.addIcon(mIcon);
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
|
@@ -27,6 +27,7 @@ import com.android.settings.TestConfig;
|
|||||||
import com.android.settings.search2.DatabaseResultLoader;
|
import com.android.settings.search2.DatabaseResultLoader;
|
||||||
import com.android.settings.search2.IntentPayload;
|
import com.android.settings.search2.IntentPayload;
|
||||||
import com.android.settings.search2.ResultPayload;
|
import com.android.settings.search2.ResultPayload;
|
||||||
|
import com.android.settings.search2.SearchFragment;
|
||||||
import com.android.settings.search2.SearchResult;
|
import com.android.settings.search2.SearchResult;
|
||||||
import com.android.settings.search2.SearchResult.Builder;
|
import com.android.settings.search2.SearchResult.Builder;
|
||||||
import com.android.settings.search2.SearchResultsAdapter;
|
import com.android.settings.search2.SearchResultsAdapter;
|
||||||
@@ -34,6 +35,8 @@ import com.android.settings.search2.SearchResultsAdapter;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.Robolectric;
|
import org.robolectric.Robolectric;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
@@ -46,14 +49,17 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||||
public class SearchAdapterTest {
|
public class SearchAdapterTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private SearchFragment mFragment;
|
||||||
private SearchResultsAdapter mAdapter;
|
private SearchResultsAdapter mAdapter;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private String mLoaderClassName;
|
private String mLoaderClassName;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
mContext = Robolectric.buildActivity(Activity.class).get();
|
mContext = Robolectric.buildActivity(Activity.class).get();
|
||||||
mAdapter = new SearchResultsAdapter();
|
mAdapter = new SearchResultsAdapter(mFragment);
|
||||||
mLoaderClassName = DatabaseResultLoader.class.getName();
|
mLoaderClassName = DatabaseResultLoader.class.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,8 +68,7 @@ public class SearchAdapterTest {
|
|||||||
ArrayList<String> breadcrumbs = new ArrayList<>();
|
ArrayList<String> breadcrumbs = new ArrayList<>();
|
||||||
final Drawable icon = mContext.getDrawable(R.drawable.ic_search_history);
|
final Drawable icon = mContext.getDrawable(R.drawable.ic_search_history);
|
||||||
final ResultPayload payload = new IntentPayload(null);
|
final ResultPayload payload = new IntentPayload(null);
|
||||||
|
final SearchResult.Builder builder = new Builder();
|
||||||
SearchResult.Builder builder = new Builder();
|
|
||||||
builder.addTitle("title")
|
builder.addTitle("title")
|
||||||
.addSummary("summary")
|
.addSummary("summary")
|
||||||
.addRank(1)
|
.addRank(1)
|
||||||
|
@@ -19,23 +19,23 @@ package com.android.settings.search;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
import com.android.settings.SettingsRobolectricTestRunner;
|
import com.android.settings.SettingsRobolectricTestRunner;
|
||||||
import com.android.settings.TestConfig;
|
import com.android.settings.TestConfig;
|
||||||
import com.android.settings.search2.IntentPayload;
|
import com.android.settings.search2.IntentPayload;
|
||||||
import com.android.settings.search2.ResultPayload;
|
import com.android.settings.search2.ResultPayload;
|
||||||
import com.android.settings.search2.SearchResult;
|
import com.android.settings.search2.SearchResult;
|
||||||
import com.android.settings.search2.SearchResult.Builder;
|
import com.android.settings.search2.SearchResult.Builder;
|
||||||
import com.android.settings.R;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
import org.robolectric.shadows.ShadowApplication;
|
import org.robolectric.shadows.ShadowApplication;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
@RunWith(SettingsRobolectricTestRunner.class)
|
||||||
@@ -99,23 +99,6 @@ public class SearchResultBuilderTest {
|
|||||||
assertThat(result).isNull();
|
assertThat(result).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
@Test
|
||||||
public void testNoRank_BuildSearchResultException() {
|
public void testNoRank_BuildSearchResultException() {
|
||||||
mBuilder.addTitle(mTitle)
|
mBuilder.addTitle(mTitle)
|
||||||
@@ -133,23 +116,6 @@ public class SearchResultBuilderTest {
|
|||||||
assertThat(result).isNull();
|
assertThat(result).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
@Test
|
||||||
public void testNoIcon_BuildSearchResultException() {
|
public void testNoIcon_BuildSearchResultException() {
|
||||||
mBuilder.addTitle(mTitle)
|
mBuilder.addTitle(mTitle)
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
@@ -47,6 +47,8 @@ public class SearchFragmentTest {
|
|||||||
private Context mContext;
|
private Context mContext;
|
||||||
@Mock
|
@Mock
|
||||||
private DatabaseResultLoader mDatabaseResultLoader;
|
private DatabaseResultLoader mDatabaseResultLoader;
|
||||||
|
@Mock
|
||||||
|
private InstalledAppResultLoader mInstalledAppResultLoader;
|
||||||
private FakeFeatureFactory mFeatureFactory;
|
private FakeFeatureFactory mFeatureFactory;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -54,14 +56,16 @@ public class SearchFragmentTest {
|
|||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
FakeFeatureFactory.setupForTest(mContext);
|
FakeFeatureFactory.setupForTest(mContext);
|
||||||
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(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
|
@Test
|
||||||
public void screenRotate_shouldPersistQuery() {
|
public void screenRotate_shouldPersistQuery() {
|
||||||
when(mFeatureFactory.searchFeatureProvider
|
|
||||||
.getDatabaseSearchLoader(any(Context.class), anyString()))
|
|
||||||
.thenReturn(mDatabaseResultLoader);
|
|
||||||
|
|
||||||
final Bundle bundle = new Bundle();
|
final Bundle bundle = new Bundle();
|
||||||
final String testQuery = "test";
|
final String testQuery = "test";
|
||||||
ActivityController<SearchActivity> activityController =
|
ActivityController<SearchActivity> activityController =
|
||||||
@@ -79,14 +83,12 @@ public class SearchFragmentTest {
|
|||||||
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider)
|
verify(mFeatureFactory.searchFeatureProvider)
|
||||||
.getDatabaseSearchLoader(any(Context.class), anyString());
|
.getDatabaseSearchLoader(any(Context.class), anyString());
|
||||||
|
verify(mFeatureFactory.searchFeatureProvider)
|
||||||
|
.getInstalledAppSearchLoader(any(Context.class), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void queryTextChange_shouldTriggerLoader() {
|
public void queryTextChange_shouldTriggerLoader() {
|
||||||
when(mFeatureFactory.searchFeatureProvider
|
|
||||||
.getDatabaseSearchLoader(any(Context.class), anyString()))
|
|
||||||
.thenReturn(mDatabaseResultLoader);
|
|
||||||
|
|
||||||
final String testQuery = "test";
|
final String testQuery = "test";
|
||||||
ActivityController<SearchActivity> activityController =
|
ActivityController<SearchActivity> activityController =
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
Robolectric.buildActivity(SearchActivity.class);
|
||||||
@@ -98,5 +100,7 @@ public class SearchFragmentTest {
|
|||||||
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider)
|
verify(mFeatureFactory.searchFeatureProvider)
|
||||||
.getDatabaseSearchLoader(any(Context.class), anyString());
|
.getDatabaseSearchLoader(any(Context.class), anyString());
|
||||||
|
verify(mFeatureFactory.searchFeatureProvider)
|
||||||
|
.getInstalledAppSearchLoader(any(Context.class), anyString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user