diff --git a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java new file mode 100644 index 00000000000..54d4fd0a36b --- /dev/null +++ b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java @@ -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 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 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 parentToKey : PARENT_TO_CATEGORY_KEY_MAP.entrySet()) { + CATEGORY_KEY_TO_PARENT_MAP.put(parentToKey.getValue(), parentToKey.getKey()); + } + } +} diff --git a/src/com/android/settings/dashboard/SiteMapManager.java b/src/com/android/settings/dashboard/SiteMapManager.java new file mode 100644 index 00000000000..31443981d25 --- /dev/null +++ b/src/com/android/settings/dashboard/SiteMapManager.java @@ -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. + *

+ * 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 mPairs = new ArrayList<>(); + + private boolean mInitialized; + + /** + * Given a fragment class name and its screen title, build a breadcrumb from Settings root to + * this screen. + *

+ * 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 buildBreadCrumb(Context context, String clazz, + String screenTitle) { + init(context); + final long startTime = System.currentTimeMillis(); + final List 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 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 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; + } + } +} diff --git a/src/com/android/settings/search/IndexDatabaseHelper.java b/src/com/android/settings/search/IndexDatabaseHelper.java index 8de6c54e3c2..60378c2ce8e 100644 --- a/src/com/android/settings/search/IndexDatabaseHelper.java +++ b/src/com/android/settings/search/IndexDatabaseHelper.java @@ -28,12 +28,13 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "IndexDatabaseHelper"; 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"; public interface Tables { String TABLE_PREFS_INDEX = "prefs_index"; + String TABLE_SITE_MAP = "site_map"; String TABLE_META_INDEX = "meta_index"; String TABLE_SAVED_QUERIES = "saved_queries"; } @@ -72,6 +73,14 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { 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 = "CREATE VIRTUAL TABLE " + Tables.TABLE_PREFS_INDEX + " USING fts4" + "(" + @@ -132,6 +141,17 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { 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 = "INSERT INTO " + Tables.TABLE_META_INDEX + " VALUES ('" + Build.VERSION.INCREMENTAL + "');"; @@ -164,6 +184,7 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { db.execSQL(CREATE_INDEX_TABLE); db.execSQL(CREATE_META_TABLE); db.execSQL(CREATE_SAVED_QUERIES_TABLE); + db.execSQL(CREATE_SITE_MAP_TABLE); db.execSQL(INSERT_BUILD_VERSION); 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_PREFS_INDEX); db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES); + db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SITE_MAP); } } diff --git a/src/com/android/settings/search2/CursorToSearchResultConverter.java b/src/com/android/settings/search2/CursorToSearchResultConverter.java index 948397fe265..6549c183140 100644 --- a/src/com/android/settings/search2/CursorToSearchResultConverter.java +++ b/src/com/android/settings/search2/CursorToSearchResultConverter.java @@ -28,8 +28,10 @@ import android.os.BadParcelableException; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; + import com.android.settings.SettingsActivity; import com.android.settings.Utils; +import com.android.settings.dashboard.SiteMapManager; import java.util.ArrayList; import java.util.Collections; @@ -39,18 +41,20 @@ import java.util.List; import java.util.Map; 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_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_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_PAYLOAD_TYPE; 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. @@ -78,7 +82,8 @@ class CursorToSearchResultConverter { mQueryText = queryText; } - public List convertCursor(Cursor cursorResults, int baseRank) { + public List convertCursor(SiteMapManager sitemapManager, + Cursor cursorResults, int baseRank) { if (cursorResults == null) { return null; } @@ -86,8 +91,8 @@ class CursorToSearchResultConverter { final List results = new ArrayList<>(); while (cursorResults.moveToNext()) { - SearchResult result = buildSingleSearchResultFromCursor(contextMap, cursorResults, - baseRank); + SearchResult result = buildSingleSearchResultFromCursor(sitemapManager, + contextMap, cursorResults, baseRank); if (result != null) { results.add(result); } @@ -96,8 +101,8 @@ class CursorToSearchResultConverter { return results; } - private SearchResult buildSingleSearchResultFromCursor(Map contextMap, - Cursor cursor, int baseRank) { + private SearchResult buildSingleSearchResultFromCursor(SiteMapManager sitemapManager, + Map contextMap, Cursor cursor, int baseRank) { final String docId = cursor.getString(COLUMN_INDEX_ID); /* 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 @@ -128,7 +133,7 @@ class CursorToSearchResultConverter { return null; } - final List breadcrumbs = getBreadcrumbs(cursor); + final List breadcrumbs = getBreadcrumbs(sitemapManager, cursor); final int rank = getRank(breadcrumbs, baseRank); final SearchResult.Builder builder = new SearchResult.Builder(); @@ -210,12 +215,11 @@ class CursorToSearchResultConverter { return null; } - private List getBreadcrumbs(Cursor cursor) { - final List breadcrumbs = new ArrayList<>(); + private List getBreadcrumbs(SiteMapManager siteMapManager, Cursor cursor) { final String screenTitle = cursor.getString(COLUMN_INDEX_SCREEN_TITLE); - if (!TextUtils.isEmpty(screenTitle)) { - breadcrumbs.add(screenTitle); - } + final String screenClass = cursor.getString(COLUMN_INDEX_CLASS_NAME); + final List breadcrumbs = siteMapManager.buildBreadCrumb(mContext, screenClass, + screenTitle); return breadcrumbs; } diff --git a/src/com/android/settings/search2/DatabaseIndexingManager.java b/src/com/android/settings/search2/DatabaseIndexingManager.java index 073e202ca44..c75f93fac2b 100644 --- a/src/com/android/settings/search2/DatabaseIndexingManager.java +++ b/src/com/android/settings/search2/DatabaseIndexingManager.java @@ -34,7 +34,6 @@ import android.provider.SearchIndexableData; import android.provider.SearchIndexableResource; import android.provider.SearchIndexablesContract; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; @@ -582,12 +581,13 @@ public class DatabaseIndexingManager { String title; String summary; String keywords; + String childFragment; ResultPayload payload; - ArrayMap controllerUriMap = null; + Map controllerUriMap = null; if (fragmentName != null) { - controllerUriMap = (ArrayMap) DatabaseIndexingUtils + controllerUriMap = DatabaseIndexingUtils .getPreferenceControllerUriMap(fragmentName, context); } @@ -655,8 +655,10 @@ public class DatabaseIndexingManager { } payload = DatabaseIndexingUtils.getPayloadFromUriMap(controllerUriMap, key); + childFragment = XmlParserUtils.getDataChildFragment(context, attrs); builder.setEntries(entries) + .setChildClassName(childFragment) .setPayload(payload); // Insert rows for the child nodes of PreferenceScreen @@ -811,6 +813,18 @@ public class DatabaseIndexingManager { values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, row.payload); 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 entries; public final String className; + public final String childClassName; public final String screenTitle; public final int iconResId; public final int rank; @@ -973,6 +988,7 @@ public class DatabaseIndexingManager { normalizedSummaryOff = builder.mNormalizedSummaryOff; entries = builder.mEntries; className = builder.mClassName; + childClassName = builder.mChildClassName; screenTitle = builder.mScreenTitle; iconResId = builder.mIconResId; rank = builder.mRank; @@ -1008,6 +1024,7 @@ public class DatabaseIndexingManager { private String mNormalizedSummaryOff; private String mEntries; private String mClassName; + private String mChildClassName; private String mScreenTitle; private int mIconResId; private int mRank; @@ -1067,6 +1084,11 @@ public class DatabaseIndexingManager { return this; } + public Builder setChildClassName(String childClassName) { + mChildClassName = childClassName; + return this; + } + public Builder setScreenTitle(String screenTitle) { mScreenTitle = screenTitle; return this; diff --git a/src/com/android/settings/search2/DatabaseIndexingUtils.java b/src/com/android/settings/search2/DatabaseIndexingUtils.java index 9fdf73218bc..bd06ef3a574 100644 --- a/src/com/android/settings/search2/DatabaseIndexingUtils.java +++ b/src/com/android/settings/search2/DatabaseIndexingUtils.java @@ -105,7 +105,7 @@ public class DatabaseIndexingUtils { * @return The Payload from the {@link PreferenceController} specified by the key, if it exists. * Otherwise null. */ - public static ResultPayload getPayloadFromUriMap(ArrayMap uriMap, + public static ResultPayload getPayloadFromUriMap(Map uriMap, String key) { if (uriMap == null) { return null; diff --git a/src/com/android/settings/search2/DatabaseResultLoader.java b/src/com/android/settings/search2/DatabaseResultLoader.java index f7acd25d45b..6c8def69b19 100644 --- a/src/com/android/settings/search2/DatabaseResultLoader.java +++ b/src/com/android/settings/search2/DatabaseResultLoader.java @@ -20,6 +20,8 @@ import android.content.Context; import android.database.Cursor; 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.utils.AsyncLoader; @@ -35,11 +37,6 @@ import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_PREFS */ public class DatabaseResultLoader extends AsyncLoader> { 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 are not necessarily the same order nor similar coverage as the schema defined in @@ -99,8 +96,15 @@ public class DatabaseResultLoader extends AsyncLoader> { */ 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) { super(context); + mSiteMapManager = FeatureFactory.getFactory(context) + .getSearchFeatureProvider().getSiteMapManager(); mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase(); mQueryText = cleanQuery(queryText); mConverter = new CursorToSearchResultConverter(context, mQueryText); @@ -144,7 +148,7 @@ public class DatabaseResultLoader extends AsyncLoader> { final Cursor resultCursor = mDatabase.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause, selection, null, null, null); - return mConverter.convertCursor(resultCursor, baseRank); + return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank); } @Override @@ -155,6 +159,7 @@ public class DatabaseResultLoader extends AsyncLoader> { /** * A generic method to make the query suitable for searching the database. + * * @return the cleaned query string */ private static String cleanQuery(String query) { diff --git a/src/com/android/settings/search2/InstalledAppResultLoader.java b/src/com/android/settings/search2/InstalledAppResultLoader.java index 17bb1572854..0b828a33a0d 100644 --- a/src/com/android/settings/search2/InstalledAppResultLoader.java +++ b/src/com/android/settings/search2/InstalledAppResultLoader.java @@ -29,7 +29,10 @@ import android.provider.Settings; import android.text.TextUtils; import com.android.settings.R; +import com.android.settings.applications.ManageApplications; 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 java.util.ArrayList; @@ -46,7 +49,8 @@ public class InstalledAppResultLoader extends AsyncLoader> { private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_LAUNCHER); - private final List mBreadcrumb; + private List mBreadcrumb; + private SiteMapManager mSiteMapManager; private final String mQuery; private final UserManager mUserManager; private final PackageManagerWrapper mPackageManager; @@ -55,9 +59,8 @@ public class InstalledAppResultLoader extends AsyncLoader> { public InstalledAppResultLoader(Context context, PackageManagerWrapper pmWrapper, String query) { super(context); - mBreadcrumb = new ArrayList<>(); - mBreadcrumb.add(context.getString(R.string.app_and_notification_dashboard_title)); - mBreadcrumb.add(context.getString(R.string.applications_settings)); + mSiteMapManager = FeatureFactory.getFactory(context) + .getSearchFeatureProvider().getSiteMapManager(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mPackageManager = pmWrapper; mQuery = query; @@ -92,7 +95,7 @@ public class InstalledAppResultLoader extends AsyncLoader> { builder.addIcon(info.loadIcon(pm)) .addTitle(info.loadLabel(pm)) .addRank(wordDiff) - .addBreadcrumbs(mBreadcrumb) + .addBreadcrumbs(getBreadCrumb()) .addPayload(new IntentPayload(intent)); results.add(builder.build()); } @@ -162,4 +165,14 @@ public class InstalledAppResultLoader extends AsyncLoader> { // to infinity. return valueText.length - queryTokens.length; } + + private List 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; + } } diff --git a/src/com/android/settings/search2/SearchFeatureProvider.java b/src/com/android/settings/search2/SearchFeatureProvider.java index 91a14444c45..a9be5a15a87 100644 --- a/src/com/android/settings/search2/SearchFeatureProvider.java +++ b/src/com/android/settings/search2/SearchFeatureProvider.java @@ -19,6 +19,8 @@ import android.app.Activity; import android.content.Context; import android.view.Menu; +import com.android.settings.dashboard.SiteMapManager; + /** * FeatureProvider for Settings Search */ @@ -57,6 +59,11 @@ public interface SearchFeatureProvider { */ DatabaseIndexingManager getIndexingManager(Context context); + /** + * Returns the manager for looking up breadcrumbs. + */ + SiteMapManager getSiteMapManager(); + /** * Updates the Settings indexes */ diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java index 5d624124140..b575b156279 100644 --- a/src/com/android/settings/search2/SearchFeatureProviderImpl.java +++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.java @@ -25,6 +25,7 @@ import android.view.MenuItem; import com.android.settings.R; import com.android.settings.applications.PackageManagerWrapperImpl; +import com.android.settings.dashboard.SiteMapManager; import com.android.settings.search.Index; /** @@ -35,6 +36,7 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider { private static final String TAG = "SearchFeatureProvider"; private DatabaseIndexingManager mDatabaseIndexingManager; + private SiteMapManager mSiteMapManager; @Override public boolean isEnabled(Context context) { @@ -86,6 +88,13 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider { return mDatabaseIndexingManager; } + public SiteMapManager getSiteMapManager() { + if (mSiteMapManager == null) { + mSiteMapManager = new SiteMapManager(); + } + return mSiteMapManager; + } + @Override public void updateIndex(Context context) { long indexStartTime = System.currentTimeMillis(); diff --git a/src/com/android/settings/search2/XmlParserUtils.java b/src/com/android/settings/search2/XmlParserUtils.java index 748d4b0330f..90b1c1fe679 100644 --- a/src/com/android/settings/search2/XmlParserUtils.java +++ b/src/com/android/settings/search2/XmlParserUtils.java @@ -24,9 +24,6 @@ import android.util.TypedValue; import com.android.settings.R; -import java.text.Normalizer; -import java.util.regex.Pattern; - /** * 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); } + /** + * 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) { final TypedArray sa = context.obtainStyledAttributes(set, attrs); final TypedValue tv = sa.peekValue(resId); diff --git a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java index e55dc1058a7..1cf72eaf7f5 100644 --- a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java +++ b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java @@ -25,6 +25,7 @@ import android.provider.SearchIndexableResource; import com.android.settings.R; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns; import com.android.settings.search2.DatabaseIndexingManager; import com.android.settings.testutils.DatabaseTestUtils; @@ -41,6 +42,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; +import static com.android.settings.dashboard.SiteMapManager.SITE_MAP_COLUMNS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; @@ -57,7 +59,7 @@ public class DatabaseIndexingManagerTest { private final String updatedSummaryOn = "summary-on"; private final String normalizedSummaryOn = "summaryon"; private final String summaryOff = "summary\u2011off"; - private final String updatedSummaryOff ="summary-off"; + private final String updatedSummaryOff = "summary-off"; private final String normalizedSummaryOff = "summaryoff"; private final String entries = "entries"; private final String keywords = "keywords, keywordss, keywordsss"; @@ -94,7 +96,7 @@ public class DatabaseIndexingManagerTest { Cursor dbCursor = mDb.query("prefs_index", null, null, null, null, null, null); List columnNames = new ArrayList<>(Arrays.asList(dbCursor.getColumnNames())); // Note that docid is not included. - List expColumnNames = new ArrayList<>(Arrays.asList(new String[ ]{ + List expColumnNames = new ArrayList<>(Arrays.asList(new String[]{ "locale", "data_rank", "data_title", @@ -263,6 +265,29 @@ public class DatabaseIndexingManagerTest { 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 public void testAddResourceCustomSetting_RowsMatch() { SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings); @@ -393,16 +418,18 @@ public class DatabaseIndexingManagerTest { // Normalized Title assertThat(cursor.getString(3)).isEqualTo("preferred install location"); // 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 - 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 assertThat(cursor.getString(6)).isEmpty(); // Summary off normalized - only on for checkbox preferences assertThat(cursor.getString(7)).isEmpty(); // Entries - only on for list preferences assertThat(cursor.getString(8)).isEqualTo("Internal device storage|Removable SD card|" + - "Let the system decide|"); + "Let the system decide|"); // Keywords assertThat(cursor.getString(9)).isEmpty(); // Screen Title diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java index 2b29a1679ce..c749a00c709 100644 --- a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java +++ b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java @@ -23,31 +23,51 @@ import android.database.sqlite.SQLiteDatabase; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.dashboard.SiteMapManager; import com.android.settings.search2.DatabaseIndexingUtils; import com.android.settings.search2.DatabaseResultLoader; import com.android.settings.testutils.DatabaseTestUtils; +import com.android.settings.testutils.FakeFeatureFactory; 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 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) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) 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; SQLiteDatabase mDb; @Before public void setUp() { + MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; + FakeFeatureFactory.setupForTest(mMockContext); + FakeFeatureFactory factory = + (FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext); + when(factory.searchFeatureProvider.getSiteMapManager()) + .thenReturn(mSiteMapManager); mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase(); setUpDb(); } @@ -61,6 +81,7 @@ public class DatabaseResultLoaderTest { public void testMatchTitle() { loader = new DatabaseResultLoader(mContext, "title"); assertThat(loader.loadInBackground().size()).isEqualTo(3); + verify(mSiteMapManager, times(3)).buildBreadCrumb(eq(mContext), anyString(), anyString()); } @Test diff --git a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java index c0b1b3d7b40..c3147286dd4 100644 --- a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java @@ -19,17 +19,13 @@ package com.android.settings.search; import android.app.Activity; import android.content.Context; -import android.content.res.Configuration; import android.view.Menu; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; -import com.android.settings.overlay.FeatureFactory; -import com.android.settings.search2.DatabaseIndexingManager; +import com.android.settings.dashboard.SiteMapManager; 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.Test; import org.junit.runner.RunWith; @@ -76,6 +72,14 @@ public class SearchFeatureProviderImplTest { 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 public void testUpdateIndexNewSearch_UsesDatabaseIndexingManager() { mProvider = spy(new SearchFeatureProviderImpl()); diff --git a/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java b/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java index 6ad7501bc77..d69ba3ee944 100644 --- a/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java +++ b/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java @@ -28,12 +28,16 @@ import com.android.settings.R; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.SubSettings; import com.android.settings.TestConfig; +import com.android.settings.dashboard.SiteMapManager; import com.android.settings.gestures.GestureSettings; import com.android.settings.search2.ResultPayload.PayloadType; 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.Robolectric; import org.robolectric.annotation.Config; @@ -57,11 +61,14 @@ public class CursorToSearchResultConverterTest { private static final int BASE_RANK = 1; private static final int EXAMPLES = 3; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private SiteMapManager mSiteMapManager; private Drawable mDrawable; private CursorToSearchResultConverter mConverter; @Before public void setUp() { + MockitoAnnotations.initMocks(this); Context context = Robolectric.buildActivity(Activity.class).get(); mDrawable = context.getDrawable(ICON); mConverter = new CursorToSearchResultConverter(context, QUERY); @@ -69,19 +76,21 @@ public class CursorToSearchResultConverterTest { @Test public void testParseNullResults_ReturnsNull() { - List results = mConverter.convertCursor(null, BASE_RANK); + List results = mConverter.convertCursor(mSiteMapManager, null, BASE_RANK); assertThat(results).isNull(); } @Test public void testParseCursor_NotNull() { - List results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); + List results = mConverter.convertCursor( + mSiteMapManager, getDummyCursor(), BASE_RANK); assertThat(results).isNotNull(); } @Test public void testParseCursor_MatchesRank() { - List results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); + List results = mConverter.convertCursor( + mSiteMapManager, getDummyCursor(), BASE_RANK); for (int i = 0; i < EXAMPLES; i++) { assertThat(results.get(i).rank).isEqualTo(BASE_RANK); } @@ -89,7 +98,8 @@ public class CursorToSearchResultConverterTest { @Test public void testParseCursor_MatchesTitle() { - List results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); + List results = mConverter.convertCursor( + mSiteMapManager, getDummyCursor(), BASE_RANK); for (int i = 0; i < EXAMPLES; i++) { assertThat(results.get(i).title).isEqualTo(TITLES[i]); } @@ -97,7 +107,8 @@ public class CursorToSearchResultConverterTest { @Test public void testParseCursor_MatchesSummary() { - List results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); + List results = mConverter.convertCursor( + mSiteMapManager, getDummyCursor(), BASE_RANK); for (int i = 0; i < EXAMPLES; i++) { assertThat(results.get(i).summary).isEqualTo(SUMMARY); } @@ -105,7 +116,8 @@ public class CursorToSearchResultConverterTest { @Test public void testParseCursor_MatchesIcon() { - List results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); + List results = mConverter.convertCursor( + mSiteMapManager, getDummyCursor(), BASE_RANK); for (int i = 0; i < EXAMPLES; i++) { Drawable resultDrawable = results.get(i).icon; assertThat(resultDrawable).isNotNull(); @@ -116,7 +128,7 @@ public class CursorToSearchResultConverterTest { @Test public void testParseCursor_NoIcon() { List results = mConverter.convertCursor( - getDummyCursor(false /* hasIcon */), BASE_RANK); + mSiteMapManager, getDummyCursor(false /* hasIcon */), BASE_RANK); for (int i = 0; i < EXAMPLES; i++) { Drawable resultDrawable = results.get(i).icon; assertThat(resultDrawable).isNull(); @@ -125,7 +137,8 @@ public class CursorToSearchResultConverterTest { @Test public void testParseCursor_MatchesPayloadType() { - List results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); + List results = mConverter.convertCursor( + mSiteMapManager, getDummyCursor(), BASE_RANK); ResultPayload payload; for (int i = 0; i < EXAMPLES; i++) { payload = results.get(i).payload; @@ -152,7 +165,7 @@ public class CursorToSearchResultConverterTest { 0, // Payload Type null // Payload }); - List results = mConverter.convertCursor(cursor, BASE_RANK); + List results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK); IntentPayload payload = (IntentPayload) results.get(0).payload; Intent intent = payload.intent; assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName()); @@ -160,7 +173,8 @@ public class CursorToSearchResultConverterTest { @Test public void testParseCursor_MatchesIntentPayload() { - List results = mConverter.convertCursor(getDummyCursor(), BASE_RANK); + List results = mConverter.convertCursor( + mSiteMapManager, getDummyCursor(), BASE_RANK); IntentPayload payload; for (int i = 0; i < EXAMPLES; i++) { payload = (IntentPayload) results.get(i).payload; @@ -187,7 +201,7 @@ public class CursorToSearchResultConverterTest { PayloadType.INTENT, // Payload Type null // Payload }); - List results = mConverter.convertCursor(cursor, BASE_RANK); + List results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK); IntentPayload payload = (IntentPayload) results.get(0).payload; Intent intent = payload.intent; @@ -222,7 +236,7 @@ public class CursorToSearchResultConverterTest { type, // Payload Type ResultPayloadUtils.marshall(payload) // Payload }); - List results = mConverter.convertCursor(cursor, BASE_RANK); + List results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK); InlineSwitchPayload newPayload = (InlineSwitchPayload) results.get(0).payload; assertThat(newPayload.settingsUri).isEqualTo(uri); diff --git a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java index 4f62a9ece5b..e6397e195e0 100644 --- a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java +++ b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java @@ -22,10 +22,13 @@ import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.os.UserManager; +import com.android.settings.R; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.applications.PackageManagerWrapper; +import com.android.settings.dashboard.SiteMapManager; import com.android.settings.testutils.ApplicationTestUtils; +import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; 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 org.mockito.Matchers.any; 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.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(SettingsRobolectricTestRunner.class) @@ -57,16 +65,24 @@ public class InstalledAppResultLoaderTest { private PackageManagerWrapper mPackageManagerWrapper; @Mock private UserManager mUserManager; + @Mock + private SiteMapManager mSiteMapManager; private InstalledAppResultLoader mLoader; @Before public void setUp() { MockitoAnnotations.initMocks(this); + FakeFeatureFactory.setupForTest(mContext); + FakeFeatureFactory factory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); + when(factory.searchFeatureProvider.getSiteMapManager()) + .thenReturn(mSiteMapManager); final List 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, @@ -94,9 +110,14 @@ public class InstalledAppResultLoaderTest { public void query_matchingQuery_shouldReturnNonSystemApps() { 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); + verify(mSiteMapManager) + .buildBreadCrumb(eq(mContext), anyString(), anyString()); } @Test @@ -107,9 +128,12 @@ public class InstalledAppResultLoaderTest { 0 /* targetSdkVersion */))); 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); + verify(mSiteMapManager) + .buildBreadCrumb(eq(mContext), anyString(), anyString()); } @Test @@ -132,7 +156,7 @@ public class InstalledAppResultLoaderTest { } @Test - public void query_matchingQuery_shouldNOtReturnSystemAppIfNotLaunchable() { + public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() { when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt())) .thenReturn(Arrays.asList( ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM, @@ -146,6 +170,8 @@ public class InstalledAppResultLoaderTest { mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query); assertThat(mLoader.loadInBackground()).isEmpty(); + verify(mSiteMapManager, never()) + .buildBreadCrumb(eq(mContext), anyString(), anyString()); } @Test diff --git a/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java b/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java new file mode 100644 index 00000000000..b8ac8feb0e0 --- /dev/null +++ b/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java @@ -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 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 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 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); + } + } +}