Search experience improvement for large screen
- Support fragment and direct link in SearchResultTrampoline
- Start activity for SI case and start deep link trampoline for others
- Disable menu highlight whenever the search bar is clicked
- Don't overwrite SettingsApplication's homepage activity in
SliceDeepLinkHomepageActivity
- Scroll to highlighted menu entry after homepage is loaded to prevent
UI overlapping
Bug: 201724410
Test: manual, robotest build pass
Change-Id: I5115d17d829e85036000da2e80f0e5b0598c733f
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index df30d8b..9c81895 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -255,7 +255,8 @@
// Should happen before any call to getIntent()
getMetaData();
final Intent intent = getIntent();
- if (launchHomepageForTwoPaneDeepLink(intent)) {
+ if (shouldShowTwoPaneDeepLink(intent)) {
+ launchHomepageForTwoPaneDeepLink(intent);
finishAndRemoveTask();
return;
}
@@ -368,16 +369,13 @@
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
}
- /** Returns true if the Activity is started by a deep link intent for large screen devices. */
- private boolean launchHomepageForTwoPaneDeepLink(Intent intent) {
- if (!shouldShowTwoPaneDeepLink(intent)) {
- return false;
- }
-
+ /**
+ * Returns the deep link trampoline intent for large screen devices.
+ */
+ public static Intent getTrampolineIntent(Intent intent, String highlightMenuKey) {
final Intent detailIntent = new Intent(intent);
// It's a deep link intent, SettingsHomepageActivity will set SplitPairRule and start it.
final Intent trampolineIntent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY);
-
trampolineIntent.replaceExtras(detailIntent);
// Relay detail intent data to prevent failure of Intent#ParseUri.
@@ -391,22 +389,27 @@
trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI,
detailIntent.toUri(Intent.URI_INTENT_SCHEME));
- if (detailIntent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) {
- trampolineIntent.setClass(this, SliceDeepLinkHomepageActivity.class);
+ trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY,
+ highlightMenuKey);
+ trampolineIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ return trampolineIntent;
+ }
+
+ private void launchHomepageForTwoPaneDeepLink(Intent intent) {
+ final Intent trampolineIntent;
+ if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) {
// Get menu key for slice deep link case.
- final String highlightMenuKey = detailIntent.getStringExtra(
+ final String highlightMenuKey = intent.getStringExtra(
EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY);
if (!TextUtils.isEmpty(highlightMenuKey)) {
mHighlightMenuKey = highlightMenuKey;
}
+ trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey);
+ trampolineIntent.setClass(this, SliceDeepLinkHomepageActivity.class);
+ } else {
+ trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey);
}
-
- trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY,
- mHighlightMenuKey);
- trampolineIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
startActivity(trampolineIntent);
-
- return true;
}
private boolean shouldShowTwoPaneDeepLink(Intent intent) {
diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java
index 2bbc11e..ae8c61e 100644
--- a/src/com/android/settings/homepage/SettingsHomepageActivity.java
+++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java
@@ -27,6 +27,7 @@
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.View;
@@ -54,6 +55,7 @@
import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin;
import java.net.URISyntaxException;
+import java.util.Set;
/** Settings homepage activity */
public class SettingsHomepageActivity extends FragmentActivity implements
@@ -79,10 +81,27 @@
private View mHomepageView;
private View mSuggestionView;
private CategoryMixin mCategoryMixin;
+ private Set<HomepageLoadedListener> mLoadedListeners;
- @Override
- public CategoryMixin getCategoryMixin() {
- return mCategoryMixin;
+ /** A listener receiving homepage loaded events. */
+ public interface HomepageLoadedListener {
+ /** Called when the homepage is loaded. */
+ void onHomepageLoaded();
+ }
+
+ /**
+ * Try to register a {@link HomepageLoadedListener}. If homepage is already loaded, the
+ * listener will not be notified.
+ *
+ * @return Whether the listener should be registered.
+ */
+ public boolean registerHomepageLoadedListenerIfNeeded(HomepageLoadedListener listener) {
+ if (mHomepageView == null) {
+ return false;
+ } else {
+ mLoadedListeners.add(listener);
+ return true;
+ }
}
/**
@@ -97,17 +116,25 @@
mSuggestionView.setVisibility(showSuggestion ? View.VISIBLE : View.GONE);
mHomepageView.setVisibility(View.VISIBLE);
mHomepageView = null;
+ mLoadedListeners.forEach(listener -> listener.onHomepageLoaded());
+ mLoadedListeners.clear();
+ }
+
+ @Override
+ public CategoryMixin getCategoryMixin() {
+ return mCategoryMixin;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- ((SettingsApplication) getApplication()).setHomeActivity(this);
+ setHomeActivity();
setContentView(R.layout.settings_homepage_container);
final View appBar = findViewById(R.id.app_bar_container);
appBar.setMinimumHeight(getSearchBoxHeight());
initHomepageContainer();
+ mLoadedListeners = new ArraySet<>();
final Toolbar toolbar = findViewById(R.id.search_action_bar);
FeatureFactory.getFactory(this).getSearchFeatureProvider()
@@ -158,6 +185,10 @@
launchDeepLinkIntentToRight();
}
+ protected void setHomeActivity() {
+ ((SettingsApplication) getApplication()).setHomeActivity(this);
+ }
+
private void showSuggestionFragment() {
final Class<? extends Fragment> fragment = FeatureFactory.getFactory(this)
.getSuggestionFeatureProvider(this).getContextualSuggestionFragment();
diff --git a/src/com/android/settings/homepage/SliceDeepLinkHomepageActivity.java b/src/com/android/settings/homepage/SliceDeepLinkHomepageActivity.java
index 2f83612..61e946d 100644
--- a/src/com/android/settings/homepage/SliceDeepLinkHomepageActivity.java
+++ b/src/com/android/settings/homepage/SliceDeepLinkHomepageActivity.java
@@ -21,6 +21,11 @@
/** Activity for Slices to launch Settings deep link page */
public class SliceDeepLinkHomepageActivity extends SettingsHomepageActivity {
@Override
+ protected void setHomeActivity() {
+ // do not overwrite homepage activity in SettingsApplication
+ }
+
+ @Override
protected ComponentName getDeepLinkComponent() {
return new ComponentName(getApplicationContext(), getClass());
}
diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java
index d3bfa02..e9c7ef8 100644
--- a/src/com/android/settings/homepage/TopLevelSettings.java
+++ b/src/com/android/settings/homepage/TopLevelSettings.java
@@ -101,7 +101,7 @@
public boolean onPreferenceTreeClick(Preference preference) {
// Register SplitPairRule for SubSettings.
ActivityEmbeddingRulesController.registerSubSettingsPairRuleIfNeeded(getContext(),
- true /* clearTop*/);
+ true /* clearTop */);
setHighlightPreferenceKey(preference.getKey());
return super.onPreferenceTreeClick(preference);
@@ -184,6 +184,15 @@
}
}
+ /** Disable highlight on the menu entry */
+ public void disableMenuHighlight() {
+ if (mTopLevelAdapter == null) {
+ return;
+ }
+ mHighlightedPreferenceKey = null;
+ mTopLevelAdapter.highlightPreference(mHighlightedPreferenceKey, /* scrollNeeded= */ false);
+ }
+
@Override
protected boolean shouldForceRoundedIcon() {
return getContext().getResources()
@@ -202,7 +211,8 @@
Log.d(TAG, "onCreateAdapter, pref key: " + mHighlightedPreferenceKey);
mTopLevelAdapter = new HighlightableTopLevelPreferenceAdapter(
- getActivity(), preferenceScreen, getListView(), mHighlightedPreferenceKey);
+ (SettingsHomepageActivity) getActivity(), preferenceScreen, getListView(),
+ mHighlightedPreferenceKey);
return mTopLevelAdapter;
}
diff --git a/src/com/android/settings/search/SearchFeatureProvider.java b/src/com/android/settings/search/SearchFeatureProvider.java
index 05fd2ae..9b081e1 100644
--- a/src/com/android/settings/search/SearchFeatureProvider.java
+++ b/src/com/android/settings/search/SearchFeatureProvider.java
@@ -19,7 +19,6 @@
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
import android.annotation.NonNull;
-import android.app.Activity;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Context;
@@ -30,8 +29,12 @@
import android.view.ViewGroup;
import android.widget.Toolbar;
+import androidx.fragment.app.FragmentActivity;
+
import com.android.settings.R;
import com.android.settings.Utils;
+import com.android.settings.activityembedding.ActivityEmbeddingUtils;
+import com.android.settings.homepage.TopLevelSettings;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.search.SearchIndexableResources;
@@ -66,7 +69,7 @@
/**
* Initializes the search toolbar.
*/
- default void initSearchToolbar(Activity activity, Toolbar toolbar, int pageId) {
+ default void initSearchToolbar(FragmentActivity activity, Toolbar toolbar, int pageId) {
if (activity == null || toolbar == null) {
return;
}
@@ -91,7 +94,8 @@
toolbar.setOnClickListener(tb -> {
final Context context = activity.getApplicationContext();
- final Intent intent = buildSearchIntent(context, pageId);
+ final Intent intent = buildSearchIntent(context, pageId)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (activity.getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
@@ -103,8 +107,19 @@
FeatureFactory.getFactory(context).getMetricsFeatureProvider()
.logSettingsTileClick(KEY_HOMEPAGE_SEARCH_BAR, pageId);
- final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle();
- activity.startActivity(intent, bundle);
+
+ if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) {
+ final TopLevelSettings fragment = (TopLevelSettings) activity
+ .getSupportFragmentManager().findFragmentById(R.id.main_content);
+ if (fragment != null) {
+ fragment.disableMenuHighlight();
+ }
+ activity.startActivity(intent);
+ } else {
+ final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity)
+ .toBundle();
+ activity.startActivity(intent, bundle);
+ }
});
}
diff --git a/src/com/android/settings/search/SearchResultTrampoline.java b/src/com/android/settings/search/SearchResultTrampoline.java
index 3414efe..d20a2ea 100644
--- a/src/com/android/settings/search/SearchResultTrampoline.java
+++ b/src/com/android/settings/search/SearchResultTrampoline.java
@@ -20,52 +20,102 @@
import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB;
import android.app.Activity;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
import com.android.settings.SettingsActivity;
import com.android.settings.SubSettings;
import com.android.settings.activityembedding.ActivityEmbeddingRulesController;
+import com.android.settings.activityembedding.ActivityEmbeddingUtils;
import com.android.settings.overlay.FeatureFactory;
+import java.net.URISyntaxException;
+
/**
* A trampoline activity that launches setting result page.
*/
public class SearchResultTrampoline extends Activity {
+ private static final String TAG = "SearchResultTrampoline";
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ final ComponentName callingActivity = getCallingActivity();
// First make sure caller has privilege to launch a search result page.
FeatureFactory.getFactory(this)
.getSearchFeatureProvider()
- .verifyLaunchSearchResultPageCaller(this, getCallingActivity());
+ .verifyLaunchSearchResultPageCaller(this, callingActivity);
// Didn't crash, proceed and launch the result as a subsetting.
- final Intent intent = getIntent();
+ Intent intent = getIntent();
+ final String highlightMenuKey = intent.getStringExtra(
+ Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY);
- // Hack to take EXTRA_FRAGMENT_ARG_KEY from intent and set into
- // EXTRA_SHOW_FRAGMENT_ARGUMENTS. This is necessary because intent could be from external
- // caller and args may not persisted.
- final String settingKey = intent.getStringExtra(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY);
- final int tab = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TAB, 0);
- final Bundle args = new Bundle();
- args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, settingKey);
- args.putInt(EXTRA_SHOW_FRAGMENT_TAB, tab);
- intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
+ final String fragment = intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT);
+ if (!TextUtils.isEmpty(fragment)) {
+ // Hack to take EXTRA_FRAGMENT_ARG_KEY from intent and set into
+ // EXTRA_SHOW_FRAGMENT_ARGUMENTS. This is necessary because intent could be from
+ // external caller and args may not persisted.
+ final String settingKey = intent.getStringExtra(
+ SettingsActivity.EXTRA_FRAGMENT_ARG_KEY);
+ final int tab = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TAB, 0);
+ final Bundle args = new Bundle();
+ args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, settingKey);
+ args.putInt(EXTRA_SHOW_FRAGMENT_TAB, tab);
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
- // Register SplirPairRule for SubSettings, set clearTop false to prevent unexpected back
- // navigation behavior.
- ActivityEmbeddingRulesController.registerSubSettingsPairRuleIfNeeded(this /* context */,
- false /* clearTop*/);
+ // Reroute request to SubSetting.
+ intent.setClass(this /* context */, SubSettings.class);
+ } else {
+ // Direct link case
+ final String intentUriString = intent.getStringExtra(
+ Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI);
+ if (TextUtils.isEmpty(intentUriString)) {
+ Log.e(TAG, "No EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI for deep link");
+ finish();
+ return;
+ }
- // Reroute request to SubSetting.
- intent.setClass(this /* context */, SubSettings.class)
- .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
- startActivity(intent);
+ try {
+ intent = Intent.parseUri(intentUriString, Intent.URI_INTENT_SCHEME);
+ } catch (URISyntaxException e) {
+ Log.e(TAG, "Failed to parse deep link intent: " + e);
+ finish();
+ return;
+ }
+ }
+
+ intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+
+ if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) {
+ startActivity(intent);
+ } else if (isFromSettingsIntelligence(callingActivity)) {
+ // Register SplitPairRule for SubSettings, set clearTop false to prevent unexpected back
+ // navigation behavior.
+ ActivityEmbeddingRulesController.registerSubSettingsPairRuleIfNeeded(this,
+ false /* clearTop */);
+ // TODO: pass menu key to homepage
+ intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ } else {
+ // Two-pane case
+ intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(SettingsActivity.getTrampolineIntent(intent, highlightMenuKey));
+ }
// Done.
finish();
}
+ private boolean isFromSettingsIntelligence(ComponentName callingActivity) {
+ return callingActivity != null && TextUtils.equals(
+ callingActivity.getPackageName(),
+ FeatureFactory.getFactory(this).getSearchFeatureProvider()
+ .getSettingsIntelligencePkgName(this));
+ }
}
diff --git a/src/com/android/settings/search/SettingsSearchIndexablesProvider.java b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
index feb9510..d6635a1 100644
--- a/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
+++ b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
@@ -365,7 +365,6 @@
// The classname and intent information comes from the PreIndexData
// This will be more clear when provider conversion is done at PreIndex time.
raw.className = bundle.getTargetClass().getName();
-
}
rawList.addAll(providerRaws);
}
diff --git a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java
index 19a91f6..bf92bbd 100644
--- a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java
+++ b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java
@@ -16,7 +16,6 @@
package com.android.settings.widget;
-import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
@@ -34,6 +33,7 @@
import com.android.settings.Utils;
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
+import com.android.settings.homepage.SettingsHomepageActivity;
/**
* Adapter for highlighting top level preferences
@@ -54,7 +54,7 @@
final int mIconColorHighlight;
private final Context mContext;
- private final Activity mActivity;
+ private final SettingsHomepageActivity mHomepageActivity;
private final RecyclerView mRecyclerView;
private final int mNormalBackgroundRes;
private String mHighlightKey;
@@ -63,13 +63,13 @@
private boolean mHighlightNeeded;
private boolean mScrolled;
- public HighlightableTopLevelPreferenceAdapter(Activity activity,
+ public HighlightableTopLevelPreferenceAdapter(SettingsHomepageActivity homepageActivity,
PreferenceGroup preferenceGroup, RecyclerView recyclerView, String key) {
super(preferenceGroup);
mRecyclerView = recyclerView;
mHighlightKey = key;
mContext = preferenceGroup.getContext();
- mActivity = activity;
+ mHomepageActivity = homepageActivity;
final TypedValue outValue = new TypedValue();
mContext.getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
outValue, true /* resolveRefs */);
@@ -115,7 +115,7 @@
* A function can highlight a specific setting in recycler view.
*/
public void requestHighlight() {
- if (mRecyclerView == null || TextUtils.isEmpty(mHighlightKey)) {
+ if (mRecyclerView == null) {
return;
}
@@ -194,6 +194,11 @@
return;
}
+ if (mHomepageActivity.registerHomepageLoadedListenerIfNeeded(
+ () -> scrollToPositionIfNeeded(position))) {
+ return;
+ }
+
// Only when the recyclerView is loaded, it can be scrolled
final View view = mRecyclerView.getChildAt(position);
if (view == null) {
@@ -236,6 +241,6 @@
}
private boolean isHighlightNeeded() {
- return ActivityEmbeddingUtils.isTwoPaneResolution(mActivity);
+ return ActivityEmbeddingUtils.isTwoPaneResolution(mHomepageActivity);
}
}