Remove search v1
Fixes: 69851037 Test: robotests Change-Id: I53bc6408116031619053066055cb26cac67b9945
This commit is contained in:
@@ -242,13 +242,6 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".search.SearchActivity"
|
|
||||||
android:label="@string/search_settings"
|
|
||||||
android:icon="@drawable/ic_search_24dp"
|
|
||||||
android:parentActivityName="Settings"
|
|
||||||
android:theme="@style/Theme.Settings.NoActionBar">
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name=".search.SearchResultTrampoline"
|
<activity android:name=".search.SearchResultTrampoline"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
android:theme="@android:style/Theme.NoDisplay"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
@@ -20,7 +20,6 @@ package com.android.settings.core;
|
|||||||
* This class keeps track of all feature flags in Settings.
|
* This class keeps track of all feature flags in Settings.
|
||||||
*/
|
*/
|
||||||
public class FeatureFlags {
|
public class FeatureFlags {
|
||||||
public static final String SEARCH_V2 = "settings_search_v2";
|
|
||||||
public static final String APP_INFO_V2 = "settings_app_info_v2";
|
public static final String APP_INFO_V2 = "settings_app_info_v2";
|
||||||
public static final String CONNECTED_DEVICE_V2 = "settings_connected_device_v2";
|
public static final String CONNECTED_DEVICE_V2 = "settings_connected_device_v2";
|
||||||
public static final String BATTERY_SETTINGS_V2 = "settings_battery_v2";
|
public static final String BATTERY_SETTINGS_V2 = "settings_battery_v2";
|
||||||
|
@@ -1,141 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import static com.android.settings.search.InstalledAppResultLoader.getWordDifference;
|
|
||||||
|
|
||||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.content.pm.ServiceInfo;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.util.IconDrawableFactory;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.accessibility.AccessibilityManager;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.accessibility.AccessibilitySettings;
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.FutureTask;
|
|
||||||
|
|
||||||
public class AccessibilityServiceResultLoader extends
|
|
||||||
FutureTask<List<? extends SearchResult>> {
|
|
||||||
|
|
||||||
private static final String TAG = "A11yResultFutureTask";
|
|
||||||
|
|
||||||
public AccessibilityServiceResultLoader(Context context, String query,
|
|
||||||
SiteMapManager manager) {
|
|
||||||
super(new AccessibilityServiceResultCallable(context, query, manager));
|
|
||||||
}
|
|
||||||
|
|
||||||
static class AccessibilityServiceResultCallable implements
|
|
||||||
Callable<List<? extends SearchResult>> {
|
|
||||||
|
|
||||||
private static final int NAME_NO_MATCH = -1;
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
private List<String> mBreadcrumb;
|
|
||||||
private SiteMapManager mSiteMapManager;
|
|
||||||
@VisibleForTesting
|
|
||||||
final String mQuery;
|
|
||||||
private final AccessibilityManager mAccessibilityManager;
|
|
||||||
private final PackageManager mPackageManager;
|
|
||||||
private final int mUserId;
|
|
||||||
|
|
||||||
public AccessibilityServiceResultCallable(Context context, String query,
|
|
||||||
SiteMapManager mapManager) {
|
|
||||||
mUserId = UserHandle.myUserId();
|
|
||||||
mContext = context;
|
|
||||||
mSiteMapManager = mapManager;
|
|
||||||
mPackageManager = context.getPackageManager();
|
|
||||||
mAccessibilityManager =
|
|
||||||
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
|
||||||
mQuery = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<? extends SearchResult> call() throws Exception {
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
final List<SearchResult> results = new ArrayList<>();
|
|
||||||
final List<AccessibilityServiceInfo> services = mAccessibilityManager
|
|
||||||
.getInstalledAccessibilityServiceList();
|
|
||||||
final IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(mContext);
|
|
||||||
final String screenTitle = mContext.getString(R.string.accessibility_settings);
|
|
||||||
for (AccessibilityServiceInfo service : services) {
|
|
||||||
if (service == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final ResolveInfo resolveInfo = service.getResolveInfo();
|
|
||||||
if (service.getResolveInfo() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
|
|
||||||
final CharSequence title = resolveInfo.loadLabel(mPackageManager);
|
|
||||||
final int wordDiff = getWordDifference(title.toString(), mQuery);
|
|
||||||
if (wordDiff == NAME_NO_MATCH) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final Drawable icon;
|
|
||||||
if (resolveInfo.getIconResource() == 0) {
|
|
||||||
icon = ContextCompat.getDrawable(mContext, R.mipmap.ic_accessibility_generic);
|
|
||||||
} else {
|
|
||||||
icon = iconFactory.getBadgedIcon(
|
|
||||||
resolveInfo.serviceInfo,
|
|
||||||
resolveInfo.serviceInfo.applicationInfo,
|
|
||||||
mUserId);
|
|
||||||
}
|
|
||||||
final String componentName = new ComponentName(serviceInfo.packageName,
|
|
||||||
serviceInfo.name).flattenToString();
|
|
||||||
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(mContext,
|
|
||||||
AccessibilitySettings.class.getName(), componentName, screenTitle);
|
|
||||||
|
|
||||||
results.add(new SearchResult.Builder()
|
|
||||||
.setTitle(title)
|
|
||||||
.addBreadcrumbs(getBreadCrumb())
|
|
||||||
.setPayload(new ResultPayload(intent))
|
|
||||||
.setRank(wordDiff)
|
|
||||||
.setIcon(icon)
|
|
||||||
.setStableId(Objects.hash(screenTitle, componentName))
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
Collections.sort(results);
|
|
||||||
Log.i(TAG, "A11y search loading took:" + (System.currentTimeMillis() - startTime));
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getBreadCrumb() {
|
|
||||||
if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
|
|
||||||
mBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
|
||||||
mContext, AccessibilitySettings.class.getName(),
|
|
||||||
mContext.getString(R.string.accessibility_settings));
|
|
||||||
}
|
|
||||||
return mBreadcrumb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
|
|
||||||
public class AppSearchResult extends SearchResult {
|
|
||||||
/**
|
|
||||||
* Installed app's ApplicationInfo for delayed loading of icons
|
|
||||||
*/
|
|
||||||
public final ApplicationInfo info;
|
|
||||||
|
|
||||||
public AppSearchResult(Builder builder) {
|
|
||||||
super(builder);
|
|
||||||
info = builder.mInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserHandle getAppUserHandle() {
|
|
||||||
return new UserHandle(UserHandle.getUserId(info.uid));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder extends SearchResult.Builder {
|
|
||||||
protected ApplicationInfo mInfo;
|
|
||||||
|
|
||||||
public SearchResult.Builder setAppInfo(ApplicationInfo info) {
|
|
||||||
mInfo = info;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AppSearchResult build() {
|
|
||||||
return new AppSearchResult(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -18,35 +18,11 @@
|
|||||||
package com.android.settings.search;
|
package com.android.settings.search;
|
||||||
|
|
||||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns;
|
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns;
|
||||||
import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.FutureTask;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AsyncTask to retrieve Settings, first party app and any intent based results.
|
* AsyncTask to retrieve Settings, first party app and any intent based results.
|
||||||
*/
|
*/
|
||||||
public class DatabaseResultLoader extends FutureTask<List<? extends SearchResult>> {
|
public class DatabaseResultLoader {
|
||||||
|
|
||||||
private static final String TAG = "DatabaseResultLoader";
|
private static final String TAG = "DatabaseResultLoader";
|
||||||
|
|
||||||
@@ -66,18 +42,6 @@ public class DatabaseResultLoader extends FutureTask<List<? extends SearchResult
|
|||||||
IndexColumns.PAYLOAD
|
IndexColumns.PAYLOAD
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final String[] MATCH_COLUMNS_PRIMARY = {
|
|
||||||
IndexColumns.DATA_TITLE,
|
|
||||||
IndexColumns.DATA_TITLE_NORMALIZED,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static final String[] MATCH_COLUMNS_SECONDARY = {
|
|
||||||
IndexColumns.DATA_SUMMARY_ON,
|
|
||||||
IndexColumns.DATA_SUMMARY_ON_NORMALIZED,
|
|
||||||
IndexColumns.DATA_SUMMARY_OFF,
|
|
||||||
IndexColumns.DATA_SUMMARY_OFF_NORMALIZED,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base ranks defines the best possible rank based on what the query matches.
|
* Base ranks defines the best possible rank based on what the query matches.
|
||||||
* If the query matches the prefix of the first word in the title, the best rank it can be
|
* If the query matches the prefix of the first word in the title, the best rank it can be
|
||||||
@@ -89,256 +53,4 @@ public class DatabaseResultLoader extends FutureTask<List<? extends SearchResult
|
|||||||
*/
|
*/
|
||||||
public static final int[] BASE_RANKS = {1, 3, 7, 9};
|
public static final int[] BASE_RANKS = {1, 3, 7, 9};
|
||||||
|
|
||||||
public DatabaseResultLoader(Context context, String query, SiteMapManager manager) {
|
|
||||||
super(new StaticSearchResultCallable(context, query, manager));
|
|
||||||
}
|
|
||||||
|
|
||||||
static class StaticSearchResultCallable implements
|
|
||||||
Callable<List<? extends SearchResult>> {
|
|
||||||
|
|
||||||
public final String[] MATCH_COLUMNS_TERTIARY = {
|
|
||||||
IndexColumns.DATA_KEYWORDS,
|
|
||||||
IndexColumns.DATA_ENTRIES
|
|
||||||
};
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
final String mQueryText;
|
|
||||||
private final Context mContext;
|
|
||||||
private final CursorToSearchResultConverter mConverter;
|
|
||||||
private final SiteMapManager mSiteMapManager;
|
|
||||||
private final SearchFeatureProvider mFeatureProvider;
|
|
||||||
|
|
||||||
public StaticSearchResultCallable(Context context, String queryText,
|
|
||||||
SiteMapManager mapManager) {
|
|
||||||
mContext = context;
|
|
||||||
mSiteMapManager = mapManager;
|
|
||||||
mQueryText = queryText;
|
|
||||||
mConverter = new CursorToSearchResultConverter(context);
|
|
||||||
mFeatureProvider = FeatureFactory.getFactory(context).getSearchFeatureProvider();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<? extends SearchResult> call() {
|
|
||||||
if (mQueryText == null || mQueryText.isEmpty()) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO (b/68656233) Consolidate timing metrics
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
// Start a Future to get search result scores.
|
|
||||||
FutureTask<List<Pair<String, Float>>> rankerTask = mFeatureProvider.getRankerTask(
|
|
||||||
mContext, mQueryText);
|
|
||||||
|
|
||||||
if (rankerTask != null) {
|
|
||||||
ExecutorService executorService = mFeatureProvider.getExecutorService();
|
|
||||||
executorService.execute(rankerTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Set<SearchResult> resultSet = new HashSet<>();
|
|
||||||
|
|
||||||
resultSet.addAll(firstWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]));
|
|
||||||
resultSet.addAll(secondaryWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[1]));
|
|
||||||
resultSet.addAll(anyWordQuery(MATCH_COLUMNS_SECONDARY, BASE_RANKS[2]));
|
|
||||||
resultSet.addAll(anyWordQuery(MATCH_COLUMNS_TERTIARY, BASE_RANKS[3]));
|
|
||||||
|
|
||||||
// Try to retrieve the scores in time. Otherwise use static ranking.
|
|
||||||
if (rankerTask != null) {
|
|
||||||
try {
|
|
||||||
final long timeoutMs = mFeatureProvider.smartSearchRankingTimeoutMs(mContext);
|
|
||||||
List<Pair<String, Float>> searchRankScores = rankerTask.get(timeoutMs,
|
|
||||||
TimeUnit.MILLISECONDS);
|
|
||||||
return getDynamicRankedResults(resultSet, searchRankScores);
|
|
||||||
} catch (TimeoutException | InterruptedException | ExecutionException e) {
|
|
||||||
Log.d(TAG, "Error waiting for result scores: " + e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SearchResult> resultList = new ArrayList<>(resultSet);
|
|
||||||
Collections.sort(resultList);
|
|
||||||
Log.i(TAG, "Static search loading took:" + (System.currentTimeMillis() - startTime));
|
|
||||||
return resultList;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO (b/33577327) Retrieve all search results with a single query.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and executes the query which matches prefixes of the first word of the given
|
|
||||||
* columns.
|
|
||||||
*
|
|
||||||
* @param matchColumns The columns to match on
|
|
||||||
* @param baseRank The highest rank achievable by these results
|
|
||||||
* @return A set of the matching results.
|
|
||||||
*/
|
|
||||||
private Set<SearchResult> firstWordQuery(String[] matchColumns, int baseRank) {
|
|
||||||
final String whereClause = buildSingleWordWhereClause(matchColumns);
|
|
||||||
final String query = mQueryText + "%";
|
|
||||||
final String[] selection = buildSingleWordSelection(query, matchColumns.length);
|
|
||||||
|
|
||||||
return query(whereClause, selection, baseRank);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and executes the query which matches prefixes of the non-first words of the
|
|
||||||
* given columns.
|
|
||||||
*
|
|
||||||
* @param matchColumns The columns to match on
|
|
||||||
* @param baseRank The highest rank achievable by these results
|
|
||||||
* @return A set of the matching results.
|
|
||||||
*/
|
|
||||||
private Set<SearchResult> secondaryWordQuery(String[] matchColumns, int baseRank) {
|
|
||||||
final String whereClause = buildSingleWordWhereClause(matchColumns);
|
|
||||||
final String query = "% " + mQueryText + "%";
|
|
||||||
final String[] selection = buildSingleWordSelection(query, matchColumns.length);
|
|
||||||
|
|
||||||
return query(whereClause, selection, baseRank);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and executes the query which matches prefixes of the any word of the given
|
|
||||||
* columns.
|
|
||||||
*
|
|
||||||
* @param matchColumns The columns to match on
|
|
||||||
* @param baseRank The highest rank achievable by these results
|
|
||||||
* @return A set of the matching results.
|
|
||||||
*/
|
|
||||||
private Set<SearchResult> anyWordQuery(String[] matchColumns, int baseRank) {
|
|
||||||
final String whereClause = buildTwoWordWhereClause(matchColumns);
|
|
||||||
final String[] selection = buildAnyWordSelection(matchColumns.length * 2);
|
|
||||||
|
|
||||||
return query(whereClause, selection, baseRank);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic method used by all of the query methods above to execute a query.
|
|
||||||
*
|
|
||||||
* @param whereClause Where clause for the SQL query which uses bindings.
|
|
||||||
* @param selection List of the transformed query to match each bind in the whereClause
|
|
||||||
* @param baseRank The highest rank achievable by these results.
|
|
||||||
* @return A set of the matching results.
|
|
||||||
*/
|
|
||||||
private Set<SearchResult> query(String whereClause, String[] selection, int baseRank) {
|
|
||||||
final SQLiteDatabase database =
|
|
||||||
IndexDatabaseHelper.getInstance(mContext).getReadableDatabase();
|
|
||||||
try (Cursor resultCursor = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS,
|
|
||||||
whereClause,
|
|
||||||
selection, null, null, null)) {
|
|
||||||
return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the SQLite WHERE clause that matches all matchColumns for a single query.
|
|
||||||
*
|
|
||||||
* @param matchColumns List of columns that will be used for matching.
|
|
||||||
* @return The constructed WHERE clause.
|
|
||||||
*/
|
|
||||||
private static String buildSingleWordWhereClause(String[] matchColumns) {
|
|
||||||
StringBuilder sb = new StringBuilder(" (");
|
|
||||||
final int count = matchColumns.length;
|
|
||||||
for (int n = 0; n < count; n++) {
|
|
||||||
sb.append(matchColumns[n]);
|
|
||||||
sb.append(" like ? ");
|
|
||||||
if (n < count - 1) {
|
|
||||||
sb.append(" OR ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.append(") AND enabled = 1");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the SQLite WHERE clause that matches all matchColumns to two different queries.
|
|
||||||
*
|
|
||||||
* @param matchColumns List of columns that will be used for matching.
|
|
||||||
* @return The constructed WHERE clause.
|
|
||||||
*/
|
|
||||||
private static String buildTwoWordWhereClause(String[] matchColumns) {
|
|
||||||
StringBuilder sb = new StringBuilder(" (");
|
|
||||||
final int count = matchColumns.length;
|
|
||||||
for (int n = 0; n < count; n++) {
|
|
||||||
sb.append(matchColumns[n]);
|
|
||||||
sb.append(" like ? OR ");
|
|
||||||
sb.append(matchColumns[n]);
|
|
||||||
sb.append(" like ?");
|
|
||||||
if (n < count - 1) {
|
|
||||||
sb.append(" OR ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.append(") AND enabled = 1");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fills out the selection array to match the query as the prefix of a single word.
|
|
||||||
*
|
|
||||||
* @param size is the number of columns to be matched.
|
|
||||||
*/
|
|
||||||
private String[] buildSingleWordSelection(String query, int size) {
|
|
||||||
String[] selection = new String[size];
|
|
||||||
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
selection[i] = query;
|
|
||||||
}
|
|
||||||
return selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fills out the selection array to match the query as the prefix of a word.
|
|
||||||
*
|
|
||||||
* @param size is twice the number of columns to be matched. The first match is for the
|
|
||||||
* prefix
|
|
||||||
* of the first word in the column. The second match is for any subsequent word
|
|
||||||
* prefix match.
|
|
||||||
*/
|
|
||||||
private String[] buildAnyWordSelection(int size) {
|
|
||||||
String[] selection = new String[size];
|
|
||||||
final String query = mQueryText + "%";
|
|
||||||
final String subStringQuery = "% " + mQueryText + "%";
|
|
||||||
|
|
||||||
for (int i = 0; i < (size - 1); i += 2) {
|
|
||||||
selection[i] = query;
|
|
||||||
selection[i + 1] = subStringQuery;
|
|
||||||
}
|
|
||||||
return selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SearchResult> getDynamicRankedResults(Set<SearchResult> unsortedSet,
|
|
||||||
List<Pair<String, Float>> searchRankScores) {
|
|
||||||
TreeSet<SearchResult> dbResultsSortedByScores = new TreeSet<>(
|
|
||||||
(o1, o2) -> {
|
|
||||||
float score1 = getRankingScoreByStableId(searchRankScores, o1.stableId);
|
|
||||||
float score2 = getRankingScoreByStableId(searchRankScores, o2.stableId);
|
|
||||||
if (score1 > score2) {
|
|
||||||
return -1;
|
|
||||||
} else if (score1 == score2) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dbResultsSortedByScores.addAll(unsortedSet);
|
|
||||||
|
|
||||||
return new ArrayList<>(dbResultsSortedByScores);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up ranking score for stableId
|
|
||||||
*
|
|
||||||
* @param stableId String of stableId
|
|
||||||
* @return the ranking score corresponding to the given stableId. If there is no score
|
|
||||||
* available for this stableId, -Float.MAX_VALUE is returned.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
Float getRankingScoreByStableId(List<Pair<String, Float>> searchRankScores, int stableId) {
|
|
||||||
for (Pair<String, Float> rankingScore : searchRankScores) {
|
|
||||||
if (Integer.toString(stableId).compareTo(rankingScore.first) == 0) {
|
|
||||||
return rankingScore.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If stableId not found in the list, we assign the minimum score so it will appear at
|
|
||||||
// the end of the list.
|
|
||||||
Log.w(TAG, "stableId " + stableId + " was not in the ranking scores.");
|
|
||||||
return -Float.MAX_VALUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Pair;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Switch;
|
|
||||||
|
|
||||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
|
||||||
import com.android.settings.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ViewHolder for Settings represented as SwitchPreferences.
|
|
||||||
*/
|
|
||||||
public class InlineSwitchViewHolder extends SearchViewHolder {
|
|
||||||
|
|
||||||
public final Switch switchView;
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
|
|
||||||
public InlineSwitchViewHolder(View view, Context context) {
|
|
||||||
super(view);
|
|
||||||
mContext = context;
|
|
||||||
switchView = view.findViewById(R.id.switchView);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getClickActionMetricName() {
|
|
||||||
return MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_INLINE_RESULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(SearchFragment fragment, final SearchResult result) {
|
|
||||||
super.onBind(fragment, result);
|
|
||||||
if (mContext == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final InlineSwitchPayload payload = (InlineSwitchPayload) result.payload;
|
|
||||||
switchView.setChecked(payload.getValue(mContext) == InlineSwitchPayload.TRUE);
|
|
||||||
switchView.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
|
||||||
final Pair<Integer, Object> value = Pair.create(
|
|
||||||
MetricsEvent.FIELD_SETTINGS_SEARCH_INLINE_RESULT_VALUE, isChecked
|
|
||||||
? 1L : 0L);
|
|
||||||
fragment.onSearchResultClicked(this, result, value);
|
|
||||||
int newValue = isChecked ? InlineSwitchPayload.TRUE : InlineSwitchPayload.FALSE;
|
|
||||||
payload.setValue(mContext, newValue);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,211 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ServiceInfo;
|
|
||||||
import android.hardware.input.InputManager;
|
|
||||||
import android.hardware.input.KeyboardLayout;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.InputDevice;
|
|
||||||
import android.view.inputmethod.InputMethodInfo;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.view.inputmethod.InputMethodSubtype;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
|
||||||
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
|
|
||||||
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
|
|
||||||
import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.FutureTask;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search result for input devices (physical/virtual keyboard, game controllers, etc)
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class InputDeviceResultLoader extends FutureTask<List<? extends SearchResult>> {
|
|
||||||
|
|
||||||
private static final String TAG = "InputResultFutureTask";
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static final String PHYSICAL_KEYBOARD_FRAGMENT = PhysicalKeyboardFragment.class.getName();
|
|
||||||
@VisibleForTesting
|
|
||||||
static final String VIRTUAL_KEYBOARD_FRAGMENT =
|
|
||||||
AvailableVirtualKeyboardFragment.class.getName();
|
|
||||||
|
|
||||||
public InputDeviceResultLoader(Context context, String query, SiteMapManager manager) {
|
|
||||||
super(new InputDeviceResultCallable(context, query, manager));
|
|
||||||
}
|
|
||||||
|
|
||||||
static class InputDeviceResultCallable implements
|
|
||||||
Callable<List<? extends SearchResult>> {
|
|
||||||
private static final int NAME_NO_MATCH = -1;
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
private final SiteMapManager mSiteMapManager;
|
|
||||||
private final InputManager mInputManager;
|
|
||||||
private final InputMethodManager mImm;
|
|
||||||
private final PackageManager mPackageManager;
|
|
||||||
@VisibleForTesting
|
|
||||||
final String mQuery;
|
|
||||||
|
|
||||||
private List<String> mPhysicalKeyboardBreadcrumb;
|
|
||||||
private List<String> mVirtualKeyboardBreadcrumb;
|
|
||||||
|
|
||||||
public InputDeviceResultCallable(Context context, String query, SiteMapManager mapManager) {
|
|
||||||
mContext = context;
|
|
||||||
mQuery = query;
|
|
||||||
mSiteMapManager = mapManager;
|
|
||||||
mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
|
||||||
mImm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
|
|
||||||
mPackageManager = context.getPackageManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<? extends SearchResult> call() {
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
final List<SearchResult> results = new ArrayList<>();
|
|
||||||
results.addAll(buildPhysicalKeyboardSearchResults());
|
|
||||||
results.addAll(buildVirtualKeyboardSearchResults());
|
|
||||||
Collections.sort(results);
|
|
||||||
Log.i(TAG, "Input search loading took:" + (System.currentTimeMillis() - startTime));
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<SearchResult> buildPhysicalKeyboardSearchResults() {
|
|
||||||
final Set<SearchResult> results = new HashSet<>();
|
|
||||||
final String screenTitle = mContext.getString(R.string.physical_keyboard_title);
|
|
||||||
|
|
||||||
for (final InputDevice device : getPhysicalFullKeyboards()) {
|
|
||||||
final String deviceName = device.getName();
|
|
||||||
final int wordDiff = InstalledAppResultLoader.getWordDifference(deviceName,
|
|
||||||
mQuery);
|
|
||||||
if (wordDiff == NAME_NO_MATCH) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final String keyboardLayoutDescriptor = mInputManager
|
|
||||||
.getCurrentKeyboardLayoutForInputDevice(device.getIdentifier());
|
|
||||||
final KeyboardLayout keyboardLayout = (keyboardLayoutDescriptor != null)
|
|
||||||
? mInputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
|
|
||||||
final String summary = (keyboardLayout != null)
|
|
||||||
? keyboardLayout.toString()
|
|
||||||
: mContext.getString(R.string.keyboard_layout_default_label);
|
|
||||||
|
|
||||||
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(mContext,
|
|
||||||
PHYSICAL_KEYBOARD_FRAGMENT, deviceName, screenTitle);
|
|
||||||
results.add(new SearchResult.Builder()
|
|
||||||
.setTitle(deviceName)
|
|
||||||
.setPayload(new ResultPayload(intent))
|
|
||||||
.setStableId(Objects.hash(PHYSICAL_KEYBOARD_FRAGMENT, deviceName))
|
|
||||||
.setSummary(summary)
|
|
||||||
.setRank(wordDiff)
|
|
||||||
.addBreadcrumbs(getPhysicalKeyboardBreadCrumb())
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<SearchResult> buildVirtualKeyboardSearchResults() {
|
|
||||||
final Set<SearchResult> results = new HashSet<>();
|
|
||||||
final String screenTitle = mContext.getString(R.string.add_virtual_keyboard);
|
|
||||||
final List<InputMethodInfo> inputMethods = mImm.getInputMethodList();
|
|
||||||
for (InputMethodInfo info : inputMethods) {
|
|
||||||
final String title = info.loadLabel(mPackageManager).toString();
|
|
||||||
final String summary = InputMethodAndSubtypeUtil
|
|
||||||
.getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), mContext, info);
|
|
||||||
int wordDiff = InstalledAppResultLoader.getWordDifference(title, mQuery);
|
|
||||||
if (wordDiff == NAME_NO_MATCH) {
|
|
||||||
wordDiff = InstalledAppResultLoader.getWordDifference(summary, mQuery);
|
|
||||||
}
|
|
||||||
if (wordDiff == NAME_NO_MATCH) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final ServiceInfo serviceInfo = info.getServiceInfo();
|
|
||||||
final String key = new ComponentName(serviceInfo.packageName, serviceInfo.name)
|
|
||||||
.flattenToString();
|
|
||||||
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(mContext,
|
|
||||||
VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle);
|
|
||||||
results.add(new SearchResult.Builder()
|
|
||||||
.setTitle(title)
|
|
||||||
.setSummary(summary)
|
|
||||||
.setRank(wordDiff)
|
|
||||||
.setStableId(Objects.hash(VIRTUAL_KEYBOARD_FRAGMENT, key))
|
|
||||||
.addBreadcrumbs(getVirtualKeyboardBreadCrumb())
|
|
||||||
.setPayload(new ResultPayload(intent))
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getPhysicalKeyboardBreadCrumb() {
|
|
||||||
if (mPhysicalKeyboardBreadcrumb == null || mPhysicalKeyboardBreadcrumb.isEmpty()) {
|
|
||||||
mPhysicalKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
|
||||||
mContext, PHYSICAL_KEYBOARD_FRAGMENT,
|
|
||||||
mContext.getString(R.string.physical_keyboard_title));
|
|
||||||
}
|
|
||||||
return mPhysicalKeyboardBreadcrumb;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private List<String> getVirtualKeyboardBreadCrumb() {
|
|
||||||
if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) {
|
|
||||||
final Context context = mContext;
|
|
||||||
mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
|
||||||
context, VIRTUAL_KEYBOARD_FRAGMENT,
|
|
||||||
context.getString(R.string.add_virtual_keyboard));
|
|
||||||
}
|
|
||||||
return mVirtualKeyboardBreadcrumb;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<InputDevice> getPhysicalFullKeyboards() {
|
|
||||||
final List<InputDevice> keyboards = new ArrayList<>();
|
|
||||||
final int[] deviceIds = InputDevice.getDeviceIds();
|
|
||||||
if (deviceIds != null) {
|
|
||||||
for (int deviceId : deviceIds) {
|
|
||||||
final InputDevice device = InputDevice.getDevice(deviceId);
|
|
||||||
if (device != null && !device.isVirtual() && device.isFullKeyboard()) {
|
|
||||||
keyboards.add(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return keyboards;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) {
|
|
||||||
final int subtypeCount = imi.getSubtypeCount();
|
|
||||||
final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
|
|
||||||
for (int index = 0; index < subtypeCount; index++) {
|
|
||||||
allSubtypes.add(imi.getSubtypeAt(index));
|
|
||||||
}
|
|
||||||
return allSubtypes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,260 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.content.pm.UserInfo;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.os.UserManager;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.internal.logging.nano.MetricsProto;
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.SettingsActivity;
|
|
||||||
import com.android.settings.applications.manageapplications.ManageApplications;
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
|
||||||
import com.android.settingslib.wrapper.PackageManagerWrapper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.FutureTask;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search loader for installed apps.
|
|
||||||
*/
|
|
||||||
public class InstalledAppResultLoader extends FutureTask<List<? extends SearchResult>> {
|
|
||||||
|
|
||||||
private static final String TAG = "InstalledAppFutureTask";
|
|
||||||
|
|
||||||
private static final int NAME_NO_MATCH = -1;
|
|
||||||
private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
|
|
||||||
.addCategory(Intent.CATEGORY_LAUNCHER);
|
|
||||||
|
|
||||||
public InstalledAppResultLoader(Context context, PackageManagerWrapper wrapper,
|
|
||||||
String query, SiteMapManager manager) {
|
|
||||||
super(new InstalledAppResultCallable(context, wrapper, query, manager));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns "difference" between appName and query string. appName must contain all
|
|
||||||
* characters from query as a prefix to a word, in the same order.
|
|
||||||
* If not, returns NAME_NO_MATCH.
|
|
||||||
* If they do match, returns an int value representing how different they are,
|
|
||||||
* and larger values means they are less similar.
|
|
||||||
* <p/>
|
|
||||||
* Example:
|
|
||||||
* appName: Abcde, query: Abcde, Returns 0
|
|
||||||
* appName: Abcde, query: abc, Returns 2
|
|
||||||
* appName: Abcde, query: ab, Returns 3
|
|
||||||
* appName: Abcde, query: bc, Returns NAME_NO_MATCH
|
|
||||||
* appName: Abcde, query: xyz, Returns NAME_NO_MATCH
|
|
||||||
* appName: Abc de, query: de, Returns 4
|
|
||||||
* TODO: Move this to a common util class.
|
|
||||||
*/
|
|
||||||
static int getWordDifference(String appName, String query) {
|
|
||||||
if (TextUtils.isEmpty(appName) || TextUtils.isEmpty(query)) {
|
|
||||||
return NAME_NO_MATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
final char[] queryTokens = query.toLowerCase().toCharArray();
|
|
||||||
final char[] appTokens = appName.toLowerCase().toCharArray();
|
|
||||||
final int appLength = appTokens.length;
|
|
||||||
if (queryTokens.length > appLength) {
|
|
||||||
return NAME_NO_MATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
int j;
|
|
||||||
|
|
||||||
while (i < appLength) {
|
|
||||||
j = 0;
|
|
||||||
// Currently matching a prefix
|
|
||||||
while ((i + j < appLength) && (queryTokens[j] == appTokens[i + j])) {
|
|
||||||
// Matched the entire query
|
|
||||||
if (++j >= queryTokens.length) {
|
|
||||||
// Use the diff in length as a proxy of how close the 2 words match.
|
|
||||||
// Value range from 0 to infinity.
|
|
||||||
return appLength - queryTokens.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i += j;
|
|
||||||
|
|
||||||
// Remaining string is longer that the query or we have search the whole app name.
|
|
||||||
if (queryTokens.length > appLength - i) {
|
|
||||||
return NAME_NO_MATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the first index where app name and query name are different
|
|
||||||
// Find the next space in the app name or the end of the app name.
|
|
||||||
while ((i < appLength) && (!Character.isWhitespace(appTokens[i++]))) ;
|
|
||||||
|
|
||||||
// Find the start of the next word
|
|
||||||
while ((i < appLength) && !(Character.isLetter(appTokens[i])
|
|
||||||
|| Character.isDigit(appTokens[i]))) {
|
|
||||||
// Increment in body because we cannot guarantee which condition was true
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NAME_NO_MATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
static class InstalledAppResultCallable implements
|
|
||||||
Callable<List<? extends SearchResult>> {
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
private List<String> mBreadcrumb;
|
|
||||||
private SiteMapManager mSiteMapManager;
|
|
||||||
@VisibleForTesting
|
|
||||||
final String mQuery;
|
|
||||||
private final UserManager mUserManager;
|
|
||||||
private final PackageManagerWrapper mPackageManager;
|
|
||||||
private final List<ResolveInfo> mHomeActivities = new ArrayList<>();
|
|
||||||
|
|
||||||
public InstalledAppResultCallable(Context context, PackageManagerWrapper pmWrapper,
|
|
||||||
String query, SiteMapManager mapManager) {
|
|
||||||
mContext = context;
|
|
||||||
mSiteMapManager = mapManager;
|
|
||||||
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
|
|
||||||
mPackageManager = pmWrapper;
|
|
||||||
mQuery = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<? extends SearchResult> call() throws Exception {
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
final List<AppSearchResult> results = new ArrayList<>();
|
|
||||||
final PackageManager pm = mPackageManager.getPackageManager();
|
|
||||||
|
|
||||||
mHomeActivities.clear();
|
|
||||||
mPackageManager.getHomeActivities(mHomeActivities);
|
|
||||||
|
|
||||||
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 (!shouldIncludeAsCandidate(info, user)) {
|
|
||||||
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))
|
|
||||||
.putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY,
|
|
||||||
MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS);
|
|
||||||
|
|
||||||
final AppSearchResult.Builder builder = new AppSearchResult.Builder();
|
|
||||||
builder.setAppInfo(info)
|
|
||||||
.setStableId(Objects.hash(info.packageName, user.id))
|
|
||||||
.setTitle(info.loadLabel(pm))
|
|
||||||
.setRank(getRank(wordDiff))
|
|
||||||
.addBreadcrumbs(getBreadCrumb())
|
|
||||||
.setPayload(new ResultPayload(intent));
|
|
||||||
results.add(builder.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collections.sort(results);
|
|
||||||
Log.i(TAG, "App search loading took:" + (System.currentTimeMillis() - startTime));
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the candidate should be included in candidate list
|
|
||||||
* <p/>
|
|
||||||
* This method matches logic in {@code ApplicationState#FILTER_DOWNLOADED_AND_LAUNCHER}.
|
|
||||||
*/
|
|
||||||
private boolean shouldIncludeAsCandidate(ApplicationInfo info, UserInfo user) {
|
|
||||||
// Not system app
|
|
||||||
if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
|
|
||||||
|| (info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Shows up in launcher
|
|
||||||
final Intent launchIntent = new Intent(LAUNCHER_PROBE)
|
|
||||||
.setPackage(info.packageName);
|
|
||||||
final List<ResolveInfo> intents = mPackageManager.queryIntentActivitiesAsUser(
|
|
||||||
launchIntent,
|
|
||||||
PackageManager.MATCH_DISABLED_COMPONENTS
|
|
||||||
| PackageManager.MATCH_DIRECT_BOOT_AWARE
|
|
||||||
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
|
|
||||||
user.id);
|
|
||||||
if (intents != null && intents.size() != 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Is launcher app itself
|
|
||||||
return isPackageInList(mHomeActivities, info.packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<UserInfo> getUsersToCount() {
|
|
||||||
return mUserManager.getProfiles(UserHandle.myUserId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPackageInList(List<ResolveInfo> resolveInfos, String pkg) {
|
|
||||||
for (ResolveInfo info : resolveInfos) {
|
|
||||||
if (TextUtils.equals(info.activityInfo.packageName, pkg)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getBreadCrumb() {
|
|
||||||
if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
|
|
||||||
mBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
|
||||||
mContext, ManageApplications.class.getName(),
|
|
||||||
mContext.getString(R.string.applications_settings));
|
|
||||||
}
|
|
||||||
return mBreadcrumb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A temporary ranking scheme for installed apps.
|
|
||||||
*
|
|
||||||
* @param wordDiff difference between query length and app name length.
|
|
||||||
* @return the ranking.
|
|
||||||
*/
|
|
||||||
private int getRank(int wordDiff) {
|
|
||||||
if (wordDiff < 6) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.android.internal.logging.nano.MetricsProto;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ViewHolder for intent based search results.
|
|
||||||
* The DatabaseResultLoader is the primary use case for this ViewHolder.
|
|
||||||
*/
|
|
||||||
public class IntentSearchViewHolder extends SearchViewHolder {
|
|
||||||
|
|
||||||
private static final String TAG = "IntentSearchViewHolder";
|
|
||||||
@VisibleForTesting
|
|
||||||
static final int REQUEST_CODE_NO_OP = 0;
|
|
||||||
|
|
||||||
public IntentSearchViewHolder(View view) {
|
|
||||||
super(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getClickActionMetricName() {
|
|
||||||
return MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(final SearchFragment fragment, final SearchResult result) {
|
|
||||||
super.onBind(fragment, result);
|
|
||||||
|
|
||||||
itemView.setOnClickListener(v -> {
|
|
||||||
fragment.onSearchResultClicked(this, result);
|
|
||||||
final Intent intent = result.payload.getIntent();
|
|
||||||
// Use app user id to support work profile use case.
|
|
||||||
if (result instanceof AppSearchResult) {
|
|
||||||
AppSearchResult appResult = (AppSearchResult) result;
|
|
||||||
UserHandle userHandle = appResult.getAppUserHandle();
|
|
||||||
fragment.getActivity().startActivityAsUser(intent, userHandle);
|
|
||||||
} else {
|
|
||||||
final PackageManager pm = fragment.getActivity().getPackageManager();
|
|
||||||
final List<ResolveInfo> info = pm.queryIntentActivities(intent, 0 /* flags */);
|
|
||||||
if (info != null && !info.isEmpty()) {
|
|
||||||
fragment.startActivityForResult(intent, REQUEST_CODE_NO_OP);
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Cannot launch search result, title: "
|
|
||||||
+ result.title + ", " + intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,127 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.app.LoaderManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Loader;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SavedQueryController implements LoaderManager.LoaderCallbacks,
|
|
||||||
MenuItem.OnMenuItemClickListener {
|
|
||||||
|
|
||||||
// TODO: make a generic background task manager to handle one-off tasks like this one.
|
|
||||||
private static final String ARG_QUERY = "remove_query";
|
|
||||||
private static final String TAG = "SearchSavedQueryCtrl";
|
|
||||||
|
|
||||||
private static final int MENU_SEARCH_HISTORY = 1000;
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
private final LoaderManager mLoaderManager;
|
|
||||||
private final SearchFeatureProvider mSearchFeatureProvider;
|
|
||||||
private final SearchResultsAdapter mResultAdapter;
|
|
||||||
|
|
||||||
public SavedQueryController(Context context, LoaderManager loaderManager,
|
|
||||||
SearchResultsAdapter resultsAdapter) {
|
|
||||||
mContext = context;
|
|
||||||
mLoaderManager = loaderManager;
|
|
||||||
mResultAdapter = resultsAdapter;
|
|
||||||
mSearchFeatureProvider = FeatureFactory.getFactory(context)
|
|
||||||
.getSearchFeatureProvider();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader onCreateLoader(int id, Bundle args) {
|
|
||||||
switch (id) {
|
|
||||||
case SearchFragment.SearchLoaderId.SAVE_QUERY_TASK:
|
|
||||||
return new SavedQueryRecorder(mContext, args.getString(ARG_QUERY));
|
|
||||||
case SearchFragment.SearchLoaderId.REMOVE_QUERY_TASK:
|
|
||||||
return new SavedQueryRemover(mContext);
|
|
||||||
case SearchFragment.SearchLoaderId.SAVED_QUERIES:
|
|
||||||
return mSearchFeatureProvider.getSavedQueryLoader(mContext);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader loader, Object data) {
|
|
||||||
switch (loader.getId()) {
|
|
||||||
case SearchFragment.SearchLoaderId.REMOVE_QUERY_TASK:
|
|
||||||
mLoaderManager.restartLoader(SearchFragment.SearchLoaderId.SAVED_QUERIES,
|
|
||||||
null /* args */, this /* callback */);
|
|
||||||
break;
|
|
||||||
case SearchFragment.SearchLoaderId.SAVED_QUERIES:
|
|
||||||
if (SettingsSearchIndexablesProvider.DEBUG) {
|
|
||||||
Log.d(TAG, "Saved queries loaded");
|
|
||||||
}
|
|
||||||
mResultAdapter.displaySavedQuery((List<SearchResult>) data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader loader) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
|
||||||
if (item.getItemId() != MENU_SEARCH_HISTORY) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
removeQueries();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void buildMenuItem(Menu menu) {
|
|
||||||
final MenuItem item =
|
|
||||||
menu.add(Menu.NONE, MENU_SEARCH_HISTORY, Menu.NONE, R.string.search_clear_history);
|
|
||||||
item.setOnMenuItemClickListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveQuery(String query) {
|
|
||||||
final Bundle args = new Bundle();
|
|
||||||
args.putString(ARG_QUERY, query);
|
|
||||||
mLoaderManager.restartLoader(SearchFragment.SearchLoaderId.SAVE_QUERY_TASK, args,
|
|
||||||
this /* callback */);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all saved queries from DB
|
|
||||||
*/
|
|
||||||
public void removeQueries() {
|
|
||||||
final Bundle args = new Bundle();
|
|
||||||
mLoaderManager.restartLoader(SearchFragment.SearchLoaderId.REMOVE_QUERY_TASK, args,
|
|
||||||
this /* callback */);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadSavedQueries() {
|
|
||||||
if (SettingsSearchIndexablesProvider.DEBUG) {
|
|
||||||
Log.d(TAG, "loading saved queries");
|
|
||||||
}
|
|
||||||
mLoaderManager.restartLoader(SearchFragment.SearchLoaderId.SAVED_QUERIES, null /* args */,
|
|
||||||
this /* callback */);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import com.android.settings.search.IndexDatabaseHelper.SavedQueriesColumns;
|
|
||||||
import com.android.settingslib.utils.AsyncLoader;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loader for recently searched queries.
|
|
||||||
*/
|
|
||||||
public class SavedQueryLoader extends AsyncLoader<List<? extends SearchResult>> {
|
|
||||||
|
|
||||||
// Max number of proposed suggestions
|
|
||||||
@VisibleForTesting
|
|
||||||
static final int MAX_PROPOSED_SUGGESTIONS = 5;
|
|
||||||
|
|
||||||
private final SQLiteDatabase mDatabase;
|
|
||||||
|
|
||||||
public SavedQueryLoader(Context context) {
|
|
||||||
super(context);
|
|
||||||
mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDiscardResult(List<? extends SearchResult> result) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<? extends SearchResult> loadInBackground() {
|
|
||||||
try (final Cursor cursor = mDatabase.query(
|
|
||||||
IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES /* table */,
|
|
||||||
new String[]{SavedQueriesColumns.QUERY} /* columns */,
|
|
||||||
null /* selection */,
|
|
||||||
null /* selectionArgs */,
|
|
||||||
null /* groupBy */,
|
|
||||||
null /* having */,
|
|
||||||
"rowId DESC" /* orderBy */,
|
|
||||||
String.valueOf(MAX_PROPOSED_SUGGESTIONS) /* limit */)) {
|
|
||||||
return convertCursorToResult(cursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SearchResult> convertCursorToResult(Cursor cursor) {
|
|
||||||
final List<SearchResult> results = new ArrayList<>();
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
final SavedQueryPayload payload = new SavedQueryPayload(
|
|
||||||
cursor.getString(cursor.getColumnIndex(SavedQueriesColumns.QUERY)));
|
|
||||||
results.add(new SearchResult.Builder()
|
|
||||||
.setStableId(payload.hashCode())
|
|
||||||
.setTitle(payload.query)
|
|
||||||
.setPayload(payload)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link ResultPayload} for saved query.
|
|
||||||
*/
|
|
||||||
public class SavedQueryPayload extends ResultPayload {
|
|
||||||
|
|
||||||
public final String query;
|
|
||||||
|
|
||||||
public SavedQueryPayload(String query) {
|
|
||||||
super(null /* Intent */);
|
|
||||||
this.query = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
||||||
SavedQueryPayload(Parcel in) {
|
|
||||||
super(null /* Intent */);
|
|
||||||
query = in.readString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getType() {
|
|
||||||
return PayloadType.SAVED_QUERY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeString(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Creator<SavedQueryPayload> CREATOR = new Creator<SavedQueryPayload>() {
|
|
||||||
@Override
|
|
||||||
public SavedQueryPayload createFromParcel(Parcel in) {
|
|
||||||
return new SavedQueryPayload(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SavedQueryPayload[] newArray(int size) {
|
|
||||||
return new SavedQueryPayload[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteException;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.settings.search.IndexDatabaseHelper;
|
|
||||||
import com.android.settingslib.utils.AsyncLoader;
|
|
||||||
|
|
||||||
import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A background task to update saved queries.
|
|
||||||
*/
|
|
||||||
public class SavedQueryRecorder extends AsyncLoader<Void> {
|
|
||||||
|
|
||||||
private static final String LOG_TAG = "SavedQueryRecorder";
|
|
||||||
|
|
||||||
// Max number of saved search queries (who will be used for proposing suggestions)
|
|
||||||
private static long MAX_SAVED_SEARCH_QUERY = 64;
|
|
||||||
|
|
||||||
private final String mQuery;
|
|
||||||
|
|
||||||
public SavedQueryRecorder(Context context, String query) {
|
|
||||||
super(context);
|
|
||||||
mQuery = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDiscardResult(Void result) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Void loadInBackground() {
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
|
|
||||||
final ContentValues values = new ContentValues();
|
|
||||||
values.put(IndexDatabaseHelper.SavedQueriesColumns.QUERY, mQuery);
|
|
||||||
values.put(IndexDatabaseHelper.SavedQueriesColumns.TIME_STAMP, now);
|
|
||||||
|
|
||||||
final SQLiteDatabase database = getWritableDatabase();
|
|
||||||
if (database == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
long lastInsertedRowId;
|
|
||||||
try {
|
|
||||||
// First, delete all saved queries that are the same
|
|
||||||
database.delete(TABLE_SAVED_QUERIES,
|
|
||||||
IndexDatabaseHelper.SavedQueriesColumns.QUERY + " = ?",
|
|
||||||
new String[]{mQuery});
|
|
||||||
|
|
||||||
// Second, insert the saved query
|
|
||||||
lastInsertedRowId = database.insertOrThrow(TABLE_SAVED_QUERIES, null, values);
|
|
||||||
|
|
||||||
// Last, remove "old" saved queries
|
|
||||||
final long delta = lastInsertedRowId - MAX_SAVED_SEARCH_QUERY;
|
|
||||||
if (delta > 0) {
|
|
||||||
int count = database.delete(TABLE_SAVED_QUERIES,
|
|
||||||
"rowId <= ?",
|
|
||||||
new String[]{Long.toString(delta)});
|
|
||||||
Log.d(LOG_TAG, "Deleted '" + count + "' saved Search query(ies)");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.d(LOG_TAG, "Cannot update saved Search queries", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SQLiteDatabase getWritableDatabase() {
|
|
||||||
try {
|
|
||||||
return IndexDatabaseHelper.getInstance(getContext()).getWritableDatabase();
|
|
||||||
} catch (SQLiteException e) {
|
|
||||||
Log.e(LOG_TAG, "Cannot open writable database", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteException;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.settingslib.utils.AsyncLoader;
|
|
||||||
|
|
||||||
public class SavedQueryRemover extends AsyncLoader<Void> {
|
|
||||||
|
|
||||||
private static final String LOG_TAG = "SavedQueryRemover";
|
|
||||||
|
|
||||||
public SavedQueryRemover(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Void loadInBackground() {
|
|
||||||
final SQLiteDatabase database = getWritableDatabase();
|
|
||||||
try {
|
|
||||||
// First, delete all saved queries that are the same
|
|
||||||
database.delete(TABLE_SAVED_QUERIES,
|
|
||||||
null /* where */,
|
|
||||||
null /* whereArgs */);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.d(LOG_TAG, "Cannot update saved Search queries", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDiscardResult(Void result) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private SQLiteDatabase getWritableDatabase() {
|
|
||||||
try {
|
|
||||||
return IndexDatabaseHelper.getInstance(getContext()).getWritableDatabase();
|
|
||||||
} catch (SQLiteException e) {
|
|
||||||
Log.e(LOG_TAG, "Cannot open writable database", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.android.internal.logging.nano.MetricsProto;
|
|
||||||
|
|
||||||
public class SavedQueryViewHolder extends SearchViewHolder {
|
|
||||||
|
|
||||||
public final TextView titleView;
|
|
||||||
|
|
||||||
public SavedQueryViewHolder(View view) {
|
|
||||||
super(view);
|
|
||||||
titleView = view.findViewById(android.R.id.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getClickActionMetricName() {
|
|
||||||
return MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_SAVED_QUERY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(SearchFragment fragment, SearchResult result) {
|
|
||||||
itemView.setOnClickListener(v -> fragment.onSavedQueryClicked(result.title));
|
|
||||||
titleView.setText(result.title);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Fragment;
|
|
||||||
import android.app.FragmentManager;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import com.android.settings.R;
|
|
||||||
|
|
||||||
public class SearchActivity extends Activity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.search_main);
|
|
||||||
// Keeps layouts in-place when keyboard opens.
|
|
||||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
|
|
||||||
|
|
||||||
FragmentManager fragmentManager = getFragmentManager();
|
|
||||||
Fragment fragment = fragmentManager.findFragmentById(R.id.main_content);
|
|
||||||
if (fragment == null) {
|
|
||||||
fragmentManager.beginTransaction()
|
|
||||||
.add(R.id.main_content, new SearchFragment())
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -21,12 +21,9 @@ import android.app.Activity;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.util.FeatureFlagUtils;
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import com.android.settings.core.FeatureFlags;
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
import com.android.settings.dashboard.SiteMapManager;
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
|
|
||||||
@@ -50,58 +47,18 @@ public interface SearchFeatureProvider {
|
|||||||
void verifyLaunchSearchResultPageCaller(Context context, @NonNull ComponentName caller)
|
void verifyLaunchSearchResultPageCaller(Context context, @NonNull ComponentName caller)
|
||||||
throws SecurityException, IllegalArgumentException;
|
throws SecurityException, IllegalArgumentException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new loader to get settings search results.
|
|
||||||
*/
|
|
||||||
SearchResultLoader getSearchResultLoader(Context context, String query);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new loader to search in index database.
|
|
||||||
*/
|
|
||||||
DatabaseResultLoader getStaticSearchResultTask(Context context, String query);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new loader to search installed apps.
|
|
||||||
*/
|
|
||||||
InstalledAppResultLoader getInstalledAppSearchTask(Context context, String query);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new loader to search accessibility services.
|
|
||||||
*/
|
|
||||||
AccessibilityServiceResultLoader getAccessibilityServiceResultTask(Context context,
|
|
||||||
String query);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new loader to search input devices.
|
|
||||||
*/
|
|
||||||
InputDeviceResultLoader getInputDeviceResultTask(Context context, String query);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new loader to get all recently saved queries search terms.
|
|
||||||
*/
|
|
||||||
SavedQueryLoader getSavedQueryLoader(Context context);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the manager for indexing Settings data.
|
|
||||||
*/
|
|
||||||
DatabaseIndexingManager getIndexingManager(Context context);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the manager for looking up breadcrumbs.
|
* Returns the manager for looking up breadcrumbs.
|
||||||
*/
|
*/
|
||||||
SiteMapManager getSiteMapManager();
|
SiteMapManager getSiteMapManager();
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the Settings indexes and calls {@link IndexingCallback#onIndexingFinished()} on
|
|
||||||
* {@param callback} when indexing is complete.
|
|
||||||
*/
|
|
||||||
void updateIndexAsync(Context context, IndexingCallback callback);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronously updates the Settings database.
|
* Synchronously updates the Settings database.
|
||||||
*/
|
*/
|
||||||
void updateIndex(Context context);
|
void updateIndex(Context context);
|
||||||
|
|
||||||
|
DatabaseIndexingManager getIndexingManager(Context context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns true when indexing is complete.
|
* @returns true when indexing is complete.
|
||||||
*/
|
*/
|
||||||
@@ -112,55 +69,6 @@ public interface SearchFeatureProvider {
|
|||||||
*/
|
*/
|
||||||
ExecutorService getExecutorService();
|
ExecutorService getExecutorService();
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the feedback button in case it was dismissed.
|
|
||||||
*/
|
|
||||||
default void initFeedbackButton() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a button users can click to submit feedback on the quality of the search results.
|
|
||||||
*/
|
|
||||||
default void showFeedbackButton(SearchFragment fragment, View view) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide the feedback button shown by
|
|
||||||
* {@link #showFeedbackButton(SearchFragment fragment, View view) showFeedbackButton}
|
|
||||||
*/
|
|
||||||
default void hideFeedbackButton() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify that a search result is clicked.
|
|
||||||
*
|
|
||||||
* @param context application context
|
|
||||||
* @param query input user query
|
|
||||||
* @param searchResult clicked result
|
|
||||||
*/
|
|
||||||
default void searchResultClicked(Context context, String query, SearchResult searchResult) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true to enable search ranking.
|
|
||||||
*/
|
|
||||||
default boolean isSmartSearchRankingEnabled(Context context) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return smart ranking timeout in milliseconds.
|
|
||||||
*/
|
|
||||||
default long smartSearchRankingTimeoutMs(Context context) {
|
|
||||||
return 300L;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare for search ranking predictions to avoid latency on the first prediction call.
|
|
||||||
*/
|
|
||||||
default void searchRankingWarmup(Context context) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a FutureTask to get a list of scores for search results.
|
* Return a FutureTask to get a list of scores for search results.
|
||||||
*/
|
*/
|
||||||
@@ -168,10 +76,6 @@ public interface SearchFeatureProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean isSearchV2Enabled(Context context) {
|
|
||||||
return FeatureFlagUtils.isEnabled(context, FeatureFlags.SEARCH_V2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the search toolbar.
|
* Initializes the search toolbar.
|
||||||
*/
|
*/
|
||||||
@@ -180,12 +84,8 @@ public interface SearchFeatureProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toolbar.setOnClickListener(tb -> {
|
toolbar.setOnClickListener(tb -> {
|
||||||
final Intent intent;
|
final Intent intent = SEARCH_UI_INTENT;
|
||||||
if (isSearchV2Enabled(activity)) {
|
|
||||||
intent = SEARCH_UI_INTENT;
|
|
||||||
} else {
|
|
||||||
intent = new Intent(activity, SearchActivity.class);
|
|
||||||
}
|
|
||||||
FeatureFactory.getFactory(
|
FeatureFactory.getFactory(
|
||||||
activity.getApplicationContext()).getSlicesFeatureProvider()
|
activity.getApplicationContext()).getSlicesFeatureProvider()
|
||||||
.indexSliceDataAsync(activity.getApplicationContext());
|
.indexSliceDataAsync(activity.getApplicationContext());
|
||||||
|
@@ -20,13 +20,11 @@ package com.android.settings.search;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
import com.android.settings.dashboard.SiteMapManager;
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
import com.android.settings.search.indexing.IndexData;
|
import com.android.settings.search.indexing.IndexData;
|
||||||
import com.android.settingslib.wrapper.PackageManagerWrapper;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -62,40 +60,6 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
|
|||||||
+ "whitelisted package.");
|
+ "whitelisted package.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public SearchResultLoader getSearchResultLoader(Context context, String query) {
|
|
||||||
return new SearchResultLoader(context, cleanQuery(query));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DatabaseResultLoader getStaticSearchResultTask(Context context, String query) {
|
|
||||||
return new DatabaseResultLoader(context, cleanQuery(query), getSiteMapManager());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InstalledAppResultLoader getInstalledAppSearchTask(Context context, String query) {
|
|
||||||
return new InstalledAppResultLoader(
|
|
||||||
context, new PackageManagerWrapper(context.getPackageManager()),
|
|
||||||
cleanQuery(query), getSiteMapManager());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AccessibilityServiceResultLoader getAccessibilityServiceResultTask(Context context,
|
|
||||||
String query) {
|
|
||||||
return new AccessibilityServiceResultLoader(context, cleanQuery(query),
|
|
||||||
getSiteMapManager());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputDeviceResultLoader getInputDeviceResultTask(Context context, String query) {
|
|
||||||
return new InputDeviceResultLoader(context, cleanQuery(query), getSiteMapManager());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SavedQueryLoader getSavedQueryLoader(Context context) {
|
|
||||||
return new SavedQueryLoader(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DatabaseIndexingManager getIndexingManager(Context context) {
|
public DatabaseIndexingManager getIndexingManager(Context context) {
|
||||||
if (mDatabaseIndexingManager == null) {
|
if (mDatabaseIndexingManager == null) {
|
||||||
@@ -116,14 +80,6 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
|
|||||||
return mSiteMapManager;
|
return mSiteMapManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateIndexAsync(Context context, IndexingCallback callback) {
|
|
||||||
if (SettingsSearchIndexablesProvider.DEBUG) {
|
|
||||||
Log.d(TAG, "updating index async");
|
|
||||||
}
|
|
||||||
getIndexingManager(context).indexDatabase(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateIndex(Context context) {
|
public void updateIndex(Context context) {
|
||||||
long indexStartTime = System.currentTimeMillis();
|
long indexStartTime = System.currentTimeMillis();
|
||||||
|
@@ -1,433 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.LoaderManager;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.Loader;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.EventLog;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.Pair;
|
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.SearchView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toolbar;
|
|
||||||
|
|
||||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
|
||||||
import com.android.settings.EventLogTags;
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.SettingsActivity;
|
|
||||||
import com.android.settings.Utils;
|
|
||||||
import com.android.settings.core.InstrumentedFragment;
|
|
||||||
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
|
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
|
||||||
import com.android.settings.widget.ActionBarShadowController;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This fragment manages the lifecycle of indexing and searching.
|
|
||||||
*
|
|
||||||
* In onCreate, the indexing process is initiated in DatabaseIndexingManager.
|
|
||||||
* While the indexing is happening, loaders are blocked from accessing the database, but the user
|
|
||||||
* is free to start typing their query.
|
|
||||||
*
|
|
||||||
* When the indexing is complete, the fragment gets a callback to initialize the loaders and search
|
|
||||||
* the query if the user has entered text.
|
|
||||||
*/
|
|
||||||
public class SearchFragment extends InstrumentedFragment implements SearchView.OnQueryTextListener,
|
|
||||||
LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {
|
|
||||||
private static final String TAG = "SearchFragment";
|
|
||||||
|
|
||||||
// State values
|
|
||||||
private static final String STATE_QUERY = "state_query";
|
|
||||||
private static final String STATE_SHOWING_SAVED_QUERY = "state_showing_saved_query";
|
|
||||||
private static final String STATE_NEVER_ENTERED_QUERY = "state_never_entered_query";
|
|
||||||
|
|
||||||
static final class SearchLoaderId {
|
|
||||||
// Search Query IDs
|
|
||||||
public static final int SEARCH_RESULT = 1;
|
|
||||||
|
|
||||||
// Saved Query IDs
|
|
||||||
public static final int SAVE_QUERY_TASK = 2;
|
|
||||||
public static final int REMOVE_QUERY_TASK = 3;
|
|
||||||
public static final int SAVED_QUERIES = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
String mQuery;
|
|
||||||
|
|
||||||
private boolean mNeverEnteredQuery = true;
|
|
||||||
@VisibleForTesting
|
|
||||||
boolean mShowingSavedQuery;
|
|
||||||
private MetricsFeatureProvider mMetricsFeatureProvider;
|
|
||||||
@VisibleForTesting
|
|
||||||
SavedQueryController mSavedQueryController;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
SearchFeatureProvider mSearchFeatureProvider;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
SearchResultsAdapter mSearchAdapter;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
RecyclerView mResultsRecyclerView;
|
|
||||||
@VisibleForTesting
|
|
||||||
SearchView mSearchView;
|
|
||||||
@VisibleForTesting
|
|
||||||
LinearLayout mNoResultsView;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
final RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
|
|
||||||
@Override
|
|
||||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
|
||||||
if (dy != 0) {
|
|
||||||
hideKeyboard();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMetricsCategory() {
|
|
||||||
return MetricsEvent.DASHBOARD_SEARCH_RESULTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
mSearchFeatureProvider = FeatureFactory.getFactory(context).getSearchFeatureProvider();
|
|
||||||
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
|
|
||||||
final LoaderManager loaderManager = getLoaderManager();
|
|
||||||
mSearchAdapter = new SearchResultsAdapter(this /* fragment */);
|
|
||||||
mSavedQueryController = new SavedQueryController(
|
|
||||||
getContext(), loaderManager, mSearchAdapter);
|
|
||||||
mSearchFeatureProvider.initFeedbackButton();
|
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
mQuery = savedInstanceState.getString(STATE_QUERY);
|
|
||||||
mNeverEnteredQuery = savedInstanceState.getBoolean(STATE_NEVER_ENTERED_QUERY);
|
|
||||||
mShowingSavedQuery = savedInstanceState.getBoolean(STATE_SHOWING_SAVED_QUERY);
|
|
||||||
} else {
|
|
||||||
mShowingSavedQuery = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
// Run the Index update only if we have some space
|
|
||||||
if (!Utils.isLowStorage(activity)) {
|
|
||||||
mSearchFeatureProvider.updateIndexAsync(activity, this /* indexingCallback */);
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Cannot update the Indexer as we are running low on storage space!");
|
|
||||||
}
|
|
||||||
if (SettingsSearchIndexablesProvider.DEBUG) {
|
|
||||||
Log.d(TAG, "onCreate spent " + (System.currentTimeMillis() - startTime) + " ms");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
mSavedQueryController.buildMenuItem(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
final View view = inflater.inflate(R.layout.search_panel, container, false);
|
|
||||||
mResultsRecyclerView = view.findViewById(R.id.list_results);
|
|
||||||
mResultsRecyclerView.setAdapter(mSearchAdapter);
|
|
||||||
mResultsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
|
||||||
mResultsRecyclerView.addOnScrollListener(mScrollListener);
|
|
||||||
|
|
||||||
mNoResultsView = view.findViewById(R.id.no_results_layout);
|
|
||||||
|
|
||||||
Toolbar toolbar = view.findViewById(R.id.search_toolbar);
|
|
||||||
getActivity().setActionBar(toolbar);
|
|
||||||
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
mSearchView = toolbar.findViewById(R.id.search_view);
|
|
||||||
mSearchView.setQuery(mQuery, false /* submitQuery */);
|
|
||||||
mSearchView.setOnQueryTextListener(this);
|
|
||||||
mSearchView.requestFocus();
|
|
||||||
|
|
||||||
// Updating internal views inside SearchView was the easiest way to get this too look right.
|
|
||||||
// Instead of grabbing the TextView directly, we grab it as a view and do an instanceof
|
|
||||||
// check. This ensures if we return, say, a LinearLayout in the tests, they won't fail.
|
|
||||||
View searchText = mSearchView.findViewById(com.android.internal.R.id.search_src_text);
|
|
||||||
if (searchText instanceof TextView) {
|
|
||||||
TextView searchTextView = (TextView) searchText;
|
|
||||||
searchTextView.setTextColor(getContext().getColorStateList(
|
|
||||||
com.android.internal.R.color.text_color_primary));
|
|
||||||
searchTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
|
|
||||||
getResources().getDimension(R.dimen.search_bar_text_size));
|
|
||||||
|
|
||||||
}
|
|
||||||
View editFrame = mSearchView.findViewById(com.android.internal.R.id.search_edit_frame);
|
|
||||||
if (editFrame != null) {
|
|
||||||
ViewGroup.MarginLayoutParams params =
|
|
||||||
(ViewGroup.MarginLayoutParams) editFrame.getLayoutParams();
|
|
||||||
params.setMarginStart(0);
|
|
||||||
editFrame.setLayoutParams(params);
|
|
||||||
}
|
|
||||||
ActionBarShadowController.attachToRecyclerView(
|
|
||||||
view.findViewById(R.id.search_bar_container), getLifecycle(), mResultsRecyclerView);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
Context appContext = getContext().getApplicationContext();
|
|
||||||
if (mSearchFeatureProvider.isSmartSearchRankingEnabled(appContext)) {
|
|
||||||
mSearchFeatureProvider.searchRankingWarmup(appContext);
|
|
||||||
}
|
|
||||||
requery();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
if (activity != null && activity.isFinishing()) {
|
|
||||||
if (mNeverEnteredQuery) {
|
|
||||||
mMetricsFeatureProvider.action(activity,
|
|
||||||
MetricsEvent.ACTION_LEAVE_SEARCH_RESULT_WITHOUT_QUERY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putString(STATE_QUERY, mQuery);
|
|
||||||
outState.putBoolean(STATE_NEVER_ENTERED_QUERY, mNeverEnteredQuery);
|
|
||||||
outState.putBoolean(STATE_SHOWING_SAVED_QUERY, mShowingSavedQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextChange(String query) {
|
|
||||||
if (TextUtils.equals(query, mQuery)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean isEmptyQuery = TextUtils.isEmpty(query);
|
|
||||||
|
|
||||||
// Hide no-results-view when the new query is not a super-string of the previous
|
|
||||||
if (mQuery != null
|
|
||||||
&& mNoResultsView.getVisibility() == View.VISIBLE
|
|
||||||
&& query.length() < mQuery.length()) {
|
|
||||||
mNoResultsView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
mNeverEnteredQuery = false;
|
|
||||||
mQuery = query;
|
|
||||||
|
|
||||||
// If indexing is not finished, register the query text, but don't search.
|
|
||||||
if (!mSearchFeatureProvider.isIndexingComplete(getActivity())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEmptyQuery) {
|
|
||||||
final LoaderManager loaderManager = getLoaderManager();
|
|
||||||
loaderManager.destroyLoader(SearchLoaderId.SEARCH_RESULT);
|
|
||||||
mShowingSavedQuery = true;
|
|
||||||
mSavedQueryController.loadSavedQueries();
|
|
||||||
mSearchFeatureProvider.hideFeedbackButton();
|
|
||||||
} else {
|
|
||||||
restartLoaders();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextSubmit(String query) {
|
|
||||||
// Save submitted query.
|
|
||||||
mSavedQueryController.saveQuery(mQuery);
|
|
||||||
hideKeyboard();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
|
|
||||||
switch(id) {
|
|
||||||
case SearchLoaderId.SEARCH_RESULT:
|
|
||||||
return mSearchFeatureProvider.getSearchResultLoader(activity, mQuery);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<List<? extends SearchResult>> loader,
|
|
||||||
List<? extends SearchResult> data) {
|
|
||||||
mSearchAdapter.postSearchResults(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<List<? extends SearchResult>> loader) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets called when Indexing is completed.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onIndexingFinished() {
|
|
||||||
if (getActivity() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mShowingSavedQuery) {
|
|
||||||
mSavedQueryController.loadSavedQueries();
|
|
||||||
} else {
|
|
||||||
final LoaderManager loaderManager = getLoaderManager();
|
|
||||||
loaderManager.initLoader(SearchLoaderId.SEARCH_RESULT, null /* args */,
|
|
||||||
this /* callback */);
|
|
||||||
}
|
|
||||||
|
|
||||||
requery();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onSearchResultClicked(SearchViewHolder resultViewHolder, SearchResult result,
|
|
||||||
Pair<Integer, Object>... logTaggedData) {
|
|
||||||
logSearchResultClicked(resultViewHolder, result, logTaggedData);
|
|
||||||
mSearchFeatureProvider.searchResultClicked(getContext(), mQuery, result);
|
|
||||||
mSavedQueryController.saveQuery(mQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onSearchResultsDisplayed(int resultCount) {
|
|
||||||
if (resultCount == 0) {
|
|
||||||
mNoResultsView.setVisibility(View.VISIBLE);
|
|
||||||
mMetricsFeatureProvider.visible(getContext(), getMetricsCategory(),
|
|
||||||
MetricsEvent.SETTINGS_SEARCH_NO_RESULT);
|
|
||||||
// Log settings_latency for search end-to-end.
|
|
||||||
EventLog.writeEvent(EventLogTags.SETTINGS_LATENCY, 1, 10);
|
|
||||||
} else {
|
|
||||||
mNoResultsView.setVisibility(View.GONE);
|
|
||||||
mResultsRecyclerView.scrollToPosition(0);
|
|
||||||
}
|
|
||||||
mMetricsFeatureProvider.action(
|
|
||||||
getVisibilityLogger(), MetricsEvent.ACTION_SEARCH_RESULTS, 1);
|
|
||||||
mSearchFeatureProvider.showFeedbackButton(this, getView());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onSavedQueryClicked(CharSequence query) {
|
|
||||||
final String queryString = query.toString();
|
|
||||||
mMetricsFeatureProvider.action(getContext(),
|
|
||||||
MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_SAVED_QUERY);
|
|
||||||
mSearchView.setQuery(queryString, false /* submit */);
|
|
||||||
onQueryTextChange(queryString);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restartLoaders() {
|
|
||||||
mShowingSavedQuery = false;
|
|
||||||
final LoaderManager loaderManager = getLoaderManager();
|
|
||||||
loaderManager.restartLoader(
|
|
||||||
SearchLoaderId.SEARCH_RESULT, null /* args */, this /* callback */);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQuery() {
|
|
||||||
return mQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SearchResult> getSearchResults() {
|
|
||||||
return mSearchAdapter.getSearchResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requery() {
|
|
||||||
if (TextUtils.isEmpty(mQuery)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final String query = mQuery;
|
|
||||||
mQuery = "";
|
|
||||||
onQueryTextChange(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hideKeyboard() {
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
if (activity != null) {
|
|
||||||
View view = activity.getCurrentFocus();
|
|
||||||
InputMethodManager imm = (InputMethodManager)
|
|
||||||
activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mResultsRecyclerView != null) {
|
|
||||||
mResultsRecyclerView.requestFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logSearchResultClicked(SearchViewHolder resultViewHolder, SearchResult result,
|
|
||||||
Pair<Integer, Object>... logTaggedData) {
|
|
||||||
final Intent intent = result.payload.getIntent();
|
|
||||||
if (intent == null) {
|
|
||||||
Log.w(TAG, "Skipped logging click on search result because of null intent, which can " +
|
|
||||||
"happen on saved query results.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final ComponentName cn = intent.getComponent();
|
|
||||||
String resultName = intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT);
|
|
||||||
if (TextUtils.isEmpty(resultName) && cn != null) {
|
|
||||||
resultName = cn.flattenToString();
|
|
||||||
}
|
|
||||||
final List<Pair<Integer, Object>> taggedData = new ArrayList<>();
|
|
||||||
if (logTaggedData != null) {
|
|
||||||
taggedData.addAll(Arrays.asList(logTaggedData));
|
|
||||||
}
|
|
||||||
taggedData.add(Pair.create(
|
|
||||||
MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_COUNT,
|
|
||||||
mSearchAdapter.getItemCount()));
|
|
||||||
taggedData.add(Pair.create(
|
|
||||||
MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_RANK,
|
|
||||||
resultViewHolder.getAdapterPosition()));
|
|
||||||
// TODO (b/67744820) Move metrics to SettingsIntelligence (including ranking state).
|
|
||||||
taggedData.add(Pair.create(
|
|
||||||
MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH,
|
|
||||||
TextUtils.isEmpty(mQuery) ? 0 : mQuery.length()));
|
|
||||||
|
|
||||||
mMetricsFeatureProvider.action(getContext(),
|
|
||||||
resultViewHolder.getClickActionMetricName(),
|
|
||||||
resultName,
|
|
||||||
taggedData.toArray(new Pair[0]));
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,177 +0,0 @@
|
|||||||
package com.android.settings.search;
|
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.SparseArray;
|
|
||||||
|
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collects the sorted list of all setting search results.
|
|
||||||
*
|
|
||||||
* TODO (b/64939692) Convert the timing logs to metrics
|
|
||||||
*/
|
|
||||||
public class SearchResultAggregator {
|
|
||||||
|
|
||||||
private static final String TAG = "SearchResultAggregator";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timeout for first task. Allows for longer delay.
|
|
||||||
*/
|
|
||||||
private static final long LONG_CHECK_TASK_TIMEOUT_MS = 500;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timeout for subsequent tasks to allow for fast returning tasks.
|
|
||||||
*/
|
|
||||||
private static final long SHORT_CHECK_TASK_TIMEOUT_MS = 150;
|
|
||||||
|
|
||||||
private static SearchResultAggregator sResultAggregator;
|
|
||||||
|
|
||||||
// TODO (b/33577327) Merge the other loaders into a single dynamic loader
|
|
||||||
static final class ResultLoaderId {
|
|
||||||
static final int STATIC_RESULTS = 1;
|
|
||||||
static final int INSTALLED_RESULTS = 2;
|
|
||||||
static final int INPUT_RESULTS = 3;
|
|
||||||
static final int ACCESSIBILITY_RESULTS = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SearchResultAggregator() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SearchResultAggregator getInstance() {
|
|
||||||
if (sResultAggregator == null) {
|
|
||||||
sResultAggregator = new SearchResultAggregator();
|
|
||||||
}
|
|
||||||
|
|
||||||
return sResultAggregator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public synchronized List<? extends SearchResult> fetchResults(Context context, String query) {
|
|
||||||
SearchFeatureProvider mFeatureProvider = FeatureFactory.getFactory(
|
|
||||||
context).getSearchFeatureProvider();
|
|
||||||
ExecutorService executorService = mFeatureProvider.getExecutorService();
|
|
||||||
|
|
||||||
final DatabaseResultLoader staticResultsTask =
|
|
||||||
mFeatureProvider.getStaticSearchResultTask(context, query);
|
|
||||||
final InstalledAppResultLoader installedAppTask =
|
|
||||||
mFeatureProvider.getInstalledAppSearchTask(context, query);
|
|
||||||
final InputDeviceResultLoader inputDevicesTask =
|
|
||||||
mFeatureProvider.getInputDeviceResultTask(context, query);
|
|
||||||
final AccessibilityServiceResultLoader accessibilityServicesTask =
|
|
||||||
mFeatureProvider.getAccessibilityServiceResultTask(context,
|
|
||||||
query);
|
|
||||||
|
|
||||||
executorService.execute(staticResultsTask);
|
|
||||||
executorService.execute(installedAppTask);
|
|
||||||
executorService.execute(inputDevicesTask);
|
|
||||||
executorService.execute(accessibilityServicesTask);
|
|
||||||
|
|
||||||
SparseArray<List<? extends SearchResult>> resultsArray = new SparseArray<>();
|
|
||||||
List<? extends SearchResult> EMPTY_LIST = new ArrayList<>();
|
|
||||||
|
|
||||||
long allTasksStart = System.currentTimeMillis();
|
|
||||||
try {
|
|
||||||
resultsArray.put(ResultLoaderId.INPUT_RESULTS,
|
|
||||||
inputDevicesTask.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
|
||||||
} catch (TimeoutException | InterruptedException | ExecutionException e) {
|
|
||||||
Log.d(TAG, "Could not retrieve input devices results in time: " + e);
|
|
||||||
resultsArray.put(ResultLoaderId.INPUT_RESULTS, EMPTY_LIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
resultsArray.put(ResultLoaderId.ACCESSIBILITY_RESULTS,
|
|
||||||
accessibilityServicesTask.get(SHORT_CHECK_TASK_TIMEOUT_MS,
|
|
||||||
TimeUnit.MILLISECONDS));
|
|
||||||
} catch (TimeoutException | InterruptedException | ExecutionException e) {
|
|
||||||
Log.d(TAG, "Could not retrieve accessibility results in time: " + e);
|
|
||||||
resultsArray.put(ResultLoaderId.ACCESSIBILITY_RESULTS, EMPTY_LIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
resultsArray.put(ResultLoaderId.STATIC_RESULTS,
|
|
||||||
staticResultsTask.get(LONG_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
|
||||||
} catch (TimeoutException | InterruptedException | ExecutionException e) {
|
|
||||||
Log.d(TAG, "Could not retrieve static results: " + e);
|
|
||||||
resultsArray.put(ResultLoaderId.STATIC_RESULTS, EMPTY_LIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
resultsArray.put(ResultLoaderId.INSTALLED_RESULTS,
|
|
||||||
installedAppTask.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
|
||||||
} catch (TimeoutException | InterruptedException | ExecutionException e) {
|
|
||||||
Log.d(TAG, "Could not retrieve installed app results in time: " + e);
|
|
||||||
|
|
||||||
resultsArray.put(ResultLoaderId.INSTALLED_RESULTS, EMPTY_LIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
long mergeStartTime = System.currentTimeMillis();
|
|
||||||
Log.i(TAG, "Total result loader time: " + (mergeStartTime - allTasksStart));
|
|
||||||
List<? extends SearchResult> mergedResults = mergeSearchResults(resultsArray);
|
|
||||||
Log.i(TAG, "Total merge time: " + (System.currentTimeMillis() - mergeStartTime));
|
|
||||||
Log.i(TAG, "Total aggregator time: " + (System.currentTimeMillis() - allTasksStart));
|
|
||||||
|
|
||||||
return mergedResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO (b/68255021) scale the dynamic search results ranks and do a k-way merge
|
|
||||||
private List<? extends SearchResult> mergeSearchResults(
|
|
||||||
SparseArray<List<? extends SearchResult>> resultsArray) {
|
|
||||||
List<? extends SearchResult> staticResults = resultsArray.get(
|
|
||||||
ResultLoaderId.STATIC_RESULTS);
|
|
||||||
List<? extends SearchResult> installedAppResults = resultsArray.get(
|
|
||||||
ResultLoaderId.INSTALLED_RESULTS);
|
|
||||||
List<? extends SearchResult> accessibilityResults = resultsArray.get(
|
|
||||||
ResultLoaderId.ACCESSIBILITY_RESULTS);
|
|
||||||
List<? extends SearchResult> inputDeviceResults = resultsArray.get(
|
|
||||||
ResultLoaderId.INPUT_RESULTS);
|
|
||||||
List<SearchResult> searchResults;
|
|
||||||
|
|
||||||
int staticSize = staticResults.size();
|
|
||||||
int appSize = installedAppResults.size();
|
|
||||||
int a11ySize = accessibilityResults.size();
|
|
||||||
int inputDeviceSize = inputDeviceResults.size();
|
|
||||||
int appIndex = 0;
|
|
||||||
int a11yIndex = 0;
|
|
||||||
int inputDeviceIndex = 0;
|
|
||||||
int rank = SearchResult.TOP_RANK;
|
|
||||||
|
|
||||||
// TODO: We need a helper method to do k-way merge.
|
|
||||||
searchResults = new ArrayList<>(staticSize + appSize + a11ySize + inputDeviceSize);
|
|
||||||
searchResults.addAll(resultsArray.get(ResultLoaderId.STATIC_RESULTS));
|
|
||||||
|
|
||||||
while (rank <= SearchResult.BOTTOM_RANK) {
|
|
||||||
while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) {
|
|
||||||
searchResults.add(installedAppResults.get(appIndex++));
|
|
||||||
}
|
|
||||||
while ((a11yIndex < a11ySize) && (accessibilityResults.get(a11yIndex).rank == rank)) {
|
|
||||||
searchResults.add(accessibilityResults.get(a11yIndex++));
|
|
||||||
}
|
|
||||||
while (inputDeviceIndex < inputDeviceSize
|
|
||||||
&& inputDeviceResults.get(inputDeviceIndex).rank == rank) {
|
|
||||||
searchResults.add(inputDeviceResults.get(inputDeviceIndex++));
|
|
||||||
}
|
|
||||||
rank++;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (appIndex < appSize) {
|
|
||||||
searchResults.add(installedAppResults.get(appIndex++));
|
|
||||||
}
|
|
||||||
while (a11yIndex < a11ySize) {
|
|
||||||
searchResults.add(accessibilityResults.get(a11yIndex++));
|
|
||||||
}
|
|
||||||
while (inputDeviceIndex < inputDeviceSize) {
|
|
||||||
searchResults.add(inputDeviceResults.get(inputDeviceIndex++));
|
|
||||||
}
|
|
||||||
|
|
||||||
return searchResults;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.support.v7.util.DiffUtil;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for DiffUtil to elegantly update search data when the query changes.
|
|
||||||
*/
|
|
||||||
public class SearchResultDiffCallback extends DiffUtil.Callback {
|
|
||||||
|
|
||||||
private List<? extends SearchResult> mOldList;
|
|
||||||
private List<? extends SearchResult> mNewList;
|
|
||||||
|
|
||||||
public SearchResultDiffCallback(List<? extends SearchResult> oldList,
|
|
||||||
List<? extends SearchResult> newList) {
|
|
||||||
mOldList = oldList;
|
|
||||||
mNewList = newList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOldListSize() {
|
|
||||||
return mOldList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getNewListSize() {
|
|
||||||
return mNewList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
|
||||||
return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
|
||||||
return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition));
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,30 +0,0 @@
|
|||||||
package com.android.settings.search;
|
|
||||||
|
|
||||||
import com.android.settingslib.utils.AsyncLoader;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads a sorted list of Search results for a given query.
|
|
||||||
*/
|
|
||||||
public class SearchResultLoader extends AsyncLoader<List<? extends SearchResult>> {
|
|
||||||
|
|
||||||
private final String mQuery;
|
|
||||||
|
|
||||||
public SearchResultLoader(Context context, String query) {
|
|
||||||
super(context);
|
|
||||||
mQuery = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<? extends SearchResult> loadInBackground() {
|
|
||||||
SearchResultAggregator aggregator = SearchResultAggregator.getInstance();
|
|
||||||
return aggregator.fetchResults(getContext(), mQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDiscardResult(List<? extends SearchResult> result) {
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,116 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.v7.util.DiffUtil;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder> {
|
|
||||||
|
|
||||||
private final SearchFragment mFragment;
|
|
||||||
private final List<SearchResult> mSearchResults;
|
|
||||||
|
|
||||||
public SearchResultsAdapter(SearchFragment fragment) {
|
|
||||||
mFragment = fragment;
|
|
||||||
mSearchResults = new ArrayList<>();
|
|
||||||
|
|
||||||
setHasStableIds(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
final Context context = parent.getContext();
|
|
||||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
|
||||||
final View view;
|
|
||||||
switch (viewType) {
|
|
||||||
case ResultPayload.PayloadType.INTENT:
|
|
||||||
view = inflater.inflate(R.layout.search_intent_item, parent, false);
|
|
||||||
return new IntentSearchViewHolder(view);
|
|
||||||
case ResultPayload.PayloadType.INLINE_SWITCH:
|
|
||||||
// TODO (b/62807132) replace layout InlineSwitchViewHolder and return an
|
|
||||||
// InlineSwitchViewHolder.
|
|
||||||
view = inflater.inflate(R.layout.search_intent_item, parent, false);
|
|
||||||
return new IntentSearchViewHolder(view);
|
|
||||||
case ResultPayload.PayloadType.INLINE_LIST:
|
|
||||||
// TODO (b/62807132) build a inline-list view holder & layout.
|
|
||||||
view = inflater.inflate(R.layout.search_intent_item, parent, false);
|
|
||||||
return new IntentSearchViewHolder(view);
|
|
||||||
case ResultPayload.PayloadType.SAVED_QUERY:
|
|
||||||
view = inflater.inflate(R.layout.search_saved_query_item, parent, false);
|
|
||||||
return new SavedQueryViewHolder(view);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(SearchViewHolder holder, int position) {
|
|
||||||
holder.onBind(mFragment, mSearchResults.get(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
return mSearchResults.get(position).stableId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position) {
|
|
||||||
return mSearchResults.get(position).viewType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return mSearchResults.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays recent searched queries.
|
|
||||||
*/
|
|
||||||
public void displaySavedQuery(List<? extends SearchResult> data) {
|
|
||||||
clearResults();
|
|
||||||
mSearchResults.addAll(data);
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearResults() {
|
|
||||||
mSearchResults.clear();
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SearchResult> getSearchResults() {
|
|
||||||
return mSearchResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void postSearchResults(List<? extends SearchResult> newSearchResults) {
|
|
||||||
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
|
|
||||||
new SearchResultDiffCallback(mSearchResults, newSearchResults));
|
|
||||||
mSearchResults.clear();
|
|
||||||
mSearchResults.addAll(newSearchResults);
|
|
||||||
diffResult.dispatchUpdatesTo(this);
|
|
||||||
mFragment.onSearchResultsDisplayed(mSearchResults.size());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.IconDrawableFactory;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
|
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {
|
|
||||||
|
|
||||||
private final String DYNAMIC_PLACEHOLDER = "%s";
|
|
||||||
|
|
||||||
private final String mPlaceholderSummary;
|
|
||||||
|
|
||||||
public final TextView titleView;
|
|
||||||
public final TextView summaryView;
|
|
||||||
public final TextView breadcrumbView;
|
|
||||||
public final ImageView iconView;
|
|
||||||
|
|
||||||
protected final MetricsFeatureProvider mMetricsFeatureProvider;
|
|
||||||
protected final SearchFeatureProvider mSearchFeatureProvider;
|
|
||||||
private final IconDrawableFactory mIconDrawableFactory;
|
|
||||||
|
|
||||||
public SearchViewHolder(View view) {
|
|
||||||
super(view);
|
|
||||||
final FeatureFactory featureFactory = FeatureFactory
|
|
||||||
.getFactory(view.getContext().getApplicationContext());
|
|
||||||
mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider();
|
|
||||||
mSearchFeatureProvider = featureFactory.getSearchFeatureProvider();
|
|
||||||
titleView = view.findViewById(android.R.id.title);
|
|
||||||
summaryView = view.findViewById(android.R.id.summary);
|
|
||||||
iconView = view.findViewById(android.R.id.icon);
|
|
||||||
breadcrumbView = view.findViewById(R.id.breadcrumb);
|
|
||||||
|
|
||||||
mPlaceholderSummary = view.getContext().getString(R.string.summary_placeholder);
|
|
||||||
mIconDrawableFactory = IconDrawableFactory.newInstance(view.getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract int getClickActionMetricName();
|
|
||||||
|
|
||||||
public void onBind(SearchFragment fragment, SearchResult result) {
|
|
||||||
titleView.setText(result.title);
|
|
||||||
// TODO (b/36101902) remove check for DYNAMIC_PLACEHOLDER
|
|
||||||
if (TextUtils.isEmpty(result.summary)
|
|
||||||
|| TextUtils.equals(result.summary, mPlaceholderSummary)
|
|
||||||
|| TextUtils.equals(result.summary, DYNAMIC_PLACEHOLDER)) {
|
|
||||||
summaryView.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
summaryView.setText(result.summary);
|
|
||||||
summaryView.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result instanceof AppSearchResult) {
|
|
||||||
AppSearchResult appResult = (AppSearchResult) result;
|
|
||||||
PackageManager pm = fragment.getActivity().getPackageManager();
|
|
||||||
UserHandle userHandle = appResult.getAppUserHandle();
|
|
||||||
Drawable badgedIcon = getBadgedIcon(appResult.info, userHandle.getIdentifier());
|
|
||||||
iconView.setImageDrawable(badgedIcon);
|
|
||||||
titleView.setContentDescription(
|
|
||||||
pm.getUserBadgedLabel(appResult.info.loadLabel(pm), userHandle));
|
|
||||||
} else {
|
|
||||||
// Valid even when result.icon is null.
|
|
||||||
iconView.setImageDrawable(result.icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
bindBreadcrumbView(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bindBreadcrumbView(SearchResult result) {
|
|
||||||
if (result.breadcrumbs == null || result.breadcrumbs.isEmpty()) {
|
|
||||||
breadcrumbView.setVisibility(View.GONE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final Context context = breadcrumbView.getContext();
|
|
||||||
String breadcrumb = result.breadcrumbs.get(0);
|
|
||||||
final int count = result.breadcrumbs.size();
|
|
||||||
for (int i = 1; i < count; i++) {
|
|
||||||
breadcrumb = context.getString(R.string.search_breadcrumb_connector,
|
|
||||||
breadcrumb, result.breadcrumbs.get(i));
|
|
||||||
}
|
|
||||||
breadcrumbView.setText(breadcrumb);
|
|
||||||
breadcrumbView.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
Drawable getBadgedIcon(ApplicationInfo info, int userId) {
|
|
||||||
return mIconDrawableFactory.getBadgedIcon(info, userId);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -18,14 +18,12 @@ package com.android.settings.search.actionbar;
|
|||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
|
||||||
import com.android.settings.search.SearchFeatureProvider;
|
import com.android.settings.search.SearchFeatureProvider;
|
||||||
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
||||||
import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
|
import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
|
||||||
@@ -38,14 +36,7 @@ public class SearchMenuController implements LifecycleObserver, OnCreateOptionsM
|
|||||||
private final Fragment mHost;
|
private final Fragment mHost;
|
||||||
|
|
||||||
public static void init(@NonNull ObservablePreferenceFragment host) {
|
public static void init(@NonNull ObservablePreferenceFragment host) {
|
||||||
final Context context = host.getContext();
|
host.getLifecycle().addObserver(new SearchMenuController(host));
|
||||||
final boolean isSearchV2Enabled = FeatureFactory.getFactory(context)
|
|
||||||
.getSearchFeatureProvider()
|
|
||||||
.isSearchV2Enabled(context);
|
|
||||||
|
|
||||||
if (isSearchV2Enabled) {
|
|
||||||
host.getLifecycle().addObserver(new SearchMenuController(host));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SearchMenuController(@NonNull Fragment host) {
|
private SearchMenuController(@NonNull Fragment host) {
|
||||||
|
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search.ranking;
|
|
||||||
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface SearchResultsRankerCallback {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when ranker provides the ranking scores.
|
|
||||||
* @param searchRankingScores Ordered List of Pairs of String and Float corresponding to
|
|
||||||
* stableIds and ranking scores. The list must be descendingly
|
|
||||||
* ordered based on scores.
|
|
||||||
*/
|
|
||||||
public void onRankingScoresAvailable(List<Pair<String, Float>> searchRankingScores);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when for any reason ranker fails, which notifies the client to proceed
|
|
||||||
* without ranking results.
|
|
||||||
*/
|
|
||||||
public void onRankingFailed();
|
|
||||||
}
|
|
@@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.content.pm.ServiceInfo;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.drawable.ColorDrawable;
|
|
||||||
import android.os.UserManager;
|
|
||||||
import android.view.accessibility.AccessibilityManager;
|
|
||||||
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
|
|
||||||
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.List;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
|
||||||
public class AccessibilityServiceResultFutureTaskTest {
|
|
||||||
|
|
||||||
private static final String QUERY = "test_query";
|
|
||||||
|
|
||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
|
||||||
private Context mContext;
|
|
||||||
@Mock
|
|
||||||
private PackageManager mPackageManager;
|
|
||||||
@Mock
|
|
||||||
private AccessibilityManager mAccessibilityManager;
|
|
||||||
@Mock
|
|
||||||
private SiteMapManager mSiteMapManager;
|
|
||||||
@Mock
|
|
||||||
private UserManager mUserManager;
|
|
||||||
|
|
||||||
private AccessibilityServiceResultLoader.AccessibilityServiceResultCallable mCallable;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
when(mContext.getSystemService(Context.ACCESSIBILITY_SERVICE))
|
|
||||||
.thenReturn(mAccessibilityManager);
|
|
||||||
when((Object)mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
|
|
||||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
|
||||||
|
|
||||||
mCallable = new AccessibilityServiceResultLoader.AccessibilityServiceResultCallable(
|
|
||||||
mContext, QUERY, mSiteMapManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_noService_shouldNotReturnAnything() throws Exception {
|
|
||||||
assertThat(mCallable.call()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_hasServiceMatchingTitle_shouldReturnResult() throws Exception {
|
|
||||||
addFakeAccessibilityService();
|
|
||||||
|
|
||||||
List<? extends SearchResult> results = mCallable.call();
|
|
||||||
assertThat(results).hasSize(1);
|
|
||||||
|
|
||||||
SearchResult result = results.get(0);
|
|
||||||
assertThat(result.title).isEqualTo(QUERY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_serviceDoesNotMatchTitle_shouldReturnResult() throws Exception {
|
|
||||||
addFakeAccessibilityService();
|
|
||||||
|
|
||||||
mCallable = new AccessibilityServiceResultLoader.AccessibilityServiceResultCallable(
|
|
||||||
mContext,
|
|
||||||
QUERY + "no_match", mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addFakeAccessibilityService() {
|
|
||||||
final List<AccessibilityServiceInfo> services = new ArrayList<>();
|
|
||||||
final AccessibilityServiceInfo info = mock(AccessibilityServiceInfo.class);
|
|
||||||
final ResolveInfo resolveInfo = mock(ResolveInfo.class);
|
|
||||||
when(info.getResolveInfo()).thenReturn(resolveInfo);
|
|
||||||
when(resolveInfo.loadIcon(mPackageManager)).thenReturn(new ColorDrawable(Color.BLUE));
|
|
||||||
when(resolveInfo.loadLabel(mPackageManager)).thenReturn(QUERY);
|
|
||||||
resolveInfo.serviceInfo = new ServiceInfo();
|
|
||||||
resolveInfo.serviceInfo.packageName = "pkg";
|
|
||||||
resolveInfo.serviceInfo.name = "class";
|
|
||||||
services.add(info);
|
|
||||||
|
|
||||||
when(mAccessibilityManager.getInstalledAccessibilityServiceList()).thenReturn(services);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.search;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.robolectric.RuntimeEnvironment;
|
|
||||||
import org.robolectric.annotation.Config;
|
|
||||||
import org.robolectric.util.ReflectionHelpers;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
|
||||||
public class InlineSwitchViewHolderTest {
|
|
||||||
|
|
||||||
private static final String TITLE = "title";
|
|
||||||
private static final String SUMMARY = "summary";
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private SearchFragment mFragment;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private InlineSwitchPayload mPayload;
|
|
||||||
|
|
||||||
private FakeFeatureFactory mFeatureFactory;
|
|
||||||
private InlineSwitchViewHolder mHolder;
|
|
||||||
private Drawable mIcon;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
final Context context = RuntimeEnvironment.application;
|
|
||||||
mIcon = context.getDrawable(R.drawable.ic_search_24dp);
|
|
||||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
|
||||||
|
|
||||||
mHolder = new InlineSwitchViewHolder(
|
|
||||||
LayoutInflater.from(context).inflate(R.layout.search_inline_switch_item, null),
|
|
||||||
context);
|
|
||||||
ReflectionHelpers.setField(mHolder, "mMetricsFeatureProvider",
|
|
||||||
mFeatureFactory.metricsFeatureProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testConstructor_MembersNotNull() {
|
|
||||||
assertThat(mHolder.titleView).isNotNull();
|
|
||||||
assertThat(mHolder.summaryView).isNotNull();
|
|
||||||
assertThat(mHolder.iconView).isNotNull();
|
|
||||||
assertThat(mHolder.switchView).isNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBindViewElements_AllUpdated() {
|
|
||||||
when(mPayload.getValue(any(Context.class))).thenReturn(1);
|
|
||||||
SearchResult result = getSearchResult();
|
|
||||||
mHolder.onBind(mFragment, result);
|
|
||||||
// Precondition: switch is on.
|
|
||||||
assertThat(mHolder.switchView.isChecked()).isTrue();
|
|
||||||
|
|
||||||
mHolder.switchView.performClick();
|
|
||||||
|
|
||||||
assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
|
|
||||||
assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
|
|
||||||
assertThat(mHolder.iconView.getDrawable()).isEqualTo(mIcon);
|
|
||||||
assertThat(mHolder.switchView.isChecked()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
private SearchResult getSearchResult() {
|
|
||||||
SearchResult.Builder builder = new SearchResult.Builder();
|
|
||||||
|
|
||||||
builder.setTitle(TITLE)
|
|
||||||
.setSummary(SUMMARY)
|
|
||||||
.setRank(1)
|
|
||||||
.setPayload(new InlineSwitchPayload("" /* uri */, 0 /* mSettingSource */,
|
|
||||||
1 /* onValue */, null /* intent */, true /* isDeviceSupported */,
|
|
||||||
1 /* default */))
|
|
||||||
.addBreadcrumbs(new ArrayList<>())
|
|
||||||
.setIcon(mIcon)
|
|
||||||
.setPayload(mPayload)
|
|
||||||
.setStableId(TITLE.hashCode());
|
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,177 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
|
||||||
|
|
||||||
import static com.android.settings.search.InputDeviceResultLoader.PHYSICAL_KEYBOARD_FRAGMENT;
|
|
||||||
import static com.android.settings.search.InputDeviceResultLoader.VIRTUAL_KEYBOARD_FRAGMENT;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import static org.mockito.Matchers.anyInt;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ServiceInfo;
|
|
||||||
import android.hardware.input.InputManager;
|
|
||||||
import android.view.InputDevice;
|
|
||||||
import android.view.inputmethod.InputMethodInfo;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
import com.android.settings.testutils.shadow.ShadowInputDevice;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
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.RuntimeEnvironment;
|
|
||||||
import org.robolectric.annotation.Config;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH,
|
|
||||||
sdk = TestConfig.SDK_VERSION,
|
|
||||||
shadows = {
|
|
||||||
ShadowInputDevice.class
|
|
||||||
})
|
|
||||||
public class InputDeviceResultFutureTaskTest {
|
|
||||||
|
|
||||||
private static final String QUERY = "test_query";
|
|
||||||
private static final List<String> PHYSICAL_KEYBOARD_BREADCRUMB;
|
|
||||||
private static final List<String> VIRTUAL_KEYBOARD_BREADCRUMB;
|
|
||||||
|
|
||||||
static {
|
|
||||||
PHYSICAL_KEYBOARD_BREADCRUMB = new ArrayList<>();
|
|
||||||
VIRTUAL_KEYBOARD_BREADCRUMB = new ArrayList<>();
|
|
||||||
PHYSICAL_KEYBOARD_BREADCRUMB.add("Settings");
|
|
||||||
PHYSICAL_KEYBOARD_BREADCRUMB.add("physical keyboard");
|
|
||||||
VIRTUAL_KEYBOARD_BREADCRUMB.add("Settings");
|
|
||||||
VIRTUAL_KEYBOARD_BREADCRUMB.add("virtual keyboard");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
|
||||||
private Context mContext;
|
|
||||||
@Mock
|
|
||||||
private SiteMapManager mSiteMapManager;
|
|
||||||
@Mock
|
|
||||||
private InputManager mInputManager;
|
|
||||||
@Mock
|
|
||||||
private InputMethodManager mImm;
|
|
||||||
@Mock
|
|
||||||
private PackageManager mPackageManager;
|
|
||||||
|
|
||||||
private InputDeviceResultLoader.InputDeviceResultCallable mCallable;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
when(mContext.getApplicationContext()).thenReturn(mContext);
|
|
||||||
when(mContext.getSystemService(Context.INPUT_SERVICE))
|
|
||||||
.thenReturn(mInputManager);
|
|
||||||
when(mContext.getSystemService(INPUT_METHOD_SERVICE))
|
|
||||||
.thenReturn(mImm);
|
|
||||||
when(mContext.getPackageManager())
|
|
||||||
.thenReturn(mPackageManager);
|
|
||||||
when(mContext.getString(anyInt()))
|
|
||||||
.thenAnswer(invocation -> RuntimeEnvironment.application.getString(
|
|
||||||
(Integer) invocation.getArguments()[0]));
|
|
||||||
mCallable = new InputDeviceResultLoader.InputDeviceResultCallable(mContext, QUERY,
|
|
||||||
mSiteMapManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
ShadowInputDevice.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_noKeyboard_shouldNotReturnAnything() throws Exception {
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_hasPhysicalKeyboard_match() throws Exception {
|
|
||||||
addPhysicalKeyboard(QUERY);
|
|
||||||
when(mSiteMapManager.buildBreadCrumb(mContext, PHYSICAL_KEYBOARD_FRAGMENT,
|
|
||||||
RuntimeEnvironment.application.getString(R.string.physical_keyboard_title)))
|
|
||||||
.thenReturn(PHYSICAL_KEYBOARD_BREADCRUMB);
|
|
||||||
|
|
||||||
final List<? extends SearchResult> results = mCallable.call();
|
|
||||||
|
|
||||||
assertThat(results).hasSize(1);
|
|
||||||
assertThat(results.get(0).title).isEqualTo(QUERY);
|
|
||||||
assertThat(results.get(0).breadcrumbs)
|
|
||||||
.containsExactlyElementsIn(PHYSICAL_KEYBOARD_BREADCRUMB);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_hasVirtualKeyboard_match() throws Exception {
|
|
||||||
addVirtualKeyboard(QUERY);
|
|
||||||
when(mSiteMapManager.buildBreadCrumb(mContext, VIRTUAL_KEYBOARD_FRAGMENT,
|
|
||||||
RuntimeEnvironment.application.getString(R.string.add_virtual_keyboard)))
|
|
||||||
.thenReturn(VIRTUAL_KEYBOARD_BREADCRUMB);
|
|
||||||
|
|
||||||
final List<? extends SearchResult> results = mCallable.call();
|
|
||||||
assertThat(results).hasSize(1);
|
|
||||||
assertThat(results.get(0).title).isEqualTo(QUERY);
|
|
||||||
assertThat(results.get(0).breadcrumbs)
|
|
||||||
.containsExactlyElementsIn(VIRTUAL_KEYBOARD_BREADCRUMB);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_hasPhysicalVirtualKeyboard_doNotMatch() throws Exception {
|
|
||||||
addPhysicalKeyboard("abc");
|
|
||||||
addVirtualKeyboard("def");
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).isEmpty();
|
|
||||||
verifyZeroInteractions(mSiteMapManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addPhysicalKeyboard(String name) {
|
|
||||||
final InputDevice device = mock(InputDevice.class);
|
|
||||||
when(device.isVirtual()).thenReturn(false);
|
|
||||||
when(device.isFullKeyboard()).thenReturn(true);
|
|
||||||
when(device.getName()).thenReturn(name);
|
|
||||||
ShadowInputDevice.sDeviceIds = new int[]{0};
|
|
||||||
ShadowInputDevice.addDevice(0, device);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addVirtualKeyboard(String name) {
|
|
||||||
final List<InputMethodInfo> imis = new ArrayList<>();
|
|
||||||
final InputMethodInfo info = mock(InputMethodInfo.class);
|
|
||||||
imis.add(info);
|
|
||||||
when(info.getServiceInfo()).thenReturn(new ServiceInfo());
|
|
||||||
when(info.loadLabel(mPackageManager)).thenReturn(name);
|
|
||||||
info.getServiceInfo().packageName = "pkg";
|
|
||||||
info.getServiceInfo().name = "class";
|
|
||||||
when(mImm.getInputMethodList()).thenReturn(imis);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,441 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
|
||||||
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Matchers.anyInt;
|
|
||||||
import static org.mockito.Matchers.anyList;
|
|
||||||
import static org.mockito.Matchers.anyString;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.ActivityInfo;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.content.pm.UserInfo;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.os.UserManager;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
|
||||||
import com.android.settings.testutils.ApplicationTestUtils;
|
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
import com.android.settingslib.wrapper.PackageManagerWrapper;
|
|
||||||
|
|
||||||
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.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@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;
|
|
||||||
@Mock
|
|
||||||
private SiteMapManager mSiteMapManager;
|
|
||||||
|
|
||||||
private InstalledAppResultLoader.InstalledAppResultCallable mCallable;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
|
|
||||||
final FakeFeatureFactory factory = FakeFeatureFactory.setupForTest();
|
|
||||||
when(factory.searchFeatureProvider.getSiteMapManager())
|
|
||||||
.thenReturn(mSiteMapManager);
|
|
||||||
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(mContext.getString(R.string.applications_settings))
|
|
||||||
.thenReturn("app");
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
|
|
||||||
0 /* targetSdkVersion */),
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app2", FLAG_SYSTEM,
|
|
||||||
0 /* targetSdkVersion */),
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app3", FLAG_SYSTEM,
|
|
||||||
0 /* targetSdkVersion */),
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app4", 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */),
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app", 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */),
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "appBuffer", 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_noMatchingQuery_shouldReturnEmptyResult() throws Exception {
|
|
||||||
final String query = "abc";
|
|
||||||
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_matchingQuery_shouldReturnNonSystemApps() throws Exception {
|
|
||||||
final String query = "app";
|
|
||||||
|
|
||||||
mCallable = spy(new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager));
|
|
||||||
when(mSiteMapManager.buildBreadCrumb(eq(mContext), anyString(), anyString()))
|
|
||||||
.thenReturn(Arrays.asList(new String[] {"123"}));
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(3);
|
|
||||||
verify(mSiteMapManager)
|
|
||||||
.buildBreadCrumb(eq(mContext), anyString(), anyString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_matchingQuery_shouldReturnSystemAppUpdates() throws Exception {
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_UPDATED_SYSTEM_APP,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
final String query = "app";
|
|
||||||
|
|
||||||
mCallable = spy(new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager));
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(1);
|
|
||||||
verify(mSiteMapManager)
|
|
||||||
.buildBreadCrumb(eq(mContext), anyString(), anyString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_matchingQuery_shouldReturnSystemAppIfLaunchable() throws Exception {
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
final List<ResolveInfo> list = mock(List.class);
|
|
||||||
when(list.size()).thenReturn(1);
|
|
||||||
when(mPackageManagerWrapper.queryIntentActivitiesAsUser(
|
|
||||||
any(Intent.class), anyInt(), anyInt()))
|
|
||||||
.thenReturn(list);
|
|
||||||
|
|
||||||
final String query = "app";
|
|
||||||
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_matchingQuery_shouldReturnSystemAppIfHomeApp() throws Exception {
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
when(mPackageManagerWrapper.queryIntentActivitiesAsUser(
|
|
||||||
any(Intent.class), anyInt(), anyInt()))
|
|
||||||
.thenReturn(null);
|
|
||||||
|
|
||||||
when(mPackageManagerWrapper.getHomeActivities(anyList())).thenAnswer(invocation -> {
|
|
||||||
final List<ResolveInfo> list = (List<ResolveInfo>) invocation.getArguments()[0];
|
|
||||||
final ResolveInfo info = new ResolveInfo();
|
|
||||||
info.activityInfo = new ActivityInfo();
|
|
||||||
info.activityInfo.packageName = "app1";
|
|
||||||
list.add(info);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
final String query = "app";
|
|
||||||
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() throws Exception {
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
when(mPackageManagerWrapper.queryIntentActivitiesAsUser(
|
|
||||||
any(Intent.class), anyInt(), anyInt()))
|
|
||||||
.thenReturn(null);
|
|
||||||
|
|
||||||
final String query = "app";
|
|
||||||
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).isEmpty();
|
|
||||||
verify(mSiteMapManager, never())
|
|
||||||
.buildBreadCrumb(eq(mContext), anyString(), anyString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_matchingQuery_multipleResults() throws Exception {
|
|
||||||
final String query = "app";
|
|
||||||
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
final List<? extends SearchResult> results = mCallable.call();
|
|
||||||
|
|
||||||
Set<CharSequence> expectedTitles = new HashSet<>(Arrays.asList("app4", "app", "appBuffer"));
|
|
||||||
Set<CharSequence> actualTitles = new HashSet<>();
|
|
||||||
for (SearchResult result : results) {
|
|
||||||
actualTitles.add(result.title);
|
|
||||||
}
|
|
||||||
assertThat(actualTitles).isEqualTo(expectedTitles);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_normalWord_MatchPrefix() throws Exception {
|
|
||||||
final String query = "ba";
|
|
||||||
final String packageName = "Bananas";
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_CapitalCase_DoestMatchSecondWord() throws Exception {
|
|
||||||
final String query = "Apples";
|
|
||||||
final String packageName = "BananasApples";
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_TwoWords_MatchesFirstWord() throws Exception {
|
|
||||||
final String query = "Banana";
|
|
||||||
final String packageName = "Bananas Apples";
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_TwoWords_MatchesSecondWord() throws Exception {
|
|
||||||
final String query = "Apple";
|
|
||||||
final String packageName = "Bananas Apples";
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_ThreeWords_MatchesThirdWord() throws Exception {
|
|
||||||
final String query = "Pear";
|
|
||||||
final String packageName = "Bananas Apples Pears";
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_DoubleSpacedWords_MatchesSecondWord() throws Exception {
|
|
||||||
final String query = "Apple";
|
|
||||||
final String packageName = "Bananas Apples";
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_SpecialChar_MatchesSecondWord() throws Exception {
|
|
||||||
final String query = "Apple";
|
|
||||||
final String packageName = "Bananas & Apples";
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_TabSeparated_MatchesSecondWord() throws Exception {
|
|
||||||
final String query = "Apple";
|
|
||||||
final String packageName = "Bananas\tApples";
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_LeadingNumber_MatchesWord() throws Exception {
|
|
||||||
final String query = "4";
|
|
||||||
final String packageName = "4Bananas";
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_FirstWordPrefixOfQuery_NoMatch() throws Exception {
|
|
||||||
final String query = "Bananass";
|
|
||||||
final String packageName = "Bananas Apples";
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_QueryLongerThanAppName_NoMatch() throws Exception {
|
|
||||||
final String query = "BananasApples";
|
|
||||||
final String packageName = "Bananas";
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(mCallable.call()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void query_appExistsInBothProfiles() throws Exception {
|
|
||||||
final String query = "carrot";
|
|
||||||
final String packageName = "carrot";
|
|
||||||
final int user1 = 0;
|
|
||||||
final int user2 = 10;
|
|
||||||
final int uid = 67672;
|
|
||||||
List<UserInfo> infos = new ArrayList<>();
|
|
||||||
infos.add(new UserInfo(user1, "user 1", 0));
|
|
||||||
infos.add(new UserInfo(user2, "user 2", UserInfo.FLAG_MANAGED_PROFILE));
|
|
||||||
|
|
||||||
when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
|
|
||||||
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), eq(user1)))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(UserHandle.getUid(user1, uid) /* uid */,
|
|
||||||
packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), eq(user2)))
|
|
||||||
.thenReturn(Arrays.asList(
|
|
||||||
ApplicationTestUtils.buildInfo(UserHandle.getUid(user2, uid) /* uid */,
|
|
||||||
packageName, 0 /* flags */,
|
|
||||||
0 /* targetSdkVersion */)));
|
|
||||||
|
|
||||||
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
|
||||||
mPackageManagerWrapper, query,
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
List<AppSearchResult> searchResults = (List<AppSearchResult>) mCallable.call();
|
|
||||||
assertThat(searchResults).hasSize(2);
|
|
||||||
|
|
||||||
Set<Integer> uidResults = searchResults.stream().map(result -> result.info.uid).collect(
|
|
||||||
Collectors.toSet());
|
|
||||||
assertThat(uidResults).containsExactly(
|
|
||||||
UserHandle.getUid(user1, uid),
|
|
||||||
UserHandle.getUid(user2, uid));
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,283 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.graphics.drawable.ColorDrawable;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.search.SearchResult.Builder;
|
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
|
|
||||||
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.RuntimeEnvironment;
|
|
||||||
import org.robolectric.annotation.Config;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
|
||||||
public class IntentSearchViewHolderTest {
|
|
||||||
|
|
||||||
private static final String TITLE = "title";
|
|
||||||
private static final String SUMMARY = "summary";
|
|
||||||
private static final int USER_ID = 10;
|
|
||||||
private static final String BADGED_LABEL = "work title";
|
|
||||||
|
|
||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
|
||||||
private Context mContext;
|
|
||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
|
||||||
private SearchFragment mFragment;
|
|
||||||
@Mock
|
|
||||||
private PackageManager mPackageManager;
|
|
||||||
private FakeFeatureFactory mFeatureFactory;
|
|
||||||
private IntentSearchViewHolder mHolder;
|
|
||||||
private Drawable mIcon;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
|
||||||
|
|
||||||
final Context context = RuntimeEnvironment.application;
|
|
||||||
final View view = LayoutInflater.from(context).inflate(R.layout.search_intent_item, null);
|
|
||||||
mHolder = new IntentSearchViewHolder(view);
|
|
||||||
|
|
||||||
mIcon = context.getDrawable(R.drawable.ic_search_24dp);
|
|
||||||
when(mFragment.getActivity().getPackageManager()).thenReturn(mPackageManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testConstructor_membersNotNull() {
|
|
||||||
assertThat(mHolder.titleView).isNotNull();
|
|
||||||
assertThat(mHolder.summaryView).isNotNull();
|
|
||||||
assertThat(mHolder.iconView).isNotNull();
|
|
||||||
assertThat(mHolder.breadcrumbView).isNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBindViewElements_allUpdated() {
|
|
||||||
final SearchResult result = getSearchResult(TITLE, SUMMARY, mIcon);
|
|
||||||
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);
|
|
||||||
assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.VISIBLE);
|
|
||||||
assertThat(mHolder.breadcrumbView.getVisibility()).isEqualTo(View.GONE);
|
|
||||||
|
|
||||||
verify(mFragment).onSearchResultClicked(eq(mHolder), any(SearchResult.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBindViewIcon_nullIcon_imageDrawableIsNull() {
|
|
||||||
final SearchResult result = getSearchResult(TITLE, SUMMARY, null);
|
|
||||||
mHolder.onBind(mFragment, result);
|
|
||||||
|
|
||||||
assertThat(mHolder.iconView.getDrawable()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBindViewElements_emptySummary_hideSummaryView() {
|
|
||||||
final SearchResult result = new Builder()
|
|
||||||
.setTitle(TITLE)
|
|
||||||
.setRank(1)
|
|
||||||
.setPayload(new ResultPayload(null))
|
|
||||||
.setIcon(mIcon)
|
|
||||||
.setStableId(1)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
mHolder.onBind(mFragment, result);
|
|
||||||
assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBindViewElements_withBreadcrumb_shouldFormatBreadcrumb() {
|
|
||||||
final List<String> breadcrumbs = new ArrayList<>();
|
|
||||||
breadcrumbs.add("a");
|
|
||||||
breadcrumbs.add("b");
|
|
||||||
breadcrumbs.add("c");
|
|
||||||
final SearchResult result = new Builder()
|
|
||||||
.setTitle(TITLE)
|
|
||||||
.setRank(1)
|
|
||||||
.setPayload(new ResultPayload(null))
|
|
||||||
.addBreadcrumbs(breadcrumbs)
|
|
||||||
.setIcon(mIcon)
|
|
||||||
.setStableId(1)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
mHolder.onBind(mFragment, result);
|
|
||||||
assertThat(mHolder.breadcrumbView.getVisibility()).isEqualTo(View.VISIBLE);
|
|
||||||
assertThat(mHolder.breadcrumbView.getText()).isEqualTo("a > b > c");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBindElements_placeholderSummary_visibilityIsGone() {
|
|
||||||
final String nonBreakingSpace = mContext.getString(R.string.summary_placeholder);
|
|
||||||
final SearchResult result = new Builder()
|
|
||||||
.setTitle(TITLE)
|
|
||||||
.setSummary(nonBreakingSpace)
|
|
||||||
.setPayload(new ResultPayload(null))
|
|
||||||
.setStableId(1)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
mHolder.onBind(mFragment, result);
|
|
||||||
|
|
||||||
assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBindElements_dynamicSummary_visibilityIsGone() {
|
|
||||||
final String dynamicSummary = "%s";
|
|
||||||
final SearchResult result = new Builder()
|
|
||||||
.setTitle(TITLE)
|
|
||||||
.setSummary(dynamicSummary)
|
|
||||||
.setPayload(new ResultPayload(null))
|
|
||||||
.setStableId(1)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
mHolder.onBind(mFragment, result);
|
|
||||||
|
|
||||||
assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBindViewElements_appSearchResult() {
|
|
||||||
mHolder = spy(mHolder);
|
|
||||||
doReturn(new ColorDrawable(0)).when(mHolder).getBadgedIcon(any(ApplicationInfo.class),
|
|
||||||
anyInt());
|
|
||||||
when(mPackageManager.getUserBadgedLabel(any(CharSequence.class),
|
|
||||||
eq(new UserHandle(USER_ID)))).thenReturn(BADGED_LABEL);
|
|
||||||
|
|
||||||
final SearchResult result = getAppSearchResult(
|
|
||||||
TITLE, SUMMARY, mIcon, getApplicationInfo(USER_ID, TITLE, mIcon));
|
|
||||||
mHolder.onBind(mFragment, result);
|
|
||||||
mHolder.itemView.performClick();
|
|
||||||
|
|
||||||
assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
|
|
||||||
assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
|
|
||||||
assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.VISIBLE);
|
|
||||||
assertThat(mHolder.breadcrumbView.getVisibility()).isEqualTo(View.GONE);
|
|
||||||
assertThat(mHolder.titleView.getContentDescription()).isEqualTo(BADGED_LABEL);
|
|
||||||
|
|
||||||
verify(mFragment).onSearchResultClicked(eq(mHolder), any(SearchResult.class));
|
|
||||||
verify(mFragment.getActivity()).startActivityAsUser(
|
|
||||||
any(Intent.class), eq(new UserHandle(USER_ID)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBindViewElements_validSubSettingIntent_shouldLaunch() {
|
|
||||||
final SearchResult result = getSearchResult(TITLE, SUMMARY, mIcon);
|
|
||||||
when(mPackageManager.queryIntentActivities(result.payload.getIntent(), 0 /* flags */))
|
|
||||||
.thenReturn(Arrays.asList(new ResolveInfo()));
|
|
||||||
|
|
||||||
mHolder.onBind(mFragment, result);
|
|
||||||
mHolder.itemView.performClick();
|
|
||||||
|
|
||||||
assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
|
|
||||||
assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
|
|
||||||
assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.VISIBLE);
|
|
||||||
verify(mFragment).onSearchResultClicked(eq(mHolder), any(SearchResult.class));
|
|
||||||
verify(mFragment).startActivityForResult(result.payload.getIntent(),
|
|
||||||
IntentSearchViewHolder.REQUEST_CODE_NO_OP);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBindViewElements_invalidSubSettingIntent_shouldNotLaunchAnything() {
|
|
||||||
final SearchResult result = getSearchResult(TITLE, SUMMARY, mIcon);
|
|
||||||
when(mPackageManager.queryIntentActivities(result.payload.getIntent(), 0 /* flags */))
|
|
||||||
.thenReturn(null);
|
|
||||||
|
|
||||||
mHolder.onBind(mFragment, result);
|
|
||||||
mHolder.itemView.performClick();
|
|
||||||
|
|
||||||
assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
|
|
||||||
assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
|
|
||||||
assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.VISIBLE);
|
|
||||||
verify(mFragment).onSearchResultClicked(eq(mHolder), any(SearchResult.class));
|
|
||||||
verify(mFragment, never()).startActivityForResult(result.payload.getIntent(),
|
|
||||||
IntentSearchViewHolder.REQUEST_CODE_NO_OP);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SearchResult getSearchResult(String title, String summary, Drawable icon) {
|
|
||||||
Builder builder = new Builder();
|
|
||||||
builder.setStableId(Objects.hash(title, summary, icon))
|
|
||||||
.setTitle(title)
|
|
||||||
.setSummary(summary)
|
|
||||||
.setRank(1)
|
|
||||||
.setPayload(new ResultPayload(
|
|
||||||
new Intent().setComponent(new ComponentName("pkg", "class"))))
|
|
||||||
.addBreadcrumbs(new ArrayList<>())
|
|
||||||
.setStableId(1)
|
|
||||||
.setIcon(icon);
|
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private SearchResult getAppSearchResult(
|
|
||||||
String title, String summary, Drawable icon, ApplicationInfo applicationInfo) {
|
|
||||||
AppSearchResult.Builder builder = new AppSearchResult.Builder();
|
|
||||||
builder.setTitle(title)
|
|
||||||
.setSummary(summary)
|
|
||||||
.setRank(1)
|
|
||||||
.setPayload(new ResultPayload(
|
|
||||||
new Intent().setComponent(new ComponentName("pkg", "class"))))
|
|
||||||
.addBreadcrumbs(new ArrayList<>())
|
|
||||||
.setIcon(icon);
|
|
||||||
builder.setAppInfo(applicationInfo);
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ApplicationInfo getApplicationInfo(int userId, CharSequence appLabel, Drawable icon) {
|
|
||||||
ApplicationInfo applicationInfo = spy(new ApplicationInfo());
|
|
||||||
applicationInfo.uid = UserHandle.getUid(userId, 12345);
|
|
||||||
doReturn(icon).when(applicationInfo).loadIcon(any(PackageManager.class));
|
|
||||||
doReturn(appLabel).when(applicationInfo).loadLabel(any(PackageManager.class));
|
|
||||||
return applicationInfo;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
package com.android.settings.search;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import com.android.settings.search.SearchResult;
|
|
||||||
import com.android.settings.search.SearchResultLoader;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mock loader to subvert the requirements of returning data while also driving the Loader
|
|
||||||
* lifecycle.
|
|
||||||
*/
|
|
||||||
public class MockSearchResultLoader extends SearchResultLoader {
|
|
||||||
|
|
||||||
public MockSearchResultLoader(Context context) {
|
|
||||||
super(context, "test");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<? extends SearchResult> loadInBackground() {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDiscardResult(List<? extends SearchResult> result) {
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.testutils.DatabaseTestUtils;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.robolectric.RuntimeEnvironment;
|
|
||||||
import org.robolectric.annotation.Config;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
|
||||||
public class SavedQueryLoaderTest {
|
|
||||||
|
|
||||||
private Context mContext;
|
|
||||||
private SQLiteDatabase mDb;
|
|
||||||
private SavedQueryLoader mLoader;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
mContext = RuntimeEnvironment.application;
|
|
||||||
mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
|
||||||
mLoader = new SavedQueryLoader(mContext);
|
|
||||||
setUpDb();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void cleanUp() {
|
|
||||||
DatabaseTestUtils.clearDb(mContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void loadInBackground_shouldReturnSavedQueries() {
|
|
||||||
final List<? extends SearchResult> results = mLoader.loadInBackground();
|
|
||||||
assertThat(results.size()).isEqualTo(SavedQueryLoader.MAX_PROPOSED_SUGGESTIONS);
|
|
||||||
for (SearchResult result : results) {
|
|
||||||
assertThat(result.viewType).isEqualTo(ResultPayload.PayloadType.SAVED_QUERY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUpDb() {
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
for (int i = 0; i < SavedQueryLoader.MAX_PROPOSED_SUGGESTIONS + 2; i++) {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(IndexDatabaseHelper.SavedQueriesColumns.QUERY, String.valueOf(i));
|
|
||||||
values.put(IndexDatabaseHelper.SavedQueriesColumns.TIME_STAMP, now);
|
|
||||||
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES, null, values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.robolectric.annotation.Config;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
|
||||||
public class SavedQueryPayloadTest {
|
|
||||||
|
|
||||||
private SavedQueryPayload mPayload;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getType_shouldBeSavedQueryType() {
|
|
||||||
mPayload = new SavedQueryPayload("Test");
|
|
||||||
assertThat(mPayload.getType()).isEqualTo(ResultPayload.PayloadType.SAVED_QUERY);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.testutils.DatabaseTestUtils;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.robolectric.RuntimeEnvironment;
|
|
||||||
import org.robolectric.annotation.Config;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
|
||||||
public class SavedQueryRecorderAndRemoverTest {
|
|
||||||
|
|
||||||
private Context mContext;
|
|
||||||
private SavedQueryRecorder mRecorder;
|
|
||||||
private SavedQueryRemover mRemover;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
mContext = RuntimeEnvironment.application;
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void cleanUp() {
|
|
||||||
DatabaseTestUtils.clearDb(mContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canSaveAndRemoveQuery() {
|
|
||||||
final String query = "test";
|
|
||||||
mRecorder = new SavedQueryRecorder(mContext, query);
|
|
||||||
mRemover = new SavedQueryRemover(mContext);
|
|
||||||
|
|
||||||
// Record a new query and load all queries from DB
|
|
||||||
mRecorder.loadInBackground();
|
|
||||||
final SavedQueryLoader loader = new SavedQueryLoader(mContext);
|
|
||||||
List<? extends SearchResult> results = loader.loadInBackground();
|
|
||||||
|
|
||||||
// Should contain the newly recorded query
|
|
||||||
assertThat(results.size()).isEqualTo(1);
|
|
||||||
assertThat(results.get(0).title).isEqualTo(query);
|
|
||||||
|
|
||||||
// Remove the query and load all queries from DB
|
|
||||||
mRemover.loadInBackground();
|
|
||||||
results = loader.loadInBackground();
|
|
||||||
|
|
||||||
// Saved query list should be empty because it's removed.
|
|
||||||
assertThat(results).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canRemoveAllQueriesAtOnce() {
|
|
||||||
mRemover = new SavedQueryRemover(mContext);;
|
|
||||||
|
|
||||||
// Record a new query and load all queries from DB
|
|
||||||
new SavedQueryRecorder(mContext, "Test1").loadInBackground();
|
|
||||||
new SavedQueryRecorder(mContext, "Test2").loadInBackground();
|
|
||||||
final SavedQueryLoader loader = new SavedQueryLoader(mContext);
|
|
||||||
List<? extends SearchResult> results = loader.loadInBackground();
|
|
||||||
assertThat(results.size()).isEqualTo(2);
|
|
||||||
|
|
||||||
mRemover.loadInBackground();
|
|
||||||
results = loader.loadInBackground();
|
|
||||||
|
|
||||||
// Saved query list should be empty because it's removed.
|
|
||||||
assertThat(results).isEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.nullable;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.robolectric.RuntimeEnvironment;
|
|
||||||
import org.robolectric.annotation.Config;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
|
||||||
public class SavedQueryViewHolderTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private SearchFragment mSearchFragment;
|
|
||||||
private Context mContext;
|
|
||||||
private SavedQueryViewHolder mHolder;
|
|
||||||
private View mView;
|
|
||||||
private View mTitleView;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
mContext = RuntimeEnvironment.application;
|
|
||||||
mView = LayoutInflater.from(mContext)
|
|
||||||
.inflate(R.layout.search_saved_query_item, null);
|
|
||||||
mTitleView = mView.findViewById(android.R.id.title);
|
|
||||||
mHolder = new SavedQueryViewHolder(mView);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onBind_shouldBindClickCallback() {
|
|
||||||
final SearchResult result = mock(SearchResult.class);
|
|
||||||
mHolder.onBind(mSearchFragment, result);
|
|
||||||
|
|
||||||
mHolder.itemView.performClick();
|
|
||||||
|
|
||||||
verify(mSearchFragment).onSavedQueryClicked(nullable(CharSequence.class));
|
|
||||||
}
|
|
||||||
}
|
|
@@ -18,19 +18,15 @@
|
|||||||
package com.android.settings.search;
|
package com.android.settings.search;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.robolectric.Shadows.shadowOf;
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.util.FeatureFlagUtils;
|
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import com.android.settings.TestConfig;
|
import com.android.settings.TestConfig;
|
||||||
import com.android.settings.core.FeatureFlags;
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
import com.android.settings.dashboard.SiteMapManager;
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
||||||
import com.android.settings.testutils.shadow.SettingsShadowSystemProperties;
|
import com.android.settings.testutils.shadow.SettingsShadowSystemProperties;
|
||||||
@@ -73,31 +69,10 @@ public class SearchFeatureProviderImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getDatabaseSearchLoader_shouldCleanupQuery() {
|
public void initSearchToolbar_shouldInitWithOnClickListener() {
|
||||||
final String query = " space ";
|
|
||||||
|
|
||||||
mProvider.getStaticSearchResultTask(mActivity, query);
|
|
||||||
|
|
||||||
verify(mProvider).cleanQuery(eq(query));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getInstalledAppSearchLoader_shouldCleanupQuery() {
|
|
||||||
final String query = " space ";
|
|
||||||
|
|
||||||
mProvider.getInstalledAppSearchTask(mActivity, query);
|
|
||||||
|
|
||||||
verify(mProvider).cleanQuery(eq(query));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void initSearchToolbar_searchV2_shouldInitWithOnClickListener() {
|
|
||||||
mProvider.initSearchToolbar(mActivity, null);
|
mProvider.initSearchToolbar(mActivity, null);
|
||||||
// Should not crash.
|
// Should not crash.
|
||||||
|
|
||||||
SettingsShadowSystemProperties.set(
|
|
||||||
FeatureFlagUtils.FFLAG_PREFIX + FeatureFlags.SEARCH_V2,
|
|
||||||
"true");
|
|
||||||
final Toolbar toolbar = new Toolbar(mActivity);
|
final Toolbar toolbar = new Toolbar(mActivity);
|
||||||
mProvider.initSearchToolbar(mActivity, toolbar);
|
mProvider.initSearchToolbar(mActivity, toolbar);
|
||||||
|
|
||||||
@@ -109,25 +84,6 @@ public class SearchFeatureProviderImplTest {
|
|||||||
.isEqualTo("com.android.settings.action.SETTINGS_SEARCH");
|
.isEqualTo("com.android.settings.action.SETTINGS_SEARCH");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void initSearchToolbar_searchV1_shouldInitWithOnClickListener() {
|
|
||||||
mProvider.initSearchToolbar(mActivity, null);
|
|
||||||
// Should not crash.
|
|
||||||
|
|
||||||
SettingsShadowSystemProperties.set(
|
|
||||||
FeatureFlagUtils.FFLAG_PREFIX + FeatureFlags.SEARCH_V2,
|
|
||||||
"false");
|
|
||||||
final Toolbar toolbar = new Toolbar(mActivity);
|
|
||||||
mProvider.initSearchToolbar(mActivity, toolbar);
|
|
||||||
|
|
||||||
toolbar.performClick();
|
|
||||||
|
|
||||||
final Intent launchIntent = shadowOf(mActivity).getNextStartedActivity();
|
|
||||||
|
|
||||||
assertThat(launchIntent.getComponent().getClassName())
|
|
||||||
.isEqualTo(SearchActivity.class.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void verifyLaunchSearchResultPageCaller_nullCaller_shouldCrash() {
|
public void verifyLaunchSearchResultPageCaller_nullCaller_shouldCrash() {
|
||||||
mProvider.verifyLaunchSearchResultPageCaller(mActivity, null /* caller */);
|
mProvider.verifyLaunchSearchResultPageCaller(mActivity, null /* caller */);
|
||||||
|
@@ -1,413 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static org.mockito.ArgumentMatchers.nullable;
|
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Matchers.anyInt;
|
|
||||||
import static org.mockito.Matchers.anyString;
|
|
||||||
import static org.mockito.Matchers.argThat;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.app.LoaderManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Pair;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.android.internal.logging.nano.MetricsProto;
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.SettingsActivity;
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.core.instrumentation.VisibilityLoggerMixin;
|
|
||||||
import com.android.settings.testutils.DatabaseTestUtils;
|
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
import com.android.settings.testutils.shadow.SettingsShadowResources;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.ArgumentMatcher;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.robolectric.Robolectric;
|
|
||||||
import org.robolectric.RuntimeEnvironment;
|
|
||||||
import org.robolectric.android.controller.ActivityController;
|
|
||||||
import org.robolectric.annotation.Config;
|
|
||||||
import org.robolectric.util.ReflectionHelpers;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH,
|
|
||||||
sdk = TestConfig.SDK_VERSION,
|
|
||||||
shadows = {
|
|
||||||
SettingsShadowResources.class,
|
|
||||||
SettingsShadowResources.SettingsShadowTheme.class,
|
|
||||||
})
|
|
||||||
public class SearchFragmentTest {
|
|
||||||
@Mock
|
|
||||||
private SearchResultLoader mSearchResultLoader;
|
|
||||||
@Mock
|
|
||||||
private SavedQueryLoader mSavedQueryLoader;
|
|
||||||
@Mock
|
|
||||||
private SavedQueryController mSavedQueryController;
|
|
||||||
@Mock
|
|
||||||
private SearchResultsAdapter mSearchResultsAdapter;
|
|
||||||
|
|
||||||
private FakeFeatureFactory mFeatureFactory;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
|
|
||||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
DatabaseTestUtils.clearDb(RuntimeEnvironment.application);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void screenRotate_shouldPersistQuery() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider
|
|
||||||
.getSearchResultLoader(any(Context.class), anyString()))
|
|
||||||
.thenReturn(new MockSearchResultLoader(RuntimeEnvironment.application));
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
|
||||||
.thenReturn(mSavedQueryLoader);
|
|
||||||
|
|
||||||
final Bundle bundle = new Bundle();
|
|
||||||
final String testQuery = "test";
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content);
|
|
||||||
|
|
||||||
ReflectionHelpers.setField(fragment, "mShowingSavedQuery", false);
|
|
||||||
fragment.mQuery = testQuery;
|
|
||||||
|
|
||||||
activityController.saveInstanceState(bundle).pause().stop().destroy();
|
|
||||||
|
|
||||||
activityController = Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup(bundle);
|
|
||||||
|
|
||||||
assertThat(fragment.mQuery).isEqualTo(testQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void screenRotateEmptyString_ShouldNotCrash() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
|
||||||
.thenReturn(mSavedQueryLoader);
|
|
||||||
|
|
||||||
final Bundle bundle = new Bundle();
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
|
|
||||||
.thenReturn(true);
|
|
||||||
|
|
||||||
fragment.mQuery = "";
|
|
||||||
|
|
||||||
activityController.saveInstanceState(bundle).pause().stop().destroy();
|
|
||||||
|
|
||||||
activityController = Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup(bundle);
|
|
||||||
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider, never())
|
|
||||||
.getStaticSearchResultTask(any(Context.class), anyString());
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider, never())
|
|
||||||
.getInstalledAppSearchTask(any(Context.class), anyString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void queryTextChange_shouldTriggerLoader() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider
|
|
||||||
.getSearchResultLoader(any(Context.class), anyString()))
|
|
||||||
.thenReturn(mSearchResultLoader);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
|
||||||
.thenReturn(mSavedQueryLoader);
|
|
||||||
|
|
||||||
final String testQuery = "test";
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
|
|
||||||
.thenReturn(true);
|
|
||||||
|
|
||||||
fragment.onQueryTextChange(testQuery);
|
|
||||||
activityController.get().onBackPressed();
|
|
||||||
|
|
||||||
activityController.pause().stop().destroy();
|
|
||||||
|
|
||||||
verify(mFeatureFactory.metricsFeatureProvider, never()).action(
|
|
||||||
any(Context.class),
|
|
||||||
eq(MetricsProto.MetricsEvent.ACTION_LEAVE_SEARCH_RESULT_WITHOUT_QUERY));
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider)
|
|
||||||
.getSearchResultLoader(any(Context.class), anyString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onSearchResultsDisplayed_noResult_shouldShowNoResultView() {
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = spy((SearchFragment) activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content));
|
|
||||||
fragment.onSearchResultsDisplayed(0 /* count */);
|
|
||||||
|
|
||||||
assertThat(fragment.mNoResultsView.getVisibility()).isEqualTo(View.VISIBLE);
|
|
||||||
verify(mFeatureFactory.metricsFeatureProvider).visible(
|
|
||||||
any(Context.class),
|
|
||||||
anyInt(),
|
|
||||||
eq(MetricsProto.MetricsEvent.SETTINGS_SEARCH_NO_RESULT));
|
|
||||||
verify(mFeatureFactory.metricsFeatureProvider).action(
|
|
||||||
any(VisibilityLoggerMixin.class),
|
|
||||||
eq(MetricsProto.MetricsEvent.ACTION_SEARCH_RESULTS),
|
|
||||||
eq(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void queryTextChangeToEmpty_shouldLoadSavedQueryAndNotInitializeSearch() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
|
||||||
.thenReturn(mSavedQueryLoader);
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = spy((SearchFragment) activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content));
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
|
|
||||||
.thenReturn(true);
|
|
||||||
ReflectionHelpers.setField(fragment, "mSavedQueryController", mSavedQueryController);
|
|
||||||
ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
|
|
||||||
fragment.mQuery = "123";
|
|
||||||
|
|
||||||
fragment.onQueryTextChange("");
|
|
||||||
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider, never())
|
|
||||||
.getStaticSearchResultTask(any(Context.class), anyString());
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider, never())
|
|
||||||
.getInstalledAppSearchTask(any(Context.class), anyString());
|
|
||||||
verify(mSavedQueryController).loadSavedQueries();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void updateIndex_TriggerOnCreate() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
|
||||||
.thenReturn(mSavedQueryLoader);
|
|
||||||
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
|
|
||||||
.thenReturn(true);
|
|
||||||
|
|
||||||
fragment.onAttach(null);
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider).updateIndexAsync(any(Context.class),
|
|
||||||
any(IndexingCallback.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void whenNoQuery_HideFeedbackIsCalled() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider
|
|
||||||
.getSearchResultLoader(any(Context.class), anyString()))
|
|
||||||
.thenReturn(new MockSearchResultLoader(RuntimeEnvironment.application));
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
|
||||||
.thenReturn(mSavedQueryLoader);
|
|
||||||
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content));
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
|
|
||||||
.thenReturn(true);
|
|
||||||
when(fragment.getLoaderManager()).thenReturn(mock(LoaderManager.class));
|
|
||||||
|
|
||||||
fragment.onQueryTextChange("");
|
|
||||||
Robolectric.flushForegroundThreadScheduler();
|
|
||||||
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider).hideFeedbackButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onLoadFinished_ShowsFeedback() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider
|
|
||||||
.getSearchResultLoader(any(Context.class), anyString()))
|
|
||||||
.thenReturn(new MockSearchResultLoader(RuntimeEnvironment.application));
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
|
||||||
.thenReturn(mSavedQueryLoader);
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
|
|
||||||
.thenReturn(true);
|
|
||||||
|
|
||||||
fragment.onQueryTextChange("non-empty");
|
|
||||||
Robolectric.flushForegroundThreadScheduler();
|
|
||||||
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider).showFeedbackButton(any(SearchFragment.class),
|
|
||||||
any(View.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void preIndexingFinished_isIndexingFinishedFlag_isFalse() {
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content);
|
|
||||||
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
|
|
||||||
.thenReturn(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onIndexingFinished_notShowingSavedQuery_initLoaders() {
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content));
|
|
||||||
final LoaderManager loaderManager = mock(LoaderManager.class);
|
|
||||||
when(fragment.getLoaderManager()).thenReturn(loaderManager);
|
|
||||||
fragment.mShowingSavedQuery = false;
|
|
||||||
fragment.mQuery = null;
|
|
||||||
|
|
||||||
fragment.onIndexingFinished();
|
|
||||||
|
|
||||||
verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.SEARCH_RESULT),
|
|
||||||
eq(null), any(LoaderManager.LoaderCallbacks.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onIndexingFinished_showingSavedQuery_loadsSavedQueries() {
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content));
|
|
||||||
fragment.mShowingSavedQuery = true;
|
|
||||||
ReflectionHelpers.setField(fragment, "mSavedQueryController", mSavedQueryController);
|
|
||||||
|
|
||||||
fragment.onIndexingFinished();
|
|
||||||
|
|
||||||
verify(fragment.mSavedQueryController).loadSavedQueries();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onIndexingFinished_noActivity_shouldNotCrash() {
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content));
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
|
|
||||||
.thenReturn(true);
|
|
||||||
fragment.mQuery = "bright";
|
|
||||||
ReflectionHelpers.setField(fragment, "mLoaderManager", null);
|
|
||||||
ReflectionHelpers.setField(fragment, "mHost", null);
|
|
||||||
|
|
||||||
fragment.onIndexingFinished();
|
|
||||||
// no crash
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onSearchResultClicked_shouldLogResultMeta() {
|
|
||||||
SearchFragment fragment = new SearchFragment();
|
|
||||||
ReflectionHelpers.setField(fragment, "mMetricsFeatureProvider",
|
|
||||||
mFeatureFactory.metricsFeatureProvider);
|
|
||||||
ReflectionHelpers.setField(fragment, "mSearchFeatureProvider",
|
|
||||||
mFeatureFactory.searchFeatureProvider);
|
|
||||||
ReflectionHelpers.setField(fragment, "mSearchAdapter", mock(SearchResultsAdapter.class));
|
|
||||||
fragment.mSavedQueryController = mock(SavedQueryController.class);
|
|
||||||
|
|
||||||
// Should log result name, result count, clicked rank, etc.
|
|
||||||
final SearchViewHolder resultViewHolder = mock(SearchViewHolder.class);
|
|
||||||
when(resultViewHolder.getClickActionMetricName())
|
|
||||||
.thenReturn(MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT);
|
|
||||||
ResultPayload payLoad = new ResultPayload(
|
|
||||||
(new Intent()).putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, "test_setting"));
|
|
||||||
SearchResult searchResult = new SearchResult.Builder()
|
|
||||||
.setStableId(payLoad.hashCode())
|
|
||||||
.setPayload(payLoad)
|
|
||||||
.setTitle("setting_title")
|
|
||||||
.build();
|
|
||||||
fragment.onSearchResultClicked(resultViewHolder, searchResult);
|
|
||||||
|
|
||||||
verify(mFeatureFactory.metricsFeatureProvider).action(
|
|
||||||
nullable(Context.class),
|
|
||||||
eq(MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT),
|
|
||||||
eq("test_setting"),
|
|
||||||
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_COUNT)),
|
|
||||||
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_RANK)),
|
|
||||||
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH)));
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider).searchResultClicked(nullable(Context.class),
|
|
||||||
nullable(String.class), eq(searchResult));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onResume_shouldCallSearchRankingWarmupIfSmartSearchRankingEnabled() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
|
|
||||||
.thenReturn(true);
|
|
||||||
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content);
|
|
||||||
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider)
|
|
||||||
.searchRankingWarmup(any(Context.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onResume_shouldNotCallSearchRankingWarmupIfSmartSearchRankingDisabled() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
|
|
||||||
.thenReturn(false);
|
|
||||||
|
|
||||||
ActivityController<SearchActivity> activityController =
|
|
||||||
Robolectric.buildActivity(SearchActivity.class);
|
|
||||||
activityController.setup();
|
|
||||||
SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
|
|
||||||
.findFragmentById(R.id.main_content);
|
|
||||||
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider, never())
|
|
||||||
.searchRankingWarmup(any(Context.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag) {
|
|
||||||
return pair -> pair.first == tag;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,269 +0,0 @@
|
|||||||
package com.android.settings.search;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyLong;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
|
|
||||||
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.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
|
||||||
public class SearchResultAggregatorTest {
|
|
||||||
|
|
||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
|
||||||
private Context mContext;
|
|
||||||
|
|
||||||
private FakeFeatureFactory mFeatureFactory;
|
|
||||||
|
|
||||||
private SearchResultAggregator mAggregator;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private DatabaseResultLoader mStaticTask;
|
|
||||||
@Mock
|
|
||||||
private InstalledAppResultLoader mAppTask;
|
|
||||||
@Mock
|
|
||||||
private InputDeviceResultLoader mInputTask;
|
|
||||||
@Mock
|
|
||||||
private AccessibilityServiceResultLoader mMAccessibilityTask;
|
|
||||||
@Mock
|
|
||||||
private ExecutorService mService;
|
|
||||||
|
|
||||||
|
|
||||||
private String[] DB_TITLES = {"static_one", "static_two"};
|
|
||||||
private String[] INPUT_TITLES = {"input_one", "input_two"};
|
|
||||||
private String[] ACCESS_TITLES = {"access_one", "access_two"};
|
|
||||||
private String[] APP_TITLES = {"app_one", "app_two"};
|
|
||||||
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
mAggregator = spy(SearchResultAggregator.getInstance());
|
|
||||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
|
||||||
|
|
||||||
// Return mock loaders from feature provider
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getStaticSearchResultTask(any(Context.class),
|
|
||||||
anyString())).thenReturn(mStaticTask);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getInstalledAppSearchTask(any(Context.class),
|
|
||||||
anyString())).thenReturn(mAppTask);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getInputDeviceResultTask(any(Context.class),
|
|
||||||
anyString())).thenReturn(mInputTask);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getAccessibilityServiceResultTask(
|
|
||||||
any(Context.class),
|
|
||||||
anyString())).thenReturn(mMAccessibilityTask);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getExecutorService()).thenReturn(mService);
|
|
||||||
|
|
||||||
// Return fake data from the loaders
|
|
||||||
List<? extends SearchResult> dbResults = getDummyDbResults();
|
|
||||||
doReturn(dbResults).when(mStaticTask).get(anyLong(), any(TimeUnit.class));
|
|
||||||
|
|
||||||
List<? extends SearchResult> appResults = getDummyAppResults();
|
|
||||||
doReturn(appResults).when(mAppTask).get(anyLong(), any(TimeUnit.class));
|
|
||||||
|
|
||||||
List<? extends SearchResult> inputResults = getDummyInputDeviceResults();
|
|
||||||
doReturn(inputResults).when(mInputTask).get(anyLong(), any(TimeUnit.class));
|
|
||||||
|
|
||||||
List<? extends SearchResult> accessResults = getDummyAccessibilityResults();
|
|
||||||
doReturn(accessResults).when(mMAccessibilityTask).get(anyLong(), any(TimeUnit.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStaticResults_mergedProperly() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
|
|
||||||
.thenReturn(false);
|
|
||||||
|
|
||||||
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
|
|
||||||
|
|
||||||
assertThat(results).hasSize(8);
|
|
||||||
assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
|
|
||||||
assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
|
|
||||||
assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
|
|
||||||
assertThat(results.get(3).title).isEqualTo(ACCESS_TITLES[0]);
|
|
||||||
assertThat(results.get(4).title).isEqualTo(INPUT_TITLES[0]);
|
|
||||||
assertThat(results.get(5).title).isEqualTo(APP_TITLES[1]);
|
|
||||||
assertThat(results.get(6).title).isEqualTo(ACCESS_TITLES[1]);
|
|
||||||
assertThat(results.get(7).title).isEqualTo(INPUT_TITLES[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStaticRanking_staticThrowsException_dbResultsAreMissing() throws Exception {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
|
|
||||||
.thenReturn(false);
|
|
||||||
when(mStaticTask.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException());
|
|
||||||
|
|
||||||
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
|
|
||||||
|
|
||||||
assertThat(results).hasSize(6);
|
|
||||||
assertThat(results.get(0).title).isEqualTo(APP_TITLES[0]);
|
|
||||||
assertThat(results.get(1).title).isEqualTo(ACCESS_TITLES[0]);
|
|
||||||
assertThat(results.get(2).title).isEqualTo(INPUT_TITLES[0]);
|
|
||||||
assertThat(results.get(3).title).isEqualTo(APP_TITLES[1]);
|
|
||||||
assertThat(results.get(4).title).isEqualTo(ACCESS_TITLES[1]);
|
|
||||||
assertThat(results.get(5).title).isEqualTo(INPUT_TITLES[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStaticRanking_appsThrowException_appResultsAreMissing() throws Exception {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
|
|
||||||
.thenReturn(false);
|
|
||||||
when(mAppTask.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException());
|
|
||||||
|
|
||||||
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
|
|
||||||
|
|
||||||
assertThat(results).hasSize(6);
|
|
||||||
assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
|
|
||||||
assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
|
|
||||||
assertThat(results.get(2).title).isEqualTo(ACCESS_TITLES[0]);
|
|
||||||
assertThat(results.get(3).title).isEqualTo(INPUT_TITLES[0]);
|
|
||||||
assertThat(results.get(4).title).isEqualTo(ACCESS_TITLES[1]);
|
|
||||||
assertThat(results.get(5).title).isEqualTo(INPUT_TITLES[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStaticRanking_inputThrowException_inputResultsAreMissing() throws Exception {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
|
|
||||||
.thenReturn(false);
|
|
||||||
when(mInputTask.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException());
|
|
||||||
|
|
||||||
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
|
|
||||||
|
|
||||||
assertThat(results).hasSize(6);
|
|
||||||
assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
|
|
||||||
assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
|
|
||||||
assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
|
|
||||||
assertThat(results.get(3).title).isEqualTo(ACCESS_TITLES[0]);
|
|
||||||
assertThat(results.get(4).title).isEqualTo(APP_TITLES[1]);
|
|
||||||
assertThat(results.get(5).title).isEqualTo(ACCESS_TITLES[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStaticRanking_accessThrowException_accessResultsAreMissing() throws Exception {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
|
|
||||||
.thenReturn(false);
|
|
||||||
when(mMAccessibilityTask.get(anyLong(), any(TimeUnit.class))).thenThrow(
|
|
||||||
new InterruptedException());
|
|
||||||
|
|
||||||
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
|
|
||||||
|
|
||||||
assertThat(results).hasSize(6);
|
|
||||||
assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
|
|
||||||
assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
|
|
||||||
assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
|
|
||||||
assertThat(results.get(3).title).isEqualTo(INPUT_TITLES[0]);
|
|
||||||
assertThat(results.get(4).title).isEqualTo(APP_TITLES[1]);
|
|
||||||
assertThat(results.get(5).title).isEqualTo(INPUT_TITLES[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDynamicRanking_sortsWithDynamicRanking() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(
|
|
||||||
true);
|
|
||||||
|
|
||||||
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
|
|
||||||
|
|
||||||
assertThat(results).hasSize(8);
|
|
||||||
assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
|
|
||||||
assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
|
|
||||||
assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
|
|
||||||
assertThat(results.get(3).title).isEqualTo(ACCESS_TITLES[0]);
|
|
||||||
assertThat(results.get(4).title).isEqualTo(INPUT_TITLES[0]);
|
|
||||||
assertThat(results.get(5).title).isEqualTo(APP_TITLES[1]);
|
|
||||||
assertThat(results.get(6).title).isEqualTo(ACCESS_TITLES[1]);
|
|
||||||
assertThat(results.get(7).title).isEqualTo(INPUT_TITLES[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<? extends SearchResult> getDummyDbResults() {
|
|
||||||
List<SearchResult> results = new ArrayList<>();
|
|
||||||
ResultPayload payload = new ResultPayload(new Intent());
|
|
||||||
SearchResult.Builder builder = new SearchResult.Builder();
|
|
||||||
builder.setPayload(payload)
|
|
||||||
.setTitle(DB_TITLES[0])
|
|
||||||
.setRank(1)
|
|
||||||
.setStableId(Objects.hash(DB_TITLES[0], "db"));
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
builder.setTitle(DB_TITLES[1])
|
|
||||||
.setRank(2)
|
|
||||||
.setStableId(Objects.hash(DB_TITLES[1], "db"));
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<? extends SearchResult> getDummyAppResults() {
|
|
||||||
List<AppSearchResult> results = new ArrayList<>();
|
|
||||||
ResultPayload payload = new ResultPayload(new Intent());
|
|
||||||
AppSearchResult.Builder builder = new AppSearchResult.Builder();
|
|
||||||
builder.setPayload(payload)
|
|
||||||
.setTitle(APP_TITLES[0])
|
|
||||||
.setRank(1)
|
|
||||||
.setStableId(Objects.hash(APP_TITLES[0], "app"));
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
builder.setTitle(APP_TITLES[1])
|
|
||||||
.setRank(2)
|
|
||||||
.setStableId(Objects.hash(APP_TITLES[1], "app"));
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<? extends SearchResult> getDummyInputDeviceResults() {
|
|
||||||
List<SearchResult> results = new ArrayList<>();
|
|
||||||
ResultPayload payload = new ResultPayload(new Intent());
|
|
||||||
AppSearchResult.Builder builder = new AppSearchResult.Builder();
|
|
||||||
builder.setPayload(payload)
|
|
||||||
.setTitle(INPUT_TITLES[0])
|
|
||||||
.setRank(1)
|
|
||||||
.setStableId(Objects.hash(INPUT_TITLES[0], "app"));
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
builder.setTitle(INPUT_TITLES[1])
|
|
||||||
.setRank(2)
|
|
||||||
.setStableId(Objects.hash(INPUT_TITLES[1], "app"));
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<? extends SearchResult> getDummyAccessibilityResults() {
|
|
||||||
List<SearchResult> results = new ArrayList<>();
|
|
||||||
ResultPayload payload = new ResultPayload(new Intent());
|
|
||||||
AppSearchResult.Builder builder = new AppSearchResult.Builder();
|
|
||||||
builder.setPayload(payload)
|
|
||||||
.setTitle(ACCESS_TITLES[0])
|
|
||||||
.setRank(1)
|
|
||||||
.setStableId(Objects.hash(ACCESS_TITLES[0], "app"));
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
builder.setTitle(ACCESS_TITLES[1])
|
|
||||||
.setRank(2)
|
|
||||||
.setStableId(Objects.hash(ACCESS_TITLES[1], "app"));
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,127 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.search;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
|
||||||
public class SearchResultsAdapterTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private SearchFragment mFragment;
|
|
||||||
@Mock
|
|
||||||
private SearchFeatureProvider mSearchFeatureProvider;
|
|
||||||
@Mock
|
|
||||||
private Context mMockContext;
|
|
||||||
private SearchResultsAdapter mAdapter;
|
|
||||||
private Context mContext;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
mContext = Robolectric.buildActivity(Activity.class).get();
|
|
||||||
when(mFragment.getContext()).thenReturn(mMockContext);
|
|
||||||
when(mMockContext.getApplicationContext()).thenReturn(mContext);
|
|
||||||
when(mSearchFeatureProvider.smartSearchRankingTimeoutMs(any(Context.class)))
|
|
||||||
.thenReturn(300L);
|
|
||||||
mAdapter = new SearchResultsAdapter(mFragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNoResultsAdded_emptyListReturned() {
|
|
||||||
List<SearchResult> updatedResults = mAdapter.getSearchResults();
|
|
||||||
assertThat(updatedResults).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateViewHolder_returnsIntentResult() {
|
|
||||||
ViewGroup group = new FrameLayout(mContext);
|
|
||||||
SearchViewHolder view = mAdapter.onCreateViewHolder(group,
|
|
||||||
ResultPayload.PayloadType.INTENT);
|
|
||||||
assertThat(view).isInstanceOf(IntentSearchViewHolder.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateViewHolder_returnsIntentSwitchResult() {
|
|
||||||
// TODO (b/62807132) test for InlineResult
|
|
||||||
ViewGroup group = new FrameLayout(mContext);
|
|
||||||
SearchViewHolder view = mAdapter.onCreateViewHolder(group,
|
|
||||||
ResultPayload.PayloadType.INLINE_SWITCH);
|
|
||||||
assertThat(view).isInstanceOf(IntentSearchViewHolder.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPostSearchResults_addsDataAndDisplays() {
|
|
||||||
List<SearchResult> results = getDummyDbResults();
|
|
||||||
|
|
||||||
mAdapter.postSearchResults(results);
|
|
||||||
|
|
||||||
assertThat(mAdapter.getSearchResults()).containsExactlyElementsIn(results);
|
|
||||||
verify(mFragment).onSearchResultsDisplayed(anyInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SearchResult> getDummyDbResults() {
|
|
||||||
List<SearchResult> results = new ArrayList<>();
|
|
||||||
ResultPayload payload = new ResultPayload(new Intent());
|
|
||||||
SearchResult.Builder builder = new SearchResult.Builder();
|
|
||||||
builder.setPayload(payload)
|
|
||||||
.setTitle("one")
|
|
||||||
.setRank(1)
|
|
||||||
.setStableId(Objects.hash("one", "db"));
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
builder.setTitle("two")
|
|
||||||
.setRank(3)
|
|
||||||
.setStableId(Objects.hash("two", "db"));
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
builder.setTitle("three")
|
|
||||||
.setRank(6)
|
|
||||||
.setStableId(Objects.hash("three", "db"));
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,513 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyLong;
|
|
||||||
import static org.mockito.Matchers.anyString;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import com.android.settings.TestConfig;
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
|
||||||
import com.android.settings.search.DatabaseResultLoader.StaticSearchResultCallable;
|
|
||||||
import com.android.settings.search.indexing.IndexData;
|
|
||||||
import com.android.settings.testutils.DatabaseTestUtils;
|
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.robolectric.RuntimeEnvironment;
|
|
||||||
import org.robolectric.annotation.Config;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.FutureTask;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
|
||||||
public class StaticSearchResultFutureTaskTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private SiteMapManager mSiteMapManager;
|
|
||||||
@Mock
|
|
||||||
private ExecutorService mService;
|
|
||||||
private Context mContext;
|
|
||||||
|
|
||||||
SQLiteDatabase mDb;
|
|
||||||
|
|
||||||
FakeFeatureFactory mFeatureFactory;
|
|
||||||
|
|
||||||
private final String[] STATIC_TITLES = {"static one", "static two", "static three"};
|
|
||||||
private final int[] STABLE_IDS =
|
|
||||||
{"id_one".hashCode(), "id_two".hashCode(), "id_three".hashCode()};
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
mContext = RuntimeEnvironment.application;
|
|
||||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getExecutorService()).thenReturn(mService);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getSiteMapManager())
|
|
||||||
.thenReturn(mSiteMapManager);
|
|
||||||
mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
|
||||||
setUpDb();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void cleanUp() {
|
|
||||||
DatabaseTestUtils.clearDb(mContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchTitle() throws Exception {
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "title",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(2);
|
|
||||||
verify(mSiteMapManager, times(2)).buildBreadCrumb(eq(mContext), anyString(), anyString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchSummary() throws Exception {
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "summary",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchKeywords() throws Exception {
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "keywords",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchEntries() throws Exception {
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "entries",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseWord_matchesNonPrefix() throws Exception {
|
|
||||||
insertSpecialCase("Data usage");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "usage",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseDash_matchesWordNoDash() throws Exception {
|
|
||||||
insertSpecialCase("wi-fi calling");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "wifi",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseDash_matchesWordWithDash() throws Exception {
|
|
||||||
insertSpecialCase("priorités seulment");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "priorités",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseDash_matchesWordWithoutDash() throws Exception {
|
|
||||||
insertSpecialCase("priorités seulment");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "priorites",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseDash_matchesEntireQueryWithoutDash() throws Exception {
|
|
||||||
insertSpecialCase("wi-fi calling");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "wifi calling",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCasePrefix_matchesPrefixOfEntry() throws Exception {
|
|
||||||
insertSpecialCase("Photos");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "pho",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCasePrefix_DoesNotMatchNonPrefixSubstring() throws Exception {
|
|
||||||
insertSpecialCase("Photos");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "hot",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseMultiWordPrefix_matchesPrefixOfEntry() throws Exception {
|
|
||||||
insertSpecialCase("Apps Notifications");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "Apps",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseMultiWordPrefix_matchesSecondWordPrefixOfEntry() throws Exception {
|
|
||||||
insertSpecialCase("Apps Notifications");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "Not",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfFirstEntry()
|
|
||||||
throws Exception {
|
|
||||||
insertSpecialCase("Apps Notifications");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "pp",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfSecondEntry()
|
|
||||||
throws Exception {
|
|
||||||
insertSpecialCase("Apps Notifications");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "tion",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfEntry() throws
|
|
||||||
Exception {
|
|
||||||
insertSpecialCase("Apps & Notifications");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "App",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfSecondEntry()
|
|
||||||
throws Exception {
|
|
||||||
insertSpecialCase("Apps & Notifications");
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "No",
|
|
||||||
mSiteMapManager);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testResultMatchedByMultipleQueries_duplicatesRemoved() throws Exception {
|
|
||||||
String key = "durr";
|
|
||||||
insertSameValueAllFieldsCase(key);
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, key, null);
|
|
||||||
|
|
||||||
assertThat(loader.call()).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialCaseTwoWords_multipleResults() throws Exception {
|
|
||||||
final String caseOne = "Apple pear";
|
|
||||||
final String caseTwo = "Banana apple";
|
|
||||||
insertSpecialCase(caseOne);
|
|
||||||
insertSpecialCase(caseTwo);
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "App", null);
|
|
||||||
|
|
||||||
List<? extends SearchResult> results = loader.call();
|
|
||||||
|
|
||||||
Set<String> actualTitles = new HashSet<>();
|
|
||||||
for (SearchResult result : results) {
|
|
||||||
actualTitles.add(result.title.toString());
|
|
||||||
}
|
|
||||||
assertThat(actualTitles).containsAllOf(caseOne, caseTwo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetRankingScoreByStableId_sortedDynamically() throws Exception {
|
|
||||||
FutureTask<List<Pair<String, Float>>> task = mock(FutureTask.class);
|
|
||||||
when(task.get(anyLong(), any(TimeUnit.class))).thenReturn(getDummyRankingScores());
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getRankerTask(any(Context.class),
|
|
||||||
anyString())).thenReturn(task);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(
|
|
||||||
true);
|
|
||||||
|
|
||||||
insertSpecialCase(STATIC_TITLES[0], STABLE_IDS[0]);
|
|
||||||
insertSpecialCase(STATIC_TITLES[1], STABLE_IDS[1]);
|
|
||||||
insertSpecialCase(STATIC_TITLES[2], STABLE_IDS[2]);
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "Static",
|
|
||||||
null);
|
|
||||||
|
|
||||||
List<? extends SearchResult> results = loader.call();
|
|
||||||
|
|
||||||
assertThat(results.get(0).title).isEqualTo(STATIC_TITLES[2]);
|
|
||||||
assertThat(results.get(1).title).isEqualTo(STATIC_TITLES[0]);
|
|
||||||
assertThat(results.get(2).title).isEqualTo(STATIC_TITLES[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetRankingScoreByStableId_scoresTimeout_sortedStatically() throws Exception {
|
|
||||||
Callable<List<Pair<String, Float>>> callable = mock(Callable.class);
|
|
||||||
when(callable.call()).thenThrow(new TimeoutException());
|
|
||||||
FutureTask<List<Pair<String, Float>>> task = new FutureTask<>(callable);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(
|
|
||||||
true);
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getRankerTask(any(Context.class),
|
|
||||||
anyString())).thenReturn(task);
|
|
||||||
insertSpecialCase("title", STABLE_IDS[0]);
|
|
||||||
|
|
||||||
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "title", null);
|
|
||||||
|
|
||||||
List<? extends SearchResult> results = loader.call();
|
|
||||||
assertThat(results.get(0).title).isEqualTo("title");
|
|
||||||
assertThat(results.get(1).title).isEqualTo("alpha_title");
|
|
||||||
assertThat(results.get(2).title).isEqualTo("bravo_title");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insertSpecialCase(String specialCase) {
|
|
||||||
insertSpecialCase(specialCase, specialCase.hashCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insertSpecialCase(String specialCase, int docId) {
|
|
||||||
String normalized = IndexData.normalizeHyphen(specialCase);
|
|
||||||
normalized = IndexData.normalizeString(normalized);
|
|
||||||
final ResultPayload payload = new ResultPayload(new Intent());
|
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DOCID, docId);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, specialCase);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, normalized);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME,
|
|
||||||
"com.android.settings.gestures.GestureSettings");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, normalized.hashCode());
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, ResultPayloadUtils.marshall(payload));
|
|
||||||
|
|
||||||
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUpDb() {
|
|
||||||
final byte[] payload = ResultPayloadUtils.marshall(new ResultPayload(new Intent()));
|
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DOCID, 1);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, "alpha_title");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, "alpha title");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, "alpha_summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, "alpha summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, "alpha_summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, "alpha summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, "alpha entries");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, "alpha keywords");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME,
|
|
||||||
"com.android.settings.gestures.GestureSettings");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power_0");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, payload);
|
|
||||||
|
|
||||||
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
|
|
||||||
|
|
||||||
values = new ContentValues();
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DOCID, 2);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, "bravo_title");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, "bravo title");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, "bravo_summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, "bravo summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, "bravo_summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, "bravo summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, "bravo entries");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, "bravo keywords");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME,
|
|
||||||
"com.android.settings.gestures.GestureSettings");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power_1");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, payload);
|
|
||||||
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
|
|
||||||
|
|
||||||
values = new ContentValues();
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DOCID, 3);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, "charlie_title");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, "charlie title");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, "charlie_summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, "charlie summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, "charlie_summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, "charlie summary");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, "charlie entries");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, "charlie keywords");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME,
|
|
||||||
"com.android.settings.gestures.GestureSettings");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.ENABLED, false);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power_2");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, payload);
|
|
||||||
|
|
||||||
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insertSameValueAllFieldsCase(String key) {
|
|
||||||
final ResultPayload payload = new ResultPayload(new Intent());
|
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DOCID, key.hashCode());
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, key);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, key);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, key);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, key);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, key);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, key);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, key);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, key);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, key);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, key);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, key);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, key.hashCode());
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
|
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, ResultPayloadUtils.marshall(payload));
|
|
||||||
|
|
||||||
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<? extends SearchResult> getDummyDbResults() {
|
|
||||||
List<SearchResult> results = new ArrayList<>();
|
|
||||||
ResultPayload payload = new ResultPayload(new Intent());
|
|
||||||
SearchResult.Builder builder = new SearchResult.Builder();
|
|
||||||
builder.setPayload(payload)
|
|
||||||
.setTitle(STATIC_TITLES[0])
|
|
||||||
.setStableId(STABLE_IDS[0]);
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
builder.setTitle(STATIC_TITLES[1])
|
|
||||||
.setStableId(STABLE_IDS[1]);
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
builder.setTitle(STATIC_TITLES[2])
|
|
||||||
.setStableId(STABLE_IDS[2]);
|
|
||||||
results.add(builder.build());
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Pair<String, Float>> getDummyRankingScores() {
|
|
||||||
List<? extends SearchResult> results = getDummyDbResults();
|
|
||||||
List<Pair<String, Float>> scores = new ArrayList<>();
|
|
||||||
scores.add(new Pair<>(Long.toString(results.get(2).stableId), 0.9f)); // static_three
|
|
||||||
scores.add(new Pair<>(Long.toString(results.get(0).stableId), 0.8f)); // static_one
|
|
||||||
scores.add(new Pair<>(Long.toString(results.get(1).stableId), 0.2f)); // static_two
|
|
||||||
return scores;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -16,20 +16,16 @@
|
|||||||
|
|
||||||
package com.android.settings.search.actionbar;
|
package com.android.settings.search.actionbar;
|
||||||
|
|
||||||
import static org.mockito.Matchers.nullable;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
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.R;
|
||||||
import com.android.settings.TestConfig;
|
import com.android.settings.TestConfig;
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
||||||
import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
|
import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
|
||||||
|
|
||||||
@@ -47,30 +43,15 @@ public class SearchMenuControllerTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private Menu mMenu;
|
private Menu mMenu;
|
||||||
private TestFragment mHost;
|
private TestFragment mHost;
|
||||||
private FakeFeatureFactory mFeatureFactory;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mHost = new TestFragment();
|
mHost = new TestFragment();
|
||||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void init_searchV2Disabled_shouldNotAddMenu() {
|
public void init_shouldAddMenu() {
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSearchV2Enabled(nullable(Context.class)))
|
|
||||||
.thenReturn(false);
|
|
||||||
|
|
||||||
SearchMenuController.init(mHost);
|
|
||||||
mHost.getLifecycle().onCreateOptionsMenu(mMenu, null /* inflater */);
|
|
||||||
|
|
||||||
verifyZeroInteractions(mMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void init_searchV2Enabled_shouldAddMenu() {
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSearchV2Enabled(nullable(Context.class)))
|
|
||||||
.thenReturn(true);
|
|
||||||
when(mMenu.add(Menu.NONE, Menu.NONE, 0 /* order */, R.string.search_menu))
|
when(mMenu.add(Menu.NONE, Menu.NONE, 0 /* order */, R.string.search_menu))
|
||||||
.thenReturn(mock(MenuItem.class));
|
.thenReturn(mock(MenuItem.class));
|
||||||
|
|
||||||
|
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.support.test.InstrumentationRegistry;
|
|
||||||
import android.support.test.filters.SmallTest;
|
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
|
||||||
|
|
||||||
import com.android.settings.Settings;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
@SmallTest
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class SearchActivityTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldHaveParentActivity() {
|
|
||||||
final Context context = InstrumentationRegistry.getTargetContext();
|
|
||||||
final PackageManager packageManager = context.getPackageManager();
|
|
||||||
final Intent intent = new Intent(context, SearchActivity.class);
|
|
||||||
final List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(
|
|
||||||
intent, PackageManager.GET_META_DATA);
|
|
||||||
|
|
||||||
assertThat(resolveInfos).isNotEmpty();
|
|
||||||
assertThat(resolveInfos.get(0).activityInfo.parentActivityName)
|
|
||||||
.isEqualTo(Settings.class.getName());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 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.search;
|
|
||||||
|
|
||||||
import static android.support.test.espresso.Espresso.onView;
|
|
||||||
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.hasFocus;
|
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
|
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.core.AllOf.allOf;
|
|
||||||
|
|
||||||
import android.support.test.filters.SmallTest;
|
|
||||||
import android.support.test.rule.ActivityTestRule;
|
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
|
||||||
import android.widget.SearchView;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
@SmallTest
|
|
||||||
public class SearchFragmentEspressoTest {
|
|
||||||
@Rule
|
|
||||||
public ActivityTestRule<SearchActivity> mActivityRule =
|
|
||||||
new ActivityTestRule<>(SearchActivity.class, true, true);
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_OpenKeyboardOnSearchLaunch() {
|
|
||||||
onView(allOf(hasFocus(), withId(R.id.search_view)))
|
|
||||||
.check(matches(withClassName(containsString(SearchView.class.getName()))));
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user