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);
     }
 }