diff options
author | Xin Li <delphij@google.com> | 2023-10-05 15:41:54 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2023-10-05 15:41:54 -0700 |
commit | 66037ac4fc1231c116f3558e879c1c348e732490 (patch) | |
tree | e03ee5b2ce939420cf29785789da31489cd3d724 | |
parent | 0220695dbadc23a09d1afb5a75f49eb6d217ff81 (diff) | |
parent | 416e2147ab1d0a14751f0423c8d26cc62ee60882 (diff) | |
download | SettingsIntelligence-66037ac4fc1231c116f3558e879c1c348e732490.tar.gz |
Merge Android 14
Bug: 298295554
Merged-In: I113e998ac78012d1ad6d24a0855d9612c9f33bda
Change-Id: I6a1dfb47a4bb95ffad5d0b747705cd683b3cf826
16 files changed, 457 insertions, 72 deletions
@@ -33,6 +33,7 @@ android_library { "androidx.preference_preference", "androidx.recyclerview_recyclerview", "androidx.legacy_legacy-preference-v14", + "SettingsLibActivityEmbedding", ], proto: { type: "nano", diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d776e59..4d49922 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -49,7 +49,8 @@ <activity android:name=".search.SearchActivity" android:exported="true" - android:theme="@style/Theme.Settings.NoActionBar"> + android:theme="@style/Theme.Settings.NoActionBar" + android:windowSoftInputMode="adjustResize"> <intent-filter priority="-1"> <action android:name="com.android.settings.action.SETTINGS_SEARCH" /> <action android:name="android.settings.APP_SEARCH_SETTINGS" /> diff --git a/src/com/android/settings/intelligence/search/IntentSearchViewHolder.java b/src/com/android/settings/intelligence/search/IntentSearchViewHolder.java index b643368..ee574aa 100644 --- a/src/com/android/settings/intelligence/search/IntentSearchViewHolder.java +++ b/src/com/android/settings/intelligence/search/IntentSearchViewHolder.java @@ -16,13 +16,16 @@ */ package com.android.settings.intelligence.search; +import static com.android.settings.intelligence.search.indexing.DatabaseIndexingUtils.SEARCH_RESULT_TRAMPOLINE_ACTION; + import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import androidx.annotation.VisibleForTesting; import android.util.Log; import android.view.View; +import androidx.annotation.VisibleForTesting; + import com.android.settings.intelligence.nano.SettingsIntelligenceLogProto; import java.util.List; @@ -59,8 +62,11 @@ public class IntentSearchViewHolder extends SearchViewHolder { final Intent intent = result.payload.getIntent(); // Use app user id to support work profile use case. if (result instanceof AppSearchResult) { - AppSearchResult appResult = (AppSearchResult) result; - fragment.getActivity().startActivity(intent); + if (SEARCH_RESULT_TRAMPOLINE_ACTION.equals(intent.getAction())) { + fragment.startActivityForResult(intent, REQUEST_CODE_NO_OP); + } else { + fragment.startActivity(intent); + } } else { final PackageManager pm = fragment.getActivity().getPackageManager(); final List<ResolveInfo> info = pm.queryIntentActivities(intent, 0 /* flags */); diff --git a/src/com/android/settings/intelligence/search/SearchFragment.java b/src/com/android/settings/intelligence/search/SearchFragment.java index 97445ff..86b5b37 100644 --- a/src/com/android/settings/intelligence/search/SearchFragment.java +++ b/src/com/android/settings/intelligence/search/SearchFragment.java @@ -293,6 +293,7 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi } public void onSearchResultClicked(SearchViewHolder resultViewHolder, SearchResult result) { + hideKeyboard(); logSearchResultClicked(resultViewHolder, result); mSearchFeatureProvider.searchResultClicked(getContext(), mQuery, result); mSavedQueryController.saveQuery(mQuery); diff --git a/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingManager.java b/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingManager.java index 32add54..146b730 100644 --- a/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingManager.java +++ b/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingManager.java @@ -16,13 +16,13 @@ package com.android.settings.intelligence.search.indexing; -import static com.android.settings.intelligence.search.query.DatabaseResultTask.SELECT_COLUMNS; -import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.DATA_AUTHORITY; -import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.DATA_PACKAGE; +import static com.android.settings.intelligence.search.SearchFeatureProvider.DEBUG; import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.CLASS_NAME; +import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.DATA_AUTHORITY; import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.DATA_ENTRIES; import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS; import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.DATA_KEY_REF; +import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.DATA_PACKAGE; import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON; import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED; import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.DATA_TITLE; @@ -35,8 +35,9 @@ import static com.android.settings.intelligence.search.indexing.IndexDatabaseHel import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.PAYLOAD; import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE; import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.SCREEN_TITLE; +import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.IndexColumns.TOP_LEVEL_MENU_KEY; import static com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX; -import static com.android.settings.intelligence.search.SearchFeatureProvider.DEBUG; +import static com.android.settings.intelligence.search.query.DatabaseResultTask.SELECT_COLUMNS; import android.content.ContentValues; import android.content.Context; @@ -47,13 +48,15 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.os.AsyncTask; import android.provider.SearchIndexablesContract; -import androidx.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import androidx.annotation.VisibleForTesting; + import com.android.settings.intelligence.nano.SettingsIntelligenceLogProto; import com.android.settings.intelligence.overlay.FeatureFactory; +import com.android.settings.intelligence.search.sitemap.HighlightableMenu; import com.android.settings.intelligence.search.sitemap.SiteMapPair; import java.util.List; @@ -160,9 +163,21 @@ public class DatabaseIndexingManager { database.beginTransaction(); // Convert all Pre-index data to Index data and and insert to db. - final List<IndexData> indexData = getIndexData(preIndexData); - insertIndexData(database, indexData); + List<IndexData> indexData = getIndexData(preIndexData); + + // Load SiteMap before writing index data into DB for updating payload insertSiteMapData(database, getSiteMapPairs(indexData, preIndexData.getSiteMapPairs())); + // Flag to re-init site map data in SiteMapManager. + FeatureFactory.get(mContext).searchFeatureProvider() + .getSiteMapManager() + .setInitialized(false); + + // Update payload based on the site map to find the top menu entry + if (HighlightableMenu.isFeatureEnabled(mContext)) { + indexData = updateIndexDataPayload(mContext, indexData); + } + + insertIndexData(database, indexData); // Only check for non-indexable key updates after initial index. // Enabled state with non-indexable keys is checked when items are first inserted. @@ -178,7 +193,7 @@ public class DatabaseIndexingManager { private List<IndexData> getIndexData(PreIndexData data) { if (mConverter == null) { - mConverter = getIndexDataConverter(mContext); + mConverter = getIndexDataConverter(); } return mConverter.convertPreIndexDataToIndexData(data); } @@ -186,7 +201,7 @@ public class DatabaseIndexingManager { private List<SiteMapPair> getSiteMapPairs(List<IndexData> indexData, List<Pair<String, String>> siteMapClassNames) { if (mConverter == null) { - mConverter = getIndexDataConverter(mContext); + mConverter = getIndexDataConverter(); } return mConverter.convertSiteMapPairs(indexData, siteMapClassNames); } @@ -201,6 +216,13 @@ public class DatabaseIndexingManager { } } + private List<IndexData> updateIndexDataPayload(Context context, List<IndexData> indexData) { + if (mConverter == null) { + mConverter = getIndexDataConverter(); + } + return mConverter.updateIndexDataPayload(context, indexData); + } + /** * Inserts all of the entries in {@param indexData} into the {@param database} * as Search Data and as part of the Information Hierarchy. @@ -232,6 +254,7 @@ public class DatabaseIndexingManager { values.put(DATA_KEY_REF, dataRow.key); values.put(PAYLOAD_TYPE, dataRow.payloadType); values.put(PAYLOAD, dataRow.payload); + values.put(TOP_LEVEL_MENU_KEY, dataRow.topLevelMenuKey); database.replaceOrThrow(TABLE_PREFS_INDEX, null, values); } @@ -316,8 +339,13 @@ public class DatabaseIndexingManager { * Protected method to get a new IndexDataConverter instance. This method can be overridden * in subclasses to substitute in a custom IndexDataConverter. */ + protected IndexDataConverter getIndexDataConverter() { + return new IndexDataConverter(); + } + + @Deprecated protected IndexDataConverter getIndexDataConverter(Context context) { - return new IndexDataConverter(context); + return getIndexDataConverter(); } public class IndexingTask extends AsyncTask<Void, Void, Void> { @@ -355,4 +383,4 @@ public class DatabaseIndexingManager { } } } -}
\ No newline at end of file +} diff --git a/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingUtils.java b/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingUtils.java index 534a20c..c7afcd4 100644 --- a/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingUtils.java +++ b/src/com/android/settings/intelligence/search/indexing/DatabaseIndexingUtils.java @@ -16,9 +16,13 @@ package com.android.settings.intelligence.search.indexing; +import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY; +import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI; + import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.text.TextUtils; /** @@ -44,16 +48,51 @@ public class DatabaseIndexingUtils { public static final String SEARCH_RESULT_TRAMPOLINE_ACTION = "com.android.settings.SEARCH_RESULT_TRAMPOLINE"; + // Additional extra of Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK. + // Set & get Uri of the Intent separately to prevent failure of Intent#ParseUri. + private static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA = + "settings_large_screen_deep_link_intent_data"; + /** * Builds intent that launches the search destination as a sub-setting. */ - public static Intent buildSearchTrampolineIntent(Context context, String className, String key, - String screenTitle) { + public static Intent buildSearchTrampolineIntent(String className, String key, + String screenTitle, String highlightMenuKey) { final Intent intent = new Intent(SEARCH_RESULT_TRAMPOLINE_ACTION); intent.putExtra(EXTRA_SHOW_FRAGMENT, className) .putExtra(EXTRA_SHOW_FRAGMENT_TITLE, screenTitle) .putExtra(EXTRA_SOURCE_METRICS_CATEGORY, DASHBOARD_SEARCH_RESULTS) - .putExtra(EXTRA_FRAGMENT_ARG_KEY, key); + .putExtra(EXTRA_FRAGMENT_ARG_KEY, key) + .putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, highlightMenuKey); + return intent; + } + + /** + * Builds intent that launches the search destination as a deep link. + */ + public static Intent buildSearchTrampolineIntent(String action, String targetPackage, + String targetClass, String key, String highlightMenuKey) { + final Intent intent = new Intent(SEARCH_RESULT_TRAMPOLINE_ACTION); + intent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI, + buildDirectSearchResultIntent(action, targetPackage, targetClass, key) + .toUri(Intent.URI_INTENT_SCHEME)) + .putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, highlightMenuKey); + return intent; + } + + /** + * Builds intent that launches the search destination as a deep link. + */ + public static Intent buildSearchTrampolineIntent(Intent targetIntent, String highlightMenuKey) { + // Relay target intent data to prevent future failure of Intent#ParseUri. + final Uri data = targetIntent.getData(); + targetIntent = new Intent(targetIntent); + targetIntent.setData(null); + final Intent intent = new Intent(SEARCH_RESULT_TRAMPOLINE_ACTION); + intent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI, + targetIntent.toUri(Intent.URI_INTENT_SCHEME)) + .putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, highlightMenuKey) + .putExtra(EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA, data); return intent; } @@ -64,6 +103,7 @@ public class DatabaseIndexingUtils { final ComponentName component = new ComponentName(targetPackage, targetClass); intent.setComponent(component); } + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return intent; } diff --git a/src/com/android/settings/intelligence/search/indexing/IndexData.java b/src/com/android/settings/intelligence/search/indexing/IndexData.java index 7318eaa..75de062 100644 --- a/src/com/android/settings/intelligence/search/indexing/IndexData.java +++ b/src/com/android/settings/intelligence/search/indexing/IndexData.java @@ -57,7 +57,10 @@ public class IndexData { public final String key; public final int payloadType; public final byte[] payload; + public final String highlightableMenuKey; // the key of top level settings row + public final String topLevelMenuKey; // the key for highlighting the menu entry + private final Builder mBuilder; private static final String NON_BREAKING_HYPHEN = "\u2011"; private static final String EMPTY = ""; private static final String HYPHEN = "-"; @@ -96,6 +99,14 @@ public class IndexData { payloadType = builder.mPayloadType; payload = builder.mPayload != null ? ResultPayloadUtils.marshall(builder.mPayload) : null; + highlightableMenuKey = builder.mHighlightableMenuKey; + topLevelMenuKey = builder.mTopLevelMenuKey; + mBuilder = builder; + } + + /** Returns the builder of the IndexData. */ + public Builder mutate() { + return mBuilder; } @Override @@ -168,6 +179,8 @@ public class IndexData { @ResultPayload.PayloadType private int mPayloadType; private ResultPayload mPayload; + private String mHighlightableMenuKey; + private String mTopLevelMenuKey; @Override public String toString() { @@ -277,6 +290,17 @@ public class IndexData { return this; } + public Builder setHighlightableMenuKey(String highlightableMenuKey) { + mHighlightableMenuKey = highlightableMenuKey; + return this; + } + + Builder setTopLevelMenuKey(String topLevelMenuKey) { + mTopLevelMenuKey = topLevelMenuKey; + mPayload = null; // clear the payload to rebuild intent + return this; + } + /** * Payload type is added when a Payload is added to the Builder in {setPayload} * @@ -292,11 +316,11 @@ public class IndexData { * Adds intent to inline payloads, or creates an Intent Payload as a fallback if the * payload is null. */ - private void setIntent(Context context) { + private void setIntent() { if (mPayload != null) { return; } - final Intent intent = buildIntent(context); + final Intent intent = buildIntent(); mPayload = new ResultPayload(intent); mPayloadType = ResultPayload.PayloadType.INTENT; } @@ -305,25 +329,35 @@ public class IndexData { * Builds Intent payload for the builder. * This protected method that can be overridden in a subclass for custom intents. */ - protected Intent buildIntent(Context context) { + protected Intent buildIntent() { final Intent intent; // TODO REFACTOR (b/62807132) With inline results re-add proper intent support boolean isEmptyIntentAction = TextUtils.isEmpty(mIntentAction); if (isEmptyIntentAction) { // No intent action is set, or the intent action is for a sub-setting. - intent = DatabaseIndexingUtils.buildSearchTrampolineIntent(context, mClassName, - mKey, mScreenTitle); + intent = DatabaseIndexingUtils.buildSearchTrampolineIntent(mClassName, mKey, + mScreenTitle, mTopLevelMenuKey); } else { - intent = DatabaseIndexingUtils.buildDirectSearchResultIntent(mIntentAction, - mIntentTargetPackage, mIntentTargetClass, mKey); + if (!TextUtils.isEmpty(mTopLevelMenuKey)) { + intent = DatabaseIndexingUtils.buildSearchTrampolineIntent(mIntentAction, + mIntentTargetPackage, mIntentTargetClass, mKey, mTopLevelMenuKey); + } else { + intent = DatabaseIndexingUtils.buildDirectSearchResultIntent(mIntentAction, + mIntentTargetPackage, mIntentTargetClass, mKey); + } } return intent; } - public IndexData build(Context context) { - setIntent(context); + @Deprecated + protected Intent buildIntent(Context context) { + return buildIntent(); + } + + public IndexData build() { + setIntent(); return new IndexData(this); } } -}
\ No newline at end of file +} diff --git a/src/com/android/settings/intelligence/search/indexing/IndexDataConverter.java b/src/com/android/settings/intelligence/search/indexing/IndexDataConverter.java index f57ce6b..670bb70 100644 --- a/src/com/android/settings/intelligence/search/indexing/IndexDataConverter.java +++ b/src/com/android/settings/intelligence/search/indexing/IndexDataConverter.java @@ -34,6 +34,8 @@ import android.util.Xml; import com.android.settings.intelligence.search.ResultPayload; import com.android.settings.intelligence.search.SearchFeatureProvider; import com.android.settings.intelligence.search.SearchIndexableRaw; +import com.android.settings.intelligence.search.sitemap.HighlightableMenu; +import com.android.settings.intelligence.search.sitemap.SiteMapManager; import com.android.settings.intelligence.search.sitemap.SiteMapPair; import org.xmlpull.v1.XmlPullParser; @@ -54,15 +56,17 @@ public class IndexDataConverter { private static final String TAG = "IndexDataConverter"; + private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; private static final String NODE_NAME_PREFERENCE_SCREEN = "PreferenceScreen"; private static final String NODE_NAME_CHECK_BOX_PREFERENCE = "CheckBoxPreference"; private static final String NODE_NAME_LIST_PREFERENCE = "ListPreference"; private static final List<String> SKIP_NODES = Arrays.asList("intent", "extra"); - private final Context mContext; + public IndexDataConverter() { + } + @Deprecated public IndexDataConverter(Context context) { - mContext = context; } /** @@ -86,7 +90,7 @@ public class IndexDataConverter { if (data instanceof SearchIndexableRaw) { final SearchIndexableRaw rawData = (SearchIndexableRaw) data; final Set<String> rawNonIndexableKeys = nonIndexableKeys.get(authority); - final IndexData convertedRaw = convertRaw(mContext, authority, rawData, + final IndexData convertedRaw = convertRaw(authority, rawData, rawNonIndexableKeys); if (convertedRaw != null) { indexData.add(convertedRaw); @@ -130,7 +134,7 @@ public class IndexDataConverter { classToTitleMap.put(row.className, row.screenTitle); if (!TextUtils.isEmpty(row.childClassName)) { pairs.add(new SiteMapPair(row.className, row.screenTitle, - row.childClassName, row.updatedTitle)); + row.childClassName, row.updatedTitle, row.highlightableMenuKey)); } } // Step 2: Extend the sitemap pairs by adding dynamic pairs provided by @@ -143,20 +147,41 @@ public class IndexDataConverter { Log.w(TAG, "Cannot build sitemap pair for incomplete names " + pair + parentName + childName); } else { - pairs.add(new SiteMapPair(pair.first, parentName, pair.second, childName)); + pairs.add(new SiteMapPair(pair.first, parentName, pair.second, childName, + null /* highlightableMenuKey*/)); } } // Done return pairs; } + public List<IndexData> updateIndexDataPayload(Context context, List<IndexData> indexData) { + final long startTime = System.currentTimeMillis(); + final List<IndexData> updatedIndexData = new ArrayList<>(indexData); + for (IndexData row : indexData) { + String menuKey = row.highlightableMenuKey; + if (!TextUtils.isEmpty(menuKey)) { + // top level settings + continue; + } + menuKey = HighlightableMenu.getMenuKey(context, row); + if (TextUtils.isEmpty(menuKey)) { + continue; + } + updatedIndexData.remove(row); + updatedIndexData.add(row.mutate().setTopLevelMenuKey(menuKey).build()); + } + Log.d(TAG, "Updating index data payload took: " + (System.currentTimeMillis() - startTime)); + return updatedIndexData; + } + /** * Return the conversion of {@link SearchIndexableRaw} to {@link IndexData}. * The fields of {@link SearchIndexableRaw} are a subset of {@link IndexData}, * and there is some data sanitization in the conversion. */ @Nullable - private IndexData convertRaw(Context context, String authority, SearchIndexableRaw raw, + private IndexData convertRaw(String authority, SearchIndexableRaw raw, Set<String> nonIndexableKeys) { if (TextUtils.isEmpty(raw.key)) { Log.w(TAG, "Skipping null key for raw indexable " + authority + "/" + raw.title); @@ -181,7 +206,7 @@ public class IndexDataConverter { .setAuthority(authority) .setKey(raw.key); - return builder.build(context); + return builder.build(); } /** @@ -227,6 +252,7 @@ public class IndexDataConverter { String keywords; String headerKeywords; String childFragment; + String highlightableMenuKey = null; @DrawableRes int iconResId; ResultPayload payload; boolean enabled; @@ -281,6 +307,11 @@ public class IndexDataConverter { enabled = !nonIndexableKeys.contains(key); keywords = XmlParserUtils.getDataKeywords(context, attrs); iconResId = XmlParserUtils.getDataIcon(context, attrs); + if (TextUtils.equals(sir.packageName, SETTINGS_PACKAGE_NAME) + && SiteMapManager.isTopLevelSettings(sir.className)) { + highlightableMenuKey = XmlParserUtils.getHighlightableMenuKey(context, attrs); + } + if (isHeaderUnique && TextUtils.equals(headerTitle, title)) { isHeaderUnique = false; @@ -298,6 +329,8 @@ public class IndexDataConverter { .setIntentTargetPackage(sir.intentTargetPackage) .setIntentTargetClass(sir.intentTargetClass) .setEnabled(enabled) + .setHighlightableMenuKey(highlightableMenuKey) + .setTopLevelMenuKey(highlightableMenuKey) .setKey(key); if (!nodeName.equals(NODE_NAME_CHECK_BOX_PREFERENCE)) { @@ -354,7 +387,7 @@ public class IndexDataConverter { private void tryAddIndexDataToList(List<IndexData> list, IndexData.Builder data) { if (!TextUtils.isEmpty(data.getKey())) { - list.add(data.build(mContext)); + list.add(data.build()); } else { Log.w(TAG, "Skipping index for null-key item " + data); } diff --git a/src/com/android/settings/intelligence/search/indexing/IndexDatabaseHelper.java b/src/com/android/settings/intelligence/search/indexing/IndexDatabaseHelper.java index b9211e7..cd61350 100644 --- a/src/com/android/settings/intelligence/search/indexing/IndexDatabaseHelper.java +++ b/src/com/android/settings/intelligence/search/indexing/IndexDatabaseHelper.java @@ -40,7 +40,7 @@ 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 = 120; + private static final int DATABASE_VERSION = 121; @VisibleForTesting static final String SHARED_PREFS_TAG = "indexing_manager"; @@ -75,6 +75,7 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { String DATA_KEY_REF = "data_key_reference"; String PAYLOAD_TYPE = "payload_type"; String PAYLOAD = "payload"; + String TOP_LEVEL_MENU_KEY = "top_level_menu_key"; } public interface MetaColumns { @@ -92,6 +93,7 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { String CHILD_CLASS = "child_class"; String PARENT_TITLE = "parent_title"; String CHILD_TITLE = "child_title"; + String HIGHLIGHTABLE_MENU_KEY = "highlightable_menu_key"; } private static final String CREATE_INDEX_TABLE = @@ -136,6 +138,8 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { IndexColumns.PAYLOAD_TYPE + ", " + IndexColumns.PAYLOAD + + ", " + + IndexColumns.TOP_LEVEL_MENU_KEY + ");"; private static final String CREATE_META_TABLE = @@ -162,6 +166,8 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { SiteMapColumns.PARENT_TITLE + ", " + SiteMapColumns.CHILD_TITLE + + ", " + + SiteMapColumns.HIGHLIGHTABLE_MENU_KEY + ")"; private static final String INSERT_BUILD_VERSION = "INSERT INTO " + Tables.TABLE_META_INDEX + diff --git a/src/com/android/settings/intelligence/search/indexing/XmlParserUtils.java b/src/com/android/settings/intelligence/search/indexing/XmlParserUtils.java index 4efbde1..d3e5c25 100644 --- a/src/com/android/settings/intelligence/search/indexing/XmlParserUtils.java +++ b/src/com/android/settings/intelligence/search/indexing/XmlParserUtils.java @@ -76,25 +76,7 @@ public class XmlParserUtils { if (TextUtils.isEmpty(keywordRes)) { return null; } - // The format of keyword is either a string, or @ followed by int (@123456). - // When it's int, we need to look up the actual string from context. - if (!keywordRes.startsWith("@")) { - // It's a string. - return keywordRes; - } else { - // It's a resource - try { - final int resValue = Integer.parseInt(keywordRes.substring(1)); - if (DevicePolicyResourcesUtils.isDevicePolicyResource(context, resValue)) { - return DevicePolicyResourcesUtils.getDevicePolicyResource(context, resValue); - } else { - return context.getString(resValue); - } - } catch (NumberFormatException e) { - Log.w(TAG, "Failed to parse keyword attribute, skipping " + keywordRes); - return null; - } - } + return getString(context, keywordRes); } public static int getDataIcon(Context context, AttributeSet attrs) { @@ -112,6 +94,17 @@ public class XmlParserUtils { R.styleable.Preference_android_fragment); } + /** + * Returns if the attrs contains highlightableMenuKey="" element. + */ + public static String getHighlightableMenuKey(Context context, AttributeSet attrs) { + String menuKey = attrs.getAttributeValue(NS_APP_RES_AUTO, "highlightableMenuKey"); + if (!TextUtils.isEmpty(menuKey)) { + menuKey = getString(context, menuKey); + } + return menuKey; + } + @Nullable private static String getData(Context context, AttributeSet set, int[] attrs, int resId) { final TypedArray ta = context.obtainStyledAttributes(set, attrs); @@ -125,7 +118,6 @@ public class XmlParserUtils { return data; } - private static String getDataEntries(Context context, AttributeSet set, int[] attrs, int resId) { final TypedArray sa = context.obtainStyledAttributes(set, attrs); @@ -148,4 +140,26 @@ public class XmlParserUtils { } return result.toString(); } + + private static String getString(Context context, String res) { + // The format of the resource is either a string, or @ followed by int (@123456). + // When it's int, we need to look up the actual string from context. + if (!res.startsWith("@")) { + // It's a string. + return res; + } else { + // It's a resource + try { + final int resValue = Integer.parseInt(res.substring(1)); + if (DevicePolicyResourcesUtils.isDevicePolicyResource(context, resValue)) { + return DevicePolicyResourcesUtils.getDevicePolicyResource(context, resValue); + } else { + return context.getString(resValue); + } + } catch (NumberFormatException e) { + Log.w(TAG, "Failed to parse keyword attribute, skipping " + res); + return null; + } + } + } } diff --git a/src/com/android/settings/intelligence/search/query/AccessibilityServiceResultTask.java b/src/com/android/settings/intelligence/search/query/AccessibilityServiceResultTask.java index d6de496..05d0a54 100644 --- a/src/com/android/settings/intelligence/search/query/AccessibilityServiceResultTask.java +++ b/src/com/android/settings/intelligence/search/query/AccessibilityServiceResultTask.java @@ -16,6 +16,8 @@ package com.android.settings.intelligence.search.query; +import static com.android.settings.intelligence.search.sitemap.HighlightableMenu.MENU_KEY_ACCESSIBILITY; + import android.accessibilityservice.AccessibilityServiceInfo; import android.content.ComponentName; import android.content.Context; @@ -85,9 +87,9 @@ public class AccessibilityServiceResultTask extends SearchQueryTask.QueryWorker final Drawable icon = serviceInfo.loadIcon(mPackageManager); final String componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToString(); - final Intent intent = DatabaseIndexingUtils.buildSearchTrampolineIntent(mContext, + final Intent intent = DatabaseIndexingUtils.buildSearchTrampolineIntent( mContext.getString(R.string.accessibility_settings_classname), - componentName, screenTitle); + componentName, screenTitle, MENU_KEY_ACCESSIBILITY); results.add(new SearchResult.Builder() .setTitle(title) diff --git a/src/com/android/settings/intelligence/search/query/InputDeviceResultTask.java b/src/com/android/settings/intelligence/search/query/InputDeviceResultTask.java index 4cad4cc..f4bd77d 100644 --- a/src/com/android/settings/intelligence/search/query/InputDeviceResultTask.java +++ b/src/com/android/settings/intelligence/search/query/InputDeviceResultTask.java @@ -16,6 +16,8 @@ package com.android.settings.intelligence.search.query; +import static com.android.settings.intelligence.search.sitemap.HighlightableMenu.MENU_KEY_SYSTEM; + import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -108,8 +110,8 @@ public class InputDeviceResultTask extends SearchQueryTask.QueryWorker { if (wordDiff == NAME_NO_MATCH) { continue; } - final Intent intent = DatabaseIndexingUtils.buildSearchTrampolineIntent(mContext, - PHYSICAL_KEYBOARD_FRAGMENT, deviceName, screenTitle); + final Intent intent = DatabaseIndexingUtils.buildSearchTrampolineIntent( + PHYSICAL_KEYBOARD_FRAGMENT, deviceName, screenTitle, MENU_KEY_SYSTEM); results.add(new SearchResult.Builder() .setTitle(deviceName) .setPayload(new ResultPayload(intent)) @@ -139,8 +141,8 @@ public class InputDeviceResultTask extends SearchQueryTask.QueryWorker { final ServiceInfo serviceInfo = info.getServiceInfo(); final String key = new ComponentName(serviceInfo.packageName, serviceInfo.name) .flattenToString(); - final Intent intent = DatabaseIndexingUtils.buildSearchTrampolineIntent(mContext, - VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle); + final Intent intent = DatabaseIndexingUtils.buildSearchTrampolineIntent( + VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle, MENU_KEY_SYSTEM); results.add(new SearchResult.Builder() .setTitle(title) .setSummary(summary) diff --git a/src/com/android/settings/intelligence/search/query/InstalledAppResultTask.java b/src/com/android/settings/intelligence/search/query/InstalledAppResultTask.java index 6732765..6d9060e 100644 --- a/src/com/android/settings/intelligence/search/query/InstalledAppResultTask.java +++ b/src/com/android/settings/intelligence/search/query/InstalledAppResultTask.java @@ -16,6 +16,8 @@ package com.android.settings.intelligence.search.query; +import static com.android.settings.intelligence.search.sitemap.HighlightableMenu.MENU_KEY_APPS; + import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -29,6 +31,7 @@ import com.android.settings.intelligence.search.AppSearchResult; import com.android.settings.intelligence.search.ResultPayload; import com.android.settings.intelligence.search.SearchResult; import com.android.settings.intelligence.search.indexing.DatabaseIndexingUtils; +import com.android.settings.intelligence.search.sitemap.HighlightableMenu; import com.android.settings.intelligence.search.sitemap.SiteMapManager; import java.util.ArrayList; @@ -40,11 +43,11 @@ import java.util.List; */ public class InstalledAppResultTask extends SearchQueryTask.QueryWorker { - public static final int QUERY_WORKER_ID = + private static final int QUERY_WORKER_ID = SettingsIntelligenceLogProto.SettingsIntelligenceEvent.SEARCH_QUERY_INSTALLED_APPS; + private static final String INTENT_SCHEME = "package"; private final PackageManager mPackageManager; - private final String INTENT_SCHEME = "package"; private List<String> mBreadcrumb; public static SearchQueryTask newTask(Context context, SiteMapManager siteMapManager, @@ -85,12 +88,14 @@ public class InstalledAppResultTask extends SearchQueryTask.QueryWorker { continue; } - final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + final Intent targetIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) .setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - .setData( - Uri.fromParts(INTENT_SCHEME, info.packageName, null /* fragment */)) + .setData(Uri.fromParts(INTENT_SCHEME, info.packageName, null /* fragment */)) .putExtra(DatabaseIndexingUtils.EXTRA_SOURCE_METRICS_CATEGORY, DatabaseIndexingUtils.DASHBOARD_SEARCH_RESULTS); + final Intent intent = HighlightableMenu.isFeatureEnabled(mContext) + ? DatabaseIndexingUtils.buildSearchTrampolineIntent(targetIntent, MENU_KEY_APPS) + : targetIntent; final AppSearchResult.Builder builder = new AppSearchResult.Builder(); builder.setAppInfo(info) diff --git a/src/com/android/settings/intelligence/search/sitemap/HighlightableMenu.java b/src/com/android/settings/intelligence/search/sitemap/HighlightableMenu.java new file mode 100644 index 0000000..dc6a96a --- /dev/null +++ b/src/com/android/settings/intelligence/search/sitemap/HighlightableMenu.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 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.intelligence.search.sitemap; + +import android.content.Context; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.settings.intelligence.overlay.FeatureFactory; +import com.android.settings.intelligence.search.indexing.IndexData; +import com.android.settingslib.activityembedding.ActivityEmbeddingUtils; + +import java.util.Map; +import java.util.Map.Entry; + +public class HighlightableMenu { + + private static final String TAG = "HighlightableMenu"; + private static final boolean DEBUG = false; + + public static final String MENU_KEY_NETWORK = "top_level_network"; + public static final String MENU_KEY_APPS = "top_level_apps"; + public static final String MENU_KEY_ACCESSIBILITY = "top_level_accessibility"; + public static final String MENU_KEY_PRIVACY = "top_level_privacy"; + public static final String MENU_KEY_SYSTEM = "top_level_system"; + + private static final Map<String, String> sAuthorityToMenuKeyMap; + private static final Map<String, String> sPackageToMenuKeyMap; + + static { + sAuthorityToMenuKeyMap = new ArrayMap<>(); + sAuthorityToMenuKeyMap.put( + "com.android.permissioncontroller.role", MENU_KEY_APPS); // Default apps + + sPackageToMenuKeyMap = new ArrayMap<>(); + sPackageToMenuKeyMap.put( + "com.android.settings.network", MENU_KEY_NETWORK); // Settings Network page + sPackageToMenuKeyMap.put( + "com.android.permissioncontroller", MENU_KEY_PRIVACY); // Permission manager + } + + private HighlightableMenu() { + } + + public static boolean isFeatureEnabled(Context context) { + boolean enabled = ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context); + Log.i(TAG, "isFeatureEnabled: " + enabled); + return enabled; + } + + public static String getMenuKey(Context context, IndexData row) { + String menuKey; + SiteMapManager siteMap = FeatureFactory.get(context).searchFeatureProvider() + .getSiteMapManager(); + + // look up in SiteMap + SiteMapPair pair = siteMap.getTopLevelPair(context, row.className, row.screenTitle); + if (pair != null) { + menuKey = pair.getHighlightableMenuKey(); + if (!TextUtils.isEmpty(menuKey)) { + return menuKey; + } + } + + // look up in custom authority map + menuKey = sAuthorityToMenuKeyMap.get(row.authority); + if (!TextUtils.isEmpty(menuKey)) { + logD("Matched authority, title: " + row.updatedTitle + ", menuKey: " + menuKey); + return menuKey; + } + + // look up in custom package map (package match) + menuKey = sPackageToMenuKeyMap.get(row.packageName); + if (!TextUtils.isEmpty(menuKey)) { + logD("Matched package, title: " + row.updatedTitle + ", menuKey: " + menuKey); + return menuKey; + } + + // look up in custom package map (target package match) + menuKey = sPackageToMenuKeyMap.get(row.intentTargetPackage); + if (!TextUtils.isEmpty(menuKey)) { + logD("Matched target package, title: " + row.updatedTitle + ", menuKey: " + menuKey); + return menuKey; + } + + // look up in custom package map (class prefix match) + if (!TextUtils.isEmpty(row.className)) { + for (Entry<String, String> entry : sPackageToMenuKeyMap.entrySet()) { + if (row.className.startsWith(entry.getKey())) { + menuKey = entry.getValue(); + if (!TextUtils.isEmpty(menuKey)) { + logD("Matched class prefix, title: " + row.updatedTitle + + ", menuKey: " + menuKey); + return menuKey; + } + } + } + } + + logD("Cannot get menu key for: " + row.updatedTitle + + ", data key: " + row.key + + ", top-level: " + (pair != null ? pair.getParentTitle() : row.screenTitle) + + ", package: " + row.packageName); + return menuKey; + } + + private static void logD(String log) { + if (DEBUG) { + Log.d(TAG, log); + } + } +} diff --git a/src/com/android/settings/intelligence/search/sitemap/SiteMapManager.java b/src/com/android/settings/intelligence/search/sitemap/SiteMapManager.java index 6575cab..a6e76b5 100644 --- a/src/com/android/settings/intelligence/search/sitemap/SiteMapManager.java +++ b/src/com/android/settings/intelligence/search/sitemap/SiteMapManager.java @@ -32,13 +32,16 @@ import java.util.List; public class SiteMapManager { private static final String TAG = "SiteMapManager"; + private static final String TOP_LEVEL_SETTINGS + = "com.android.settings.homepage.TopLevelSettings"; private static final boolean DEBUG_TIMING = false; public static final String[] SITE_MAP_COLUMNS = { SiteMapColumns.PARENT_CLASS, SiteMapColumns.PARENT_TITLE, SiteMapColumns.CHILD_CLASS, - SiteMapColumns.CHILD_TITLE + SiteMapColumns.CHILD_TITLE, + SiteMapColumns.HIGHLIGHTABLE_MENU_KEY }; private final List<SiteMapPair> mPairs = new ArrayList<>(); @@ -46,6 +49,13 @@ public class SiteMapManager { private boolean mInitialized; /** + * Check whether the specified class is top level settings class. + */ + public static boolean isTopLevelSettings(String clazz) { + return TextUtils.equals(TOP_LEVEL_SETTINGS, clazz); + } + + /** * Given a fragment class name and its screen title, build a breadcrumb from Settings root to * this screen. * <p/> @@ -85,6 +95,48 @@ public class SiteMapManager { } } + public synchronized SiteMapPair getTopLevelPair(Context context, String clazz, + String screenTitle) { + if (!mInitialized) { + init(context); + } + + // find the default pair + SiteMapPair currentPair = null; + if (!TextUtils.isEmpty(clazz)) { + for (SiteMapPair pair : mPairs) { + if (TextUtils.equals(pair.getChildClass(), clazz)) { + currentPair = pair; + if (TextUtils.isEmpty(screenTitle)) { + screenTitle = pair.getChildTitle(); + } + break; + } + } + } + + // recursively find the top level pair + String currentClass = clazz; + String currentTitle = screenTitle; + while (true) { + // look up parent by class and title + SiteMapPair pair = lookUpParent(currentClass, currentTitle); + if (pair == null) { + // fallback option: look up parent only by title + pair = lookUpParent(currentTitle); + if (pair == null) { + return currentPair; + } + } + if (!TextUtils.isEmpty(pair.getHighlightableMenuKey())) { + return pair; + } + currentPair = pair; + currentClass = pair.getParentClass(); + currentTitle = pair.getParentTitle(); + } + } + /** * Initialize a list of {@link SiteMapPair}s. Each pair knows about a single parent-child * page relationship. @@ -95,6 +147,12 @@ public class SiteMapManager { // Make sure only init once. return; } + + // Will init again if site map table updated, need clear the old data if it's not empty. + if (!mPairs.isEmpty()) { + mPairs.clear(); + } + final long startTime = System.currentTimeMillis(); // First load site map from static index table. final Context appContext = context.getApplicationContext(); @@ -106,7 +164,9 @@ public class SiteMapManager { 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))); + sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_TITLE)), + sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.HIGHLIGHTABLE_MENU_KEY)) + ); mPairs.add(pair); } sitemap.close(); @@ -128,4 +188,21 @@ public class SiteMapManager { return null; } + @WorkerThread + private SiteMapPair lookUpParent(String title) { + if (TextUtils.isEmpty(title)) { + return null; + } + for (SiteMapPair pair : mPairs) { + if (TextUtils.equals(title, pair.getChildTitle())) { + return pair; + } + } + return null; + } + + @WorkerThread + public void setInitialized(boolean initialized) { + mInitialized = initialized; + } } diff --git a/src/com/android/settings/intelligence/search/sitemap/SiteMapPair.java b/src/com/android/settings/intelligence/search/sitemap/SiteMapPair.java index cf30dc4..a92f67b 100644 --- a/src/com/android/settings/intelligence/search/sitemap/SiteMapPair.java +++ b/src/com/android/settings/intelligence/search/sitemap/SiteMapPair.java @@ -33,13 +33,15 @@ public class SiteMapPair { private final String mParentTitle; private final String mChildClass; private final String mChildTitle; + private final String mHighlightableMenuKey; - public SiteMapPair(String parentClass, String parentTitle, String childClass, - String childTitle) { + public SiteMapPair(String parentClass, String parentTitle, String childClass, String childTitle, + String highlightableMenuKey) { mParentClass = parentClass; mParentTitle = parentTitle; mChildClass = childClass; mChildTitle = childTitle; + mHighlightableMenuKey = highlightableMenuKey; } @Override @@ -72,6 +74,10 @@ public class SiteMapPair { return mChildTitle; } + public String getHighlightableMenuKey() { + return mHighlightableMenuKey; + } + /** * Converts this object into {@link ContentValues}. The content follows schema in * {@link IndexDatabaseHelper.SiteMapColumns}. @@ -83,6 +89,8 @@ public class SiteMapPair { values.put(IndexDatabaseHelper.SiteMapColumns.PARENT_TITLE, mParentTitle); values.put(IndexDatabaseHelper.SiteMapColumns.CHILD_CLASS, mChildClass); values.put(IndexDatabaseHelper.SiteMapColumns.CHILD_TITLE, mChildTitle); + values.put(IndexDatabaseHelper.SiteMapColumns.HIGHLIGHTABLE_MENU_KEY, + mHighlightableMenuKey); return values; } } |