Build a sitemap manager to keep track of breadcrumbs

- Have a new db to track parent-child page relation at index time.
- Make a registry class to track (in IA) which page host what type of
  sub pages.
- Make a manager class that queries the db as well as IA to compute
  breadcrumbs

Fix: 32936784
Test: RunSettingsRoboTest

Change-Id: I5f1583fae772c3d477d2ad186e111b79cc3e41aa
This commit is contained in:
Fan Zhang
2017-01-21 14:53:01 -08:00
parent 03cd212f17
commit a96b11f65d
17 changed files with 692 additions and 63 deletions

View File

@@ -0,0 +1,93 @@
/*
* 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.dashboard;
import android.util.ArrayMap;
import com.android.settings.DevelopmentSettings;
import com.android.settings.DisplaySettings;
import com.android.settings.SecuritySettings;
import com.android.settings.accounts.UserAndAccountDashboardFragment;
import com.android.settings.applications.AdvancedAppSettings;
import com.android.settings.applications.AppAndNotificationDashboardFragment;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.deviceinfo.StorageDashboardFragment;
import com.android.settings.inputmethod.InputAndGestureSettings;
import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
import com.android.settings.network.NetworkDashboardFragment;
import com.android.settings.notification.SoundSettings;
import com.android.settings.system.SystemDashboardFragment;
import com.android.settingslib.drawer.CategoryKey;
import java.util.Map;
/**
* A registry to keep track of which page hosts which category.
* TODO: Remove DashboardFragment#getCategoryKey() and just use this registry instead.
*/
public class DashboardFragmentRegistry {
/**
* Map from parent fragment to category key. The parent fragment hosts child with
* category_key.
*/
public static final Map<String, String> PARENT_TO_CATEGORY_KEY_MAP;
/**
* Map from category_key to parent. This is a helper to look up which fragment hosts the
* category_key.
*/
public static final Map<String, String> CATEGORY_KEY_TO_PARENT_MAP;
static {
PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>();
PARENT_TO_CATEGORY_KEY_MAP.put(
NetworkDashboardFragment.class.getName(), CategoryKey.CATEGORY_NETWORK);
PARENT_TO_CATEGORY_KEY_MAP.put(ConnectedDeviceDashboardFragment.class.getName(),
CategoryKey.CATEGORY_DEVICE);
PARENT_TO_CATEGORY_KEY_MAP.put(AppAndNotificationDashboardFragment.class.getName(),
CategoryKey.CATEGORY_APPS);
PARENT_TO_CATEGORY_KEY_MAP.put(AdvancedAppSettings.class.getName(),
CategoryKey.CATEGORY_APPS_DEFAULT);
PARENT_TO_CATEGORY_KEY_MAP.put(DisplaySettings.class.getName(),
CategoryKey.CATEGORY_DISPLAY);
PARENT_TO_CATEGORY_KEY_MAP.put(SoundSettings.class.getName(),
CategoryKey.CATEGORY_SOUND);
PARENT_TO_CATEGORY_KEY_MAP.put(StorageDashboardFragment.class.getName(),
CategoryKey.CATEGORY_STORAGE);
PARENT_TO_CATEGORY_KEY_MAP.put(SecuritySettings.class.getName(),
CategoryKey.CATEGORY_SECURITY);
PARENT_TO_CATEGORY_KEY_MAP.put(UserAndAccountDashboardFragment.class.getName(),
CategoryKey.CATEGORY_ACCOUNT);
PARENT_TO_CATEGORY_KEY_MAP.put(UserAndAccountDashboardFragment.class.getName(),
CategoryKey.CATEGORY_ACCOUNT);
PARENT_TO_CATEGORY_KEY_MAP.put(
SystemDashboardFragment.class.getName(), CategoryKey.CATEGORY_SYSTEM);
PARENT_TO_CATEGORY_KEY_MAP.put(
InputAndGestureSettings.class.getName(), CategoryKey.CATEGORY_SYSTEM_INPUT);
PARENT_TO_CATEGORY_KEY_MAP.put(InputMethodAndLanguageSettings.class.getName(),
CategoryKey.CATEGORY_SYSTEM_LANGUAGE);
PARENT_TO_CATEGORY_KEY_MAP.put(DevelopmentSettings.class.getName(),
CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
CATEGORY_KEY_TO_PARENT_MAP = new ArrayMap<>(PARENT_TO_CATEGORY_KEY_MAP.size());
for (Map.Entry<String, String> parentToKey : PARENT_TO_CATEGORY_KEY_MAP.entrySet()) {
CATEGORY_KEY_TO_PARENT_MAP.put(parentToKey.getValue(), parentToKey.getKey());
}
}
}

View File

@@ -0,0 +1,212 @@
/*
* 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.dashboard;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
import com.android.settings.SettingsActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.IndexDatabaseHelper;
import com.android.settings.search.IndexDatabaseHelper.IndexColumns;
import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.android.settings.dashboard.DashboardFragmentRegistry.CATEGORY_KEY_TO_PARENT_MAP;
/**
* A manager class that maintains a "site map" and look up breadcrumb for a certain page on demand.
* <p/>
* The methods on this class can only be called on a background thread.
*/
public class SiteMapManager {
private static final String TAG = "SiteMapManager";
private static final boolean DEBUG_TIMING = false;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public static final String[] SITE_MAP_COLUMNS = {
SiteMapColumns.PARENT_CLASS,
SiteMapColumns.PARENT_TITLE,
SiteMapColumns.CHILD_CLASS,
SiteMapColumns.CHILD_TITLE
};
private static final String[] CLASS_TO_SCREEN_TITLE_COLUMNS = {
IndexColumns.CLASS_NAME,
IndexColumns.SCREEN_TITLE,
};
private final List<SiteMapPair> mPairs = new ArrayList<>();
private boolean mInitialized;
/**
* Given a fragment class name and its screen title, build a breadcrumb from Settings root to
* this screen.
* <p/>
* Not all screens have a full breadcrumb path leading up to root, it's because either some
* page in the breadcrumb path is not indexed, or it's only reachable via search.
*/
@WorkerThread
public synchronized List<String> buildBreadCrumb(Context context, String clazz,
String screenTitle) {
init(context);
final long startTime = System.currentTimeMillis();
final List<String> breadcrumbs = new ArrayList<>();
if (!mInitialized) {
Log.w(TAG, "SiteMap is not initialized yet, skipping");
return breadcrumbs;
}
breadcrumbs.add(screenTitle);
String currentClass = clazz;
String currentTitle = screenTitle;
// Look up current page's parent, if found add it to breadcrumb string list, and repeat.
while (true) {
final SiteMapPair pair = lookUpParent(currentClass, currentTitle);
if (pair == null) {
if (DEBUG_TIMING) {
Log.d(TAG, "BreadCrumb timing: " + (System.currentTimeMillis() - startTime));
}
return breadcrumbs;
}
breadcrumbs.add(0, pair.parentTitle);
currentClass = pair.parentClass;
currentTitle = pair.parentTitle;
}
}
/**
* Initialize a list of {@link SiteMapPair}s. Each pair knows about a single parent-child
* page relationship.
*
* We get the knowledge of such mPairs from 2 sources:
* 1. Static indexing time: we know which page(s) a parent can open by parsing its pref xml.
* 2. IA: We know from {@link DashboardFeatureProvider} which page can be dynamically
* injected to where.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@WorkerThread
synchronized void init(Context context) {
if (mInitialized) {
// Make sure only init once.
return;
}
final long startTime = System.currentTimeMillis();
// First load site map from static index table.
final Context appContext = context.getApplicationContext();
final SQLiteDatabase db = IndexDatabaseHelper.getInstance(appContext).getReadableDatabase();
Cursor sitemap = db.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS, null,
null, null, null, null);
while (sitemap.moveToNext()) {
final SiteMapPair pair = new SiteMapPair(
sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_CLASS)),
sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_TITLE)),
sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_CLASS)),
sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_TITLE)));
mPairs.add(pair);
}
sitemap.close();
// Then prepare a local map that contains class name -> screen title mapping. This is needed
// to figure out the display name for any fragment if it's injected dynamically through IA.
final Map<String, String> classToTitleMap = new HashMap<>();
final Cursor titleQuery = db.query(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX,
CLASS_TO_SCREEN_TITLE_COLUMNS, null, null, null, null, null);
while (titleQuery.moveToNext()) {
classToTitleMap.put(
titleQuery.getString(titleQuery.getColumnIndex(IndexColumns.CLASS_NAME)),
titleQuery.getString(titleQuery.getColumnIndex(IndexColumns.SCREEN_TITLE)));
}
titleQuery.close();
// Loop through all IA categories and pages and build additional SiteMapPairs
List<DashboardCategory> categories = FeatureFactory.getFactory(context)
.getDashboardFeatureProvider(context).getAllCategories();
for (DashboardCategory category : categories) {
// Find the category key first.
final String parentClass = CATEGORY_KEY_TO_PARENT_MAP.get(category.key);
if (parentClass == null) {
continue;
}
// Use the key to look up parent (which page hosts this key)
final String parentName = classToTitleMap.get(parentClass);
if (parentName == null) {
continue;
}
// Build parent-child mPairs for all children listed under this key.
for (Tile tile : category.tiles) {
final String childTitle = tile.title.toString();
String childClass = null;
if (tile.metaData != null) {
childClass = tile.metaData.getString(
SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
}
if (childClass == null) {
continue;
}
mPairs.add(new SiteMapPair(parentClass, parentName, childClass, childTitle));
}
}
// Done.
mInitialized = true;
if (DEBUG_TIMING) {
Log.d(TAG, "Init timing: " + (System.currentTimeMillis() - startTime));
}
}
@WorkerThread
private SiteMapPair lookUpParent(String clazz, String title) {
for (SiteMapPair pair : mPairs) {
if (TextUtils.equals(pair.childClass, clazz)
&& TextUtils.equals(title, pair.childTitle)) {
return pair;
}
}
return null;
}
/**
* Data model for a parent-child page pair.
*/
private static class SiteMapPair {
public final String parentClass;
public final String parentTitle;
public final String childClass;
public final String childTitle;
public SiteMapPair(String parentClass, String parentTitle, String childClass,
String childTitle) {
this.parentClass = parentClass;
this.parentTitle = parentTitle;
this.childClass = childClass;
this.childTitle = childTitle;
}
}
}

View File

@@ -28,12 +28,13 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "IndexDatabaseHelper"; private static final String TAG = "IndexDatabaseHelper";
private static final String DATABASE_NAME = "search_index.db"; private static final String DATABASE_NAME = "search_index.db";
private static final int DATABASE_VERSION = 116; private static final int DATABASE_VERSION = 117;
private static final String INDEX = "index"; private static final String INDEX = "index";
public interface Tables { public interface Tables {
String TABLE_PREFS_INDEX = "prefs_index"; String TABLE_PREFS_INDEX = "prefs_index";
String TABLE_SITE_MAP = "site_map";
String TABLE_META_INDEX = "meta_index"; String TABLE_META_INDEX = "meta_index";
String TABLE_SAVED_QUERIES = "saved_queries"; String TABLE_SAVED_QUERIES = "saved_queries";
} }
@@ -72,6 +73,14 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper {
String TIME_STAMP = "timestamp"; String TIME_STAMP = "timestamp";
} }
public interface SiteMapColumns {
String DOCID = "docid";
String PARENT_CLASS = "parent_class";
String CHILD_CLASS = "child_class";
String PARENT_TITLE = "parent_title";
String CHILD_TITLE = "child_title";
}
private static final String CREATE_INDEX_TABLE = private static final String CREATE_INDEX_TABLE =
"CREATE VIRTUAL TABLE " + Tables.TABLE_PREFS_INDEX + " USING fts4" + "CREATE VIRTUAL TABLE " + Tables.TABLE_PREFS_INDEX + " USING fts4" +
"(" + "(" +
@@ -132,6 +141,17 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper {
SavedQueriesColumns.TIME_STAMP + " INTEGER" + SavedQueriesColumns.TIME_STAMP + " INTEGER" +
")"; ")";
private static final String CREATE_SITE_MAP_TABLE =
"CREATE VIRTUAL TABLE " + Tables.TABLE_SITE_MAP + " USING fts4" +
"(" +
SiteMapColumns.PARENT_CLASS +
", " +
SiteMapColumns.CHILD_CLASS +
", " +
SiteMapColumns.PARENT_TITLE +
", " +
SiteMapColumns.CHILD_TITLE +
")";
private static final String INSERT_BUILD_VERSION = private static final String INSERT_BUILD_VERSION =
"INSERT INTO " + Tables.TABLE_META_INDEX + "INSERT INTO " + Tables.TABLE_META_INDEX +
" VALUES ('" + Build.VERSION.INCREMENTAL + "');"; " VALUES ('" + Build.VERSION.INCREMENTAL + "');";
@@ -164,6 +184,7 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(CREATE_INDEX_TABLE); db.execSQL(CREATE_INDEX_TABLE);
db.execSQL(CREATE_META_TABLE); db.execSQL(CREATE_META_TABLE);
db.execSQL(CREATE_SAVED_QUERIES_TABLE); db.execSQL(CREATE_SAVED_QUERIES_TABLE);
db.execSQL(CREATE_SITE_MAP_TABLE);
db.execSQL(INSERT_BUILD_VERSION); db.execSQL(INSERT_BUILD_VERSION);
Log.i(TAG, "Bootstrapped database"); Log.i(TAG, "Bootstrapped database");
} }
@@ -241,5 +262,6 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX); db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX);
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX); db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX);
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES); db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES);
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SITE_MAP);
} }
} }

View File

@@ -28,8 +28,10 @@ import android.os.BadParcelableException;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.dashboard.SiteMapManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -39,18 +41,20 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SCREEN_TITLE;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_TITLE;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_CLASS_NAME; import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_CLASS_NAME;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ICON; import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ICON;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION; import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE; import static com.android.settings.search2.DatabaseResultLoader
.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS;
import static com.android.settings.search2.DatabaseResultLoader
.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_KEY; import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_KEY;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD_TYPE;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD; import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD_TYPE;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SCREEN_TITLE;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_TITLE;
/** /**
* Controller to Build search results from {@link Cursor} Objects. * Controller to Build search results from {@link Cursor} Objects.
@@ -78,7 +82,8 @@ class CursorToSearchResultConverter {
mQueryText = queryText; mQueryText = queryText;
} }
public List<SearchResult> convertCursor(Cursor cursorResults, int baseRank) { public List<SearchResult> convertCursor(SiteMapManager sitemapManager,
Cursor cursorResults, int baseRank) {
if (cursorResults == null) { if (cursorResults == null) {
return null; return null;
} }
@@ -86,8 +91,8 @@ class CursorToSearchResultConverter {
final List<SearchResult> results = new ArrayList<>(); final List<SearchResult> results = new ArrayList<>();
while (cursorResults.moveToNext()) { while (cursorResults.moveToNext()) {
SearchResult result = buildSingleSearchResultFromCursor(contextMap, cursorResults, SearchResult result = buildSingleSearchResultFromCursor(sitemapManager,
baseRank); contextMap, cursorResults, baseRank);
if (result != null) { if (result != null) {
results.add(result); results.add(result);
} }
@@ -96,8 +101,8 @@ class CursorToSearchResultConverter {
return results; return results;
} }
private SearchResult buildSingleSearchResultFromCursor(Map<String, Context> contextMap, private SearchResult buildSingleSearchResultFromCursor(SiteMapManager sitemapManager,
Cursor cursor, int baseRank) { Map<String, Context> contextMap, Cursor cursor, int baseRank) {
final String docId = cursor.getString(COLUMN_INDEX_ID); final String docId = cursor.getString(COLUMN_INDEX_ID);
/* Make sure that this result has not yet been added as a result. Checking the docID /* Make sure that this result has not yet been added as a result. Checking the docID
covers the case of multiple queries matching the same row, but we need to also to check covers the case of multiple queries matching the same row, but we need to also to check
@@ -128,7 +133,7 @@ class CursorToSearchResultConverter {
return null; return null;
} }
final List<String> breadcrumbs = getBreadcrumbs(cursor); final List<String> breadcrumbs = getBreadcrumbs(sitemapManager, cursor);
final int rank = getRank(breadcrumbs, baseRank); final int rank = getRank(breadcrumbs, baseRank);
final SearchResult.Builder builder = new SearchResult.Builder(); final SearchResult.Builder builder = new SearchResult.Builder();
@@ -210,12 +215,11 @@ class CursorToSearchResultConverter {
return null; return null;
} }
private List<String> getBreadcrumbs(Cursor cursor) { private List<String> getBreadcrumbs(SiteMapManager siteMapManager, Cursor cursor) {
final List<String> breadcrumbs = new ArrayList<>();
final String screenTitle = cursor.getString(COLUMN_INDEX_SCREEN_TITLE); final String screenTitle = cursor.getString(COLUMN_INDEX_SCREEN_TITLE);
if (!TextUtils.isEmpty(screenTitle)) { final String screenClass = cursor.getString(COLUMN_INDEX_CLASS_NAME);
breadcrumbs.add(screenTitle); final List<String> breadcrumbs = siteMapManager.buildBreadCrumb(mContext, screenClass,
} screenTitle);
return breadcrumbs; return breadcrumbs;
} }

View File

@@ -34,7 +34,6 @@ import android.provider.SearchIndexableData;
import android.provider.SearchIndexableResource; import android.provider.SearchIndexableResource;
import android.provider.SearchIndexablesContract; import android.provider.SearchIndexablesContract;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.util.Xml; import android.util.Xml;
@@ -582,12 +581,13 @@ public class DatabaseIndexingManager {
String title; String title;
String summary; String summary;
String keywords; String keywords;
String childFragment;
ResultPayload payload; ResultPayload payload;
ArrayMap<String, PreferenceController> controllerUriMap = null; Map<String, PreferenceController> controllerUriMap = null;
if (fragmentName != null) { if (fragmentName != null) {
controllerUriMap = (ArrayMap) DatabaseIndexingUtils controllerUriMap = DatabaseIndexingUtils
.getPreferenceControllerUriMap(fragmentName, context); .getPreferenceControllerUriMap(fragmentName, context);
} }
@@ -655,8 +655,10 @@ public class DatabaseIndexingManager {
} }
payload = DatabaseIndexingUtils.getPayloadFromUriMap(controllerUriMap, key); payload = DatabaseIndexingUtils.getPayloadFromUriMap(controllerUriMap, key);
childFragment = XmlParserUtils.getDataChildFragment(context, attrs);
builder.setEntries(entries) builder.setEntries(entries)
.setChildClassName(childFragment)
.setPayload(payload); .setPayload(payload);
// Insert rows for the child nodes of PreferenceScreen // Insert rows for the child nodes of PreferenceScreen
@@ -811,6 +813,18 @@ public class DatabaseIndexingManager {
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, row.payload); values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, row.payload);
database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values); database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
if (!TextUtils.isEmpty(row.className) && !TextUtils.isEmpty(row.childClassName)) {
ContentValues siteMapPair = new ContentValues();
final int pairDocId = Objects.hash(row.className, row.childClassName);
siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.DOCID, pairDocId);
siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.PARENT_CLASS, row.className);
siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.PARENT_TITLE, row.screenTitle);
siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.CHILD_CLASS, row.childClassName);
siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.CHILD_TITLE, row.updatedTitle);
database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, null, siteMapPair);
}
} }
/** /**
@@ -950,6 +964,7 @@ public class DatabaseIndexingManager {
public final String normalizedSummaryOff; public final String normalizedSummaryOff;
public final String entries; public final String entries;
public final String className; public final String className;
public final String childClassName;
public final String screenTitle; public final String screenTitle;
public final int iconResId; public final int iconResId;
public final int rank; public final int rank;
@@ -973,6 +988,7 @@ public class DatabaseIndexingManager {
normalizedSummaryOff = builder.mNormalizedSummaryOff; normalizedSummaryOff = builder.mNormalizedSummaryOff;
entries = builder.mEntries; entries = builder.mEntries;
className = builder.mClassName; className = builder.mClassName;
childClassName = builder.mChildClassName;
screenTitle = builder.mScreenTitle; screenTitle = builder.mScreenTitle;
iconResId = builder.mIconResId; iconResId = builder.mIconResId;
rank = builder.mRank; rank = builder.mRank;
@@ -1008,6 +1024,7 @@ public class DatabaseIndexingManager {
private String mNormalizedSummaryOff; private String mNormalizedSummaryOff;
private String mEntries; private String mEntries;
private String mClassName; private String mClassName;
private String mChildClassName;
private String mScreenTitle; private String mScreenTitle;
private int mIconResId; private int mIconResId;
private int mRank; private int mRank;
@@ -1067,6 +1084,11 @@ public class DatabaseIndexingManager {
return this; return this;
} }
public Builder setChildClassName(String childClassName) {
mChildClassName = childClassName;
return this;
}
public Builder setScreenTitle(String screenTitle) { public Builder setScreenTitle(String screenTitle) {
mScreenTitle = screenTitle; mScreenTitle = screenTitle;
return this; return this;

View File

@@ -105,7 +105,7 @@ public class DatabaseIndexingUtils {
* @return The Payload from the {@link PreferenceController} specified by the key, if it exists. * @return The Payload from the {@link PreferenceController} specified by the key, if it exists.
* Otherwise null. * Otherwise null.
*/ */
public static ResultPayload getPayloadFromUriMap(ArrayMap<String, PreferenceController> uriMap, public static ResultPayload getPayloadFromUriMap(Map<String, PreferenceController> uriMap,
String key) { String key) {
if (uriMap == null) { if (uriMap == null) {
return null; return null;

View File

@@ -20,6 +20,8 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.IndexDatabaseHelper; import com.android.settings.search.IndexDatabaseHelper;
import com.android.settings.utils.AsyncLoader; import com.android.settings.utils.AsyncLoader;
@@ -35,11 +37,6 @@ import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_PREFS
*/ */
public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> { public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
private static final String LOG = "DatabaseResultLoader"; private static final String LOG = "DatabaseResultLoader";
private final String mQueryText;
protected final SQLiteDatabase mDatabase;
private final CursorToSearchResultConverter mConverter;
/* These indices are used to match the columns of the this loader's SELECT statement. /* These indices are used to match the columns of the this loader's SELECT statement.
These are not necessarily the same order nor similar coverage as the schema defined in These are not necessarily the same order nor similar coverage as the schema defined in
@@ -99,8 +96,15 @@ public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
*/ */
private static final int[] BASE_RANKS = {1, 4, 7}; private static final int[] BASE_RANKS = {1, 4, 7};
private final String mQueryText;
private final SQLiteDatabase mDatabase;
private final CursorToSearchResultConverter mConverter;
private final SiteMapManager mSiteMapManager;
public DatabaseResultLoader(Context context, String queryText) { public DatabaseResultLoader(Context context, String queryText) {
super(context); super(context);
mSiteMapManager = FeatureFactory.getFactory(context)
.getSearchFeatureProvider().getSiteMapManager();
mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase(); mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
mQueryText = cleanQuery(queryText); mQueryText = cleanQuery(queryText);
mConverter = new CursorToSearchResultConverter(context, mQueryText); mConverter = new CursorToSearchResultConverter(context, mQueryText);
@@ -144,7 +148,7 @@ public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
final Cursor resultCursor = mDatabase.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause, final Cursor resultCursor = mDatabase.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause,
selection, null, null, null); selection, null, null, null);
return mConverter.convertCursor(resultCursor, baseRank); return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
} }
@Override @Override
@@ -155,6 +159,7 @@ public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
/** /**
* A generic method to make the query suitable for searching the database. * A generic method to make the query suitable for searching the database.
*
* @return the cleaned query string * @return the cleaned query string
*/ */
private static String cleanQuery(String query) { private static String cleanQuery(String query) {

View File

@@ -29,7 +29,10 @@ import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.applications.ManageApplications;
import com.android.settings.applications.PackageManagerWrapper; import com.android.settings.applications.PackageManagerWrapper;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.utils.AsyncLoader; import com.android.settings.utils.AsyncLoader;
import java.util.ArrayList; import java.util.ArrayList;
@@ -46,7 +49,8 @@ public class InstalledAppResultLoader extends AsyncLoader<List<SearchResult>> {
private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN) private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER); .addCategory(Intent.CATEGORY_LAUNCHER);
private final List<String> mBreadcrumb; private List<String> mBreadcrumb;
private SiteMapManager mSiteMapManager;
private final String mQuery; private final String mQuery;
private final UserManager mUserManager; private final UserManager mUserManager;
private final PackageManagerWrapper mPackageManager; private final PackageManagerWrapper mPackageManager;
@@ -55,9 +59,8 @@ public class InstalledAppResultLoader extends AsyncLoader<List<SearchResult>> {
public InstalledAppResultLoader(Context context, PackageManagerWrapper pmWrapper, public InstalledAppResultLoader(Context context, PackageManagerWrapper pmWrapper,
String query) { String query) {
super(context); super(context);
mBreadcrumb = new ArrayList<>(); mSiteMapManager = FeatureFactory.getFactory(context)
mBreadcrumb.add(context.getString(R.string.app_and_notification_dashboard_title)); .getSearchFeatureProvider().getSiteMapManager();
mBreadcrumb.add(context.getString(R.string.applications_settings));
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mPackageManager = pmWrapper; mPackageManager = pmWrapper;
mQuery = query; mQuery = query;
@@ -92,7 +95,7 @@ public class InstalledAppResultLoader extends AsyncLoader<List<SearchResult>> {
builder.addIcon(info.loadIcon(pm)) builder.addIcon(info.loadIcon(pm))
.addTitle(info.loadLabel(pm)) .addTitle(info.loadLabel(pm))
.addRank(wordDiff) .addRank(wordDiff)
.addBreadcrumbs(mBreadcrumb) .addBreadcrumbs(getBreadCrumb())
.addPayload(new IntentPayload(intent)); .addPayload(new IntentPayload(intent));
results.add(builder.build()); results.add(builder.build());
} }
@@ -162,4 +165,14 @@ public class InstalledAppResultLoader extends AsyncLoader<List<SearchResult>> {
// to infinity. // to infinity.
return valueText.length - queryTokens.length; return valueText.length - queryTokens.length;
} }
private List<String> getBreadCrumb() {
if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
final Context context = getContext();
mBreadcrumb = mSiteMapManager.buildBreadCrumb(
context, ManageApplications.class.getName(),
context.getString(R.string.applications_settings));
}
return mBreadcrumb;
}
} }

View File

@@ -19,6 +19,8 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.view.Menu; import android.view.Menu;
import com.android.settings.dashboard.SiteMapManager;
/** /**
* FeatureProvider for Settings Search * FeatureProvider for Settings Search
*/ */
@@ -57,6 +59,11 @@ public interface SearchFeatureProvider {
*/ */
DatabaseIndexingManager getIndexingManager(Context context); DatabaseIndexingManager getIndexingManager(Context context);
/**
* Returns the manager for looking up breadcrumbs.
*/
SiteMapManager getSiteMapManager();
/** /**
* Updates the Settings indexes * Updates the Settings indexes
*/ */

View File

@@ -25,6 +25,7 @@ import android.view.MenuItem;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.applications.PackageManagerWrapperImpl; import com.android.settings.applications.PackageManagerWrapperImpl;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.search.Index; import com.android.settings.search.Index;
/** /**
@@ -35,6 +36,7 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
private static final String TAG = "SearchFeatureProvider"; private static final String TAG = "SearchFeatureProvider";
private DatabaseIndexingManager mDatabaseIndexingManager; private DatabaseIndexingManager mDatabaseIndexingManager;
private SiteMapManager mSiteMapManager;
@Override @Override
public boolean isEnabled(Context context) { public boolean isEnabled(Context context) {
@@ -86,6 +88,13 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
return mDatabaseIndexingManager; return mDatabaseIndexingManager;
} }
public SiteMapManager getSiteMapManager() {
if (mSiteMapManager == null) {
mSiteMapManager = new SiteMapManager();
}
return mSiteMapManager;
}
@Override @Override
public void updateIndex(Context context) { public void updateIndex(Context context) {
long indexStartTime = System.currentTimeMillis(); long indexStartTime = System.currentTimeMillis();

View File

@@ -24,9 +24,6 @@ import android.util.TypedValue;
import com.android.settings.R; import com.android.settings.R;
import java.text.Normalizer;
import java.util.regex.Pattern;
/** /**
* Utility class to parse elements of XML preferences * Utility class to parse elements of XML preferences
*/ */
@@ -74,6 +71,14 @@ public class XmlParserUtils {
return getData(context, attrs, R.styleable.Preference, R.styleable.Preference_keywords); return getData(context, attrs, R.styleable.Preference, R.styleable.Preference_keywords);
} }
/**
* Returns the fragment name if this preference launches a child fragment.
*/
public static String getDataChildFragment(Context context, AttributeSet attrs) {
return getData(context, attrs, R.styleable.Preference,
R.styleable.Preference_android_fragment);
}
private static String getData(Context context, AttributeSet set, int[] attrs, int resId) { private static String getData(Context context, AttributeSet set, int[] attrs, int resId) {
final TypedArray sa = context.obtainStyledAttributes(set, attrs); final TypedArray sa = context.obtainStyledAttributes(set, attrs);
final TypedValue tv = sa.peekValue(resId); final TypedValue tv = sa.peekValue(resId);

View File

@@ -25,6 +25,7 @@ import android.provider.SearchIndexableResource;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
import com.android.settings.search2.DatabaseIndexingManager; import com.android.settings.search2.DatabaseIndexingManager;
import com.android.settings.testutils.DatabaseTestUtils; import com.android.settings.testutils.DatabaseTestUtils;
@@ -41,6 +42,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import static com.android.settings.dashboard.SiteMapManager.SITE_MAP_COLUMNS;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
@@ -263,6 +265,29 @@ public class DatabaseIndexingManagerTest {
assertThat(cursor.getBlob(20)).isNull(); assertThat(cursor.getBlob(20)).isNull();
} }
@Test
public void testAddResourceWithChildFragment_shouldUpdateSiteMapDb() {
SearchIndexableResource resource = getFakeResource(R.xml.network_and_internet);
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
new HashMap<>());
Cursor query = mDb.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS,
null, null, null, null, null);
query.moveToPosition(-1);
int count = 0;
while (query.moveToNext()) {
count++;
assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_CLASS)))
.isEqualTo(className);
assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_TITLE)))
.isEqualTo(mContext.getString(R.string.network_dashboard_title));
assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_CLASS)))
.isNotEmpty();
assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_TITLE)))
.isNotEmpty();
}
assertThat(count).isEqualTo(5);
}
@Test @Test
public void testAddResourceCustomSetting_RowsMatch() { public void testAddResourceCustomSetting_RowsMatch() {
SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings); SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings);
@@ -393,9 +418,11 @@ public class DatabaseIndexingManagerTest {
// Normalized Title // Normalized Title
assertThat(cursor.getString(3)).isEqualTo("preferred install location"); assertThat(cursor.getString(3)).isEqualTo("preferred install location");
// Summary On // Summary On
assertThat(cursor.getString(4)).isEqualTo("Change the preferred installation location for new apps"); assertThat(cursor.getString(4)).isEqualTo(
"Change the preferred installation location for new apps");
// Summary On Normalized // Summary On Normalized
assertThat(cursor.getString(5)).isEqualTo("change the preferred installation location for new apps"); assertThat(cursor.getString(5)).isEqualTo(
"change the preferred installation location for new apps");
// Summary Off - only on for checkbox preferences // Summary Off - only on for checkbox preferences
assertThat(cursor.getString(6)).isEmpty(); assertThat(cursor.getString(6)).isEmpty();
// Summary off normalized - only on for checkbox preferences // Summary off normalized - only on for checkbox preferences

View File

@@ -23,31 +23,51 @@ import android.database.sqlite.SQLiteDatabase;
import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.search2.DatabaseIndexingUtils; import com.android.settings.search2.DatabaseIndexingUtils;
import com.android.settings.search2.DatabaseResultLoader; import com.android.settings.search2.DatabaseResultLoader;
import com.android.settings.testutils.DatabaseTestUtils; import com.android.settings.testutils.DatabaseTestUtils;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class DatabaseResultLoaderTest { public class DatabaseResultLoaderTest {
private Context mContext;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mMockContext;
@Mock
private SiteMapManager mSiteMapManager;
private Context mContext;
private DatabaseResultLoader loader; private DatabaseResultLoader loader;
SQLiteDatabase mDb; SQLiteDatabase mDb;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application; mContext = RuntimeEnvironment.application;
FakeFeatureFactory.setupForTest(mMockContext);
FakeFeatureFactory factory =
(FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext);
when(factory.searchFeatureProvider.getSiteMapManager())
.thenReturn(mSiteMapManager);
mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase(); mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
setUpDb(); setUpDb();
} }
@@ -61,6 +81,7 @@ public class DatabaseResultLoaderTest {
public void testMatchTitle() { public void testMatchTitle() {
loader = new DatabaseResultLoader(mContext, "title"); loader = new DatabaseResultLoader(mContext, "title");
assertThat(loader.loadInBackground().size()).isEqualTo(3); assertThat(loader.loadInBackground().size()).isEqualTo(3);
verify(mSiteMapManager, times(3)).buildBreadCrumb(eq(mContext), anyString(), anyString());
} }
@Test @Test

View File

@@ -19,17 +19,13 @@ package com.android.settings.search;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.view.Menu; import android.view.Menu;
import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.search2.DatabaseIndexingManager;
import com.android.settings.search2.SearchFeatureProviderImpl; import com.android.settings.search2.SearchFeatureProviderImpl;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.drawer.DashboardCategory;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -76,6 +72,14 @@ public class SearchFeatureProviderImplTest {
verify(menu).add(anyInt(), anyInt(), anyInt(), anyString()); verify(menu).add(anyInt(), anyInt(), anyInt(), anyString());
} }
@Test
public void getSiteMapManager_shouldCacheInstanec() {
final SiteMapManager manager1 = mProvider.getSiteMapManager();
final SiteMapManager manager2 = mProvider.getSiteMapManager();
assertThat(manager1).isSameAs(manager2);
}
@Test @Test
public void testUpdateIndexNewSearch_UsesDatabaseIndexingManager() { public void testUpdateIndexNewSearch_UsesDatabaseIndexingManager() {
mProvider = spy(new SearchFeatureProviderImpl()); mProvider = spy(new SearchFeatureProviderImpl());

View File

@@ -28,12 +28,16 @@ import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.SubSettings; import com.android.settings.SubSettings;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.gestures.GestureSettings; import com.android.settings.gestures.GestureSettings;
import com.android.settings.search2.ResultPayload.PayloadType; import com.android.settings.search2.ResultPayload.PayloadType;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric; import org.robolectric.Robolectric;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
@@ -57,11 +61,14 @@ public class CursorToSearchResultConverterTest {
private static final int BASE_RANK = 1; private static final int BASE_RANK = 1;
private static final int EXAMPLES = 3; private static final int EXAMPLES = 3;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private SiteMapManager mSiteMapManager;
private Drawable mDrawable; private Drawable mDrawable;
private CursorToSearchResultConverter mConverter; private CursorToSearchResultConverter mConverter;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this);
Context context = Robolectric.buildActivity(Activity.class).get(); Context context = Robolectric.buildActivity(Activity.class).get();
mDrawable = context.getDrawable(ICON); mDrawable = context.getDrawable(ICON);
mConverter = new CursorToSearchResultConverter(context, QUERY); mConverter = new CursorToSearchResultConverter(context, QUERY);
@@ -69,19 +76,21 @@ public class CursorToSearchResultConverterTest {
@Test @Test
public void testParseNullResults_ReturnsNull() { public void testParseNullResults_ReturnsNull() {
List<SearchResult> results = mConverter.convertCursor(null, BASE_RANK); List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, null, BASE_RANK);
assertThat(results).isNull(); assertThat(results).isNull();
} }
@Test @Test
public void testParseCursor_NotNull() { public void testParseCursor_NotNull() {
List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); List<SearchResult> results = mConverter.convertCursor(
mSiteMapManager, getDummyCursor(), BASE_RANK);
assertThat(results).isNotNull(); assertThat(results).isNotNull();
} }
@Test @Test
public void testParseCursor_MatchesRank() { public void testParseCursor_MatchesRank() {
List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); List<SearchResult> results = mConverter.convertCursor(
mSiteMapManager, getDummyCursor(), BASE_RANK);
for (int i = 0; i < EXAMPLES; i++) { for (int i = 0; i < EXAMPLES; i++) {
assertThat(results.get(i).rank).isEqualTo(BASE_RANK); assertThat(results.get(i).rank).isEqualTo(BASE_RANK);
} }
@@ -89,7 +98,8 @@ public class CursorToSearchResultConverterTest {
@Test @Test
public void testParseCursor_MatchesTitle() { public void testParseCursor_MatchesTitle() {
List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); List<SearchResult> results = mConverter.convertCursor(
mSiteMapManager, getDummyCursor(), BASE_RANK);
for (int i = 0; i < EXAMPLES; i++) { for (int i = 0; i < EXAMPLES; i++) {
assertThat(results.get(i).title).isEqualTo(TITLES[i]); assertThat(results.get(i).title).isEqualTo(TITLES[i]);
} }
@@ -97,7 +107,8 @@ public class CursorToSearchResultConverterTest {
@Test @Test
public void testParseCursor_MatchesSummary() { public void testParseCursor_MatchesSummary() {
List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); List<SearchResult> results = mConverter.convertCursor(
mSiteMapManager, getDummyCursor(), BASE_RANK);
for (int i = 0; i < EXAMPLES; i++) { for (int i = 0; i < EXAMPLES; i++) {
assertThat(results.get(i).summary).isEqualTo(SUMMARY); assertThat(results.get(i).summary).isEqualTo(SUMMARY);
} }
@@ -105,7 +116,8 @@ public class CursorToSearchResultConverterTest {
@Test @Test
public void testParseCursor_MatchesIcon() { public void testParseCursor_MatchesIcon() {
List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); List<SearchResult> results = mConverter.convertCursor(
mSiteMapManager, getDummyCursor(), BASE_RANK);
for (int i = 0; i < EXAMPLES; i++) { for (int i = 0; i < EXAMPLES; i++) {
Drawable resultDrawable = results.get(i).icon; Drawable resultDrawable = results.get(i).icon;
assertThat(resultDrawable).isNotNull(); assertThat(resultDrawable).isNotNull();
@@ -116,7 +128,7 @@ public class CursorToSearchResultConverterTest {
@Test @Test
public void testParseCursor_NoIcon() { public void testParseCursor_NoIcon() {
List<SearchResult> results = mConverter.convertCursor( List<SearchResult> results = mConverter.convertCursor(
getDummyCursor(false /* hasIcon */), BASE_RANK); mSiteMapManager, getDummyCursor(false /* hasIcon */), BASE_RANK);
for (int i = 0; i < EXAMPLES; i++) { for (int i = 0; i < EXAMPLES; i++) {
Drawable resultDrawable = results.get(i).icon; Drawable resultDrawable = results.get(i).icon;
assertThat(resultDrawable).isNull(); assertThat(resultDrawable).isNull();
@@ -125,7 +137,8 @@ public class CursorToSearchResultConverterTest {
@Test @Test
public void testParseCursor_MatchesPayloadType() { public void testParseCursor_MatchesPayloadType() {
List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); List<SearchResult> results = mConverter.convertCursor(
mSiteMapManager, getDummyCursor(), BASE_RANK);
ResultPayload payload; ResultPayload payload;
for (int i = 0; i < EXAMPLES; i++) { for (int i = 0; i < EXAMPLES; i++) {
payload = results.get(i).payload; payload = results.get(i).payload;
@@ -152,7 +165,7 @@ public class CursorToSearchResultConverterTest {
0, // Payload Type 0, // Payload Type
null // Payload null // Payload
}); });
List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK); List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
IntentPayload payload = (IntentPayload) results.get(0).payload; IntentPayload payload = (IntentPayload) results.get(0).payload;
Intent intent = payload.intent; Intent intent = payload.intent;
assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName()); assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName());
@@ -160,7 +173,8 @@ public class CursorToSearchResultConverterTest {
@Test @Test
public void testParseCursor_MatchesIntentPayload() { public void testParseCursor_MatchesIntentPayload() {
List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); List<SearchResult> results = mConverter.convertCursor(
mSiteMapManager, getDummyCursor(), BASE_RANK);
IntentPayload payload; IntentPayload payload;
for (int i = 0; i < EXAMPLES; i++) { for (int i = 0; i < EXAMPLES; i++) {
payload = (IntentPayload) results.get(i).payload; payload = (IntentPayload) results.get(i).payload;
@@ -187,7 +201,7 @@ public class CursorToSearchResultConverterTest {
PayloadType.INTENT, // Payload Type PayloadType.INTENT, // Payload Type
null // Payload null // Payload
}); });
List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK); List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
IntentPayload payload = (IntentPayload) results.get(0).payload; IntentPayload payload = (IntentPayload) results.get(0).payload;
Intent intent = payload.intent; Intent intent = payload.intent;
@@ -222,7 +236,7 @@ public class CursorToSearchResultConverterTest {
type, // Payload Type type, // Payload Type
ResultPayloadUtils.marshall(payload) // Payload ResultPayloadUtils.marshall(payload) // Payload
}); });
List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK); List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
InlineSwitchPayload newPayload = (InlineSwitchPayload) results.get(0).payload; InlineSwitchPayload newPayload = (InlineSwitchPayload) results.get(0).payload;
assertThat(newPayload.settingsUri).isEqualTo(uri); assertThat(newPayload.settingsUri).isEqualTo(uri);

View File

@@ -22,10 +22,13 @@ import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo; import android.content.pm.UserInfo;
import android.os.UserManager; import android.os.UserManager;
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.applications.PackageManagerWrapper; import com.android.settings.applications.PackageManagerWrapper;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.testutils.ApplicationTestUtils; import com.android.settings.testutils.ApplicationTestUtils;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -44,7 +47,12 @@ import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; 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 static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
@@ -57,16 +65,24 @@ public class InstalledAppResultLoaderTest {
private PackageManagerWrapper mPackageManagerWrapper; private PackageManagerWrapper mPackageManagerWrapper;
@Mock @Mock
private UserManager mUserManager; private UserManager mUserManager;
@Mock
private SiteMapManager mSiteMapManager;
private InstalledAppResultLoader mLoader; private InstalledAppResultLoader mLoader;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
FakeFeatureFactory.setupForTest(mContext);
FakeFeatureFactory factory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
when(factory.searchFeatureProvider.getSiteMapManager())
.thenReturn(mSiteMapManager);
final List<UserInfo> infos = new ArrayList<>(); final List<UserInfo> infos = new ArrayList<>();
infos.add(new UserInfo(1, "user 1", 0)); infos.add(new UserInfo(1, "user 1", 0));
when(mUserManager.getProfiles(anyInt())).thenReturn(infos); when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mContext.getString(R.string.applications_settings))
.thenReturn("app");
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt())) when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList( .thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM, ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -94,9 +110,14 @@ public class InstalledAppResultLoaderTest {
public void query_matchingQuery_shouldReturnNonSystemApps() { public void query_matchingQuery_shouldReturnNonSystemApps() {
final String query = "app"; final String query = "app";
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query); mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query));
when(mLoader.getContext()).thenReturn(mContext);
when(mSiteMapManager.buildBreadCrumb(eq(mContext), anyString(), anyString()))
.thenReturn(Arrays.asList(new String[]{"123"}));
assertThat(mLoader.loadInBackground().size()).isEqualTo(2); assertThat(mLoader.loadInBackground().size()).isEqualTo(2);
verify(mSiteMapManager)
.buildBreadCrumb(eq(mContext), anyString(), anyString());
} }
@Test @Test
@@ -107,9 +128,12 @@ public class InstalledAppResultLoaderTest {
0 /* targetSdkVersion */))); 0 /* targetSdkVersion */)));
final String query = "app"; final String query = "app";
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query); mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query));
when(mLoader.getContext()).thenReturn(mContext);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1); assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
verify(mSiteMapManager)
.buildBreadCrumb(eq(mContext), anyString(), anyString());
} }
@Test @Test
@@ -132,7 +156,7 @@ public class InstalledAppResultLoaderTest {
} }
@Test @Test
public void query_matchingQuery_shouldNOtReturnSystemAppIfNotLaunchable() { public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() {
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt())) when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList( .thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM, ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -146,6 +170,8 @@ public class InstalledAppResultLoaderTest {
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query); mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
assertThat(mLoader.loadInBackground()).isEmpty(); assertThat(mLoader.loadInBackground()).isEmpty();
verify(mSiteMapManager, never())
.buildBreadCrumb(eq(mContext), anyString(), anyString());
} }
@Test @Test

View File

@@ -0,0 +1,145 @@
/*
* 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.search2;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.search.IndexDatabaseHelper;
import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
import com.android.settings.system.SystemDashboardFragment;
import com.android.settings.testutils.DatabaseTestUtils;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.drawer.CategoryKey;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
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.Arrays;
import java.util.List;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SiteMapManagerTest {
private static final int STATIC_DB_DEPTH = 4;
private static final String CLASS_PREFIX = "class_";
private static final String TITLE_PREFIX = "title_";
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mMockContext;
private Context mContext;
private SQLiteDatabase mDb;
private SiteMapManager mSiteMapManager;
private FakeFeatureFactory mFeatureFactory;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
FakeFeatureFactory.setupForTest(mMockContext);
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext);
mContext = RuntimeEnvironment.application;
mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
buildDb();
mSiteMapManager = new SiteMapManager();
}
@After
public void cleanUp() {
DatabaseTestUtils.clearDb();
}
@Test
public void buildBreadCrumb_onlyFromSiteMapDb_breadcrumbShouldLinkUp() {
List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
CLASS_PREFIX + 0, TITLE_PREFIX + 0);
assertThat(breadcrumb.size()).isEqualTo(STATIC_DB_DEPTH + 1);
for (int i = 0; i < STATIC_DB_DEPTH; i++) {
assertThat(breadcrumb.get(i)).isEqualTo(TITLE_PREFIX + (STATIC_DB_DEPTH - i));
}
}
@Test
public void buildBreadCrumb_fromSiteMapDbAndDashboardProvider_breadcrumbShouldLinkUp() {
final String iaClass = SystemDashboardFragment.class.getName();
final String iaTitle = "ia_title";
ContentValues index = new ContentValues();
index.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, iaClass);
index.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, iaTitle);
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, index);
final DashboardCategory category = new DashboardCategory();
category.key = CategoryKey.CATEGORY_SYSTEM;
category.tiles.add(new Tile());
category.tiles.get(0).title = TITLE_PREFIX + STATIC_DB_DEPTH;
category.tiles.get(0).metaData = new Bundle();
category.tiles.get(0).metaData.putString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS,
CLASS_PREFIX + STATIC_DB_DEPTH);
when(mFeatureFactory.dashboardFeatureProvider.getAllCategories())
.thenReturn(Arrays.asList(category));
final List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
CLASS_PREFIX + 0, TITLE_PREFIX + 0);
assertThat(breadcrumb.size()).isEqualTo(STATIC_DB_DEPTH + 2);
assertThat(breadcrumb.get(0))
.isEqualTo(iaTitle);
}
@Test
public void buildBreadCrumb_classNotIndexed_shouldNotHaveBreadCrumb() {
final String title = "wrong_title";
final List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
"wrong_class", title);
assertThat(breadcrumb.size()).isEqualTo(1);
assertThat(breadcrumb.get(0)).isEqualTo(title);
}
private void buildDb() {
for (int i = 0; i < STATIC_DB_DEPTH; i++) {
final ContentValues siteMapPair = new ContentValues();
siteMapPair.put(SiteMapColumns.DOCID, i);
siteMapPair.put(SiteMapColumns.PARENT_CLASS, CLASS_PREFIX + (i + 1));
siteMapPair.put(SiteMapColumns.PARENT_TITLE, TITLE_PREFIX + (i + 1));
siteMapPair.put(SiteMapColumns.CHILD_CLASS, CLASS_PREFIX + i);
siteMapPair.put(SiteMapColumns.CHILD_TITLE, TITLE_PREFIX + i);
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, null, siteMapPair);
}
}
}