From a305c23f5e53f590fbfc9612c89ac6d788633ec2 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Tue, 30 Nov 2021 16:13:37 +0800 Subject: [PATCH] Revise homepage highlight mechanism - Create TopLevelHighlightMixin to handle highlight actions and simplify TopLevelSettings - Fix the error highlight and the flicker after screen rotation - Postpone creating the fragment until it's needed to accelerate the initialization and to fix the search highlight function breakage after toggling light/dark mode - Register activity embedding rules only once for injection and wallpaper - Do not highlight Tips & support since it's full screen - Refactor ActivityEmbeddingRulesController Bug: 207316936 Test: manual, robotest build pass Change-Id: If322ec180b03ee123987c70779a25c6a570d9faf --- .../ActivityEmbeddingRulesController.java | 30 +-- .../DashboardFeatureProviderImpl.java | 34 ++-- .../profileselector/ProfileSelectDialog.java | 33 ++- ...TopLevelWallpaperPreferenceController.java | 10 +- .../homepage/SettingsHomepageActivity.java | 53 +++-- .../homepage/TopLevelHighlightMixin.java | 189 ++++++++++++++++++ .../settings/homepage/TopLevelSettings.java | 126 +++++------- .../search/SearchResultTrampoline.java | 3 +- ...ighlightableTopLevelPreferenceAdapter.java | 34 ++-- 9 files changed, 346 insertions(+), 166 deletions(-) create mode 100644 src/com/android/settings/homepage/TopLevelHighlightMixin.java diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java index fa61994077a..84843241504 100644 --- a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java +++ b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.util.LayoutDirection; import android.util.Log; -import androidx.annotation.NonNull; import androidx.window.embedding.ActivityFilter; import androidx.window.embedding.ActivityRule; import androidx.window.embedding.SplitController; @@ -114,7 +113,7 @@ public class ActivityEmbeddingRulesController { registerTwoPanePairRule( context, - getComponentName(context, Settings.class), + new ComponentName(context, Settings.class), secondaryComponent, secondaryIntentAction, finishPrimaryWithSecondary ? SplitRule.FINISH_ADJACENT : SplitRule.FINISH_NEVER, @@ -123,7 +122,7 @@ public class ActivityEmbeddingRulesController { registerTwoPanePairRule( context, - getComponentName(context, SettingsHomepageActivity.class), + new ComponentName(context, SettingsHomepageActivity.class), secondaryComponent, secondaryIntentAction, finishPrimaryWithSecondary ? SplitRule.FINISH_ADJACENT : SplitRule.FINISH_NEVER, @@ -143,7 +142,7 @@ public class ActivityEmbeddingRulesController { registerTwoPanePairRule( context, - getComponentName(context, SliceDeepLinkHomepageActivity.class), + new ComponentName(context, SliceDeepLinkHomepageActivity.class), secondaryComponent, secondaryIntentAction, finishPrimaryWithSecondary ? SplitRule.FINISH_ALWAYS : SplitRule.FINISH_NEVER, @@ -179,7 +178,7 @@ public class ActivityEmbeddingRulesController { registerTwoPanePairRuleForSettingsHome( context, - getComponentName(context, SubSettings.class), + new ComponentName(context, SubSettings.class), null /* secondaryIntentAction */, clearTop); } @@ -191,8 +190,7 @@ public class ActivityEmbeddingRulesController { addActivityFilter(activityFilters, SliceDeepLinkHomepageActivity.class); addActivityFilter(activityFilters, Settings.class); - final Intent intent = new Intent(); - intent.setComponent(getComponentName(Settings.NetworkDashboardActivity.class)); + final Intent intent = new Intent(mContext, Settings.NetworkDashboardActivity.class); final SplitPlaceholderRule placeholderRule = new SplitPlaceholderRule( activityFilters, intent, @@ -215,23 +213,7 @@ public class ActivityEmbeddingRulesController { private void addActivityFilter(Set activityFilters, Class activityClass) { - activityFilters.add(new ActivityFilter(getComponentName(activityClass), + activityFilters.add(new ActivityFilter(new ComponentName(mContext, activityClass), null /* intentAction */)); } - - private void addActivityFilter(Set activityFilters, - ComponentName componentName) { - activityFilters.add(new ActivityFilter(componentName, null /* intentAction */)); - } - - @NonNull - private ComponentName getComponentName(Class activityClass) { - return getComponentName(mContext, activityClass); - } - - @NonNull - private static ComponentName getComponentName(Context context, - Class activityClass) { - return new ComponentName(context.getPackageName(), activityClass.getName()); - } } diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java index 3d1381d6867..69fdc9eb6d8 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java @@ -36,7 +36,6 @@ import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITL import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface.OnCancelListener; import android.content.IContentProvider; import android.content.Intent; import android.content.pm.PackageManager; @@ -63,6 +62,7 @@ import com.android.settings.Utils; import com.android.settings.activityembedding.ActivityEmbeddingRulesController; import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.dashboard.profileselector.ProfileSelectDialog; +import com.android.settings.homepage.TopLevelHighlightMixin; import com.android.settings.homepage.TopLevelSettings; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.PrimarySwitchPreference; @@ -170,27 +170,23 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { if (action != null) { intent.setAction(action); } + // Register the rule for injected apps. + ActivityEmbeddingRulesController.registerTwoPanePairRuleForSettingsHome( + mContext, + new ComponentName(tile.getPackageName(), tile.getComponentName()), + action, + true /* clearTop */); pref.setOnPreferenceClickListener(preference -> { - OnCancelListener listener = null; + TopLevelHighlightMixin highlightMixin = null; if (fragment instanceof TopLevelSettings && ActivityEmbeddingUtils.isEmbeddingActivityEnabled(mContext)) { - // Register the rule for injected apps. - ActivityEmbeddingRulesController.registerTwoPanePairRuleForSettingsHome( - mContext, - new ComponentName(tile.getPackageName(), tile.getComponentName()), - null /* secondaryIntentAction */, - true /* clearTop */); - - // Highlight preference ui. + // Highlight the preference whenever it's clicked final TopLevelSettings topLevelSettings = (TopLevelSettings) fragment; - // Highlight the tile immediately whenever it's clicked topLevelSettings.setHighlightPreferenceKey(key); - // If the tile allows users to select profile, the pop-op dialog may be - // cancelled and then the previous highlight entry should be restored. - listener = dialog -> topLevelSettings.restorePreviousHighlight(); + highlightMixin = topLevelSettings.getHighlightMixin(); } launchIntentOrSelectProfile(activity, tile, intent, sourceMetricsCategory, - listener); + highlightMixin); return true; }); } @@ -223,7 +219,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { SettingsEnums.DASHBOARD_SUMMARY) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); launchIntentOrSelectProfile(activity, tile, intent, SettingsEnums.DASHBOARD_SUMMARY, - /* listener= */ null); + /* highlightMixin= */ null); } private DynamicDataObserver createDynamicDataObserver(String method, Uri uri, Preference pref) { @@ -438,7 +434,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { } private void launchIntentOrSelectProfile(FragmentActivity activity, Tile tile, Intent intent, - int sourceMetricCategory, OnCancelListener listener) { + int sourceMetricCategory, TopLevelHighlightMixin highlightMixin) { if (!isIntentResolvable(intent)) { Log.w(TAG, "Cannot resolve intent, skipping. " + intent); return; @@ -469,7 +465,9 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { } ProfileSelectDialog.show(activity.getSupportFragmentManager(), tile, - sourceMetricCategory, listener); + sourceMetricCategory, /* onShowListener= */ highlightMixin, + /* onDismissListener= */ highlightMixin, + /* onCancelListener= */ highlightMixin); } } diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java index b05f23b812b..e0ba3787199 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java @@ -21,6 +21,8 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.DialogInterface.OnShowListener; import android.content.Intent; import android.os.Bundle; import android.os.UserHandle; @@ -45,23 +47,30 @@ public class ProfileSelectDialog extends DialogFragment implements OnClickListen private int mSourceMetricCategory; private Tile mSelectedTile; + private OnShowListener mOnShowListener; private OnCancelListener mOnCancelListener; + private OnDismissListener mOnDismissListener; /** * Display the profile select dialog, adding the fragment to the given FragmentManager. * @param manager The FragmentManager this fragment will be added to. * @param tile The tile for this fragment. * @param sourceMetricCategory The source metric category. - * @param listener The listener listens to the dialog cancelling event. + * @param onShowListener The listener listens to the dialog showing event. + * @param onDismissListener The listener listens to the dialog dismissing event. + * @param onCancelListener The listener listens to the dialog cancelling event. */ public static void show(FragmentManager manager, Tile tile, int sourceMetricCategory, - OnCancelListener listener) { + OnShowListener onShowListener, OnDismissListener onDismissListener, + OnCancelListener onCancelListener) { final ProfileSelectDialog dialog = new ProfileSelectDialog(); final Bundle args = new Bundle(); args.putParcelable(ARG_SELECTED_TILE, tile); args.putInt(ARG_SOURCE_METRIC_CATEGORY, sourceMetricCategory); dialog.setArguments(args); - dialog.mOnCancelListener = listener; + dialog.mOnShowListener = onShowListener; + dialog.mOnDismissListener = onDismissListener; + dialog.mOnCancelListener = onCancelListener; dialog.show(manager, "select_profile"); } @@ -96,13 +105,31 @@ public class ProfileSelectDialog extends DialogFragment implements OnClickListen getActivity().startActivityAsUser(intent, user); } + @Override + public void onStart() { + super.onStart(); + // The fragment shows the dialog within onStart() + if (mOnShowListener != null) { + mOnShowListener.onShow(getDialog()); + } + } + @Override public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); if (mOnCancelListener != null) { mOnCancelListener.onCancel(dialog); } } + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + if (mOnDismissListener != null) { + mOnDismissListener.onDismiss(dialog); + } + } + public static void updateUserHandlesIfNeeded(Context context, Tile tile) { final List userHandles = tile.userHandle; if (tile.userHandle == null || tile.userHandle.size() <= 1) { diff --git a/src/com/android/settings/display/TopLevelWallpaperPreferenceController.java b/src/com/android/settings/display/TopLevelWallpaperPreferenceController.java index 7640d08716f..0136eac187b 100644 --- a/src/com/android/settings/display/TopLevelWallpaperPreferenceController.java +++ b/src/com/android/settings/display/TopLevelWallpaperPreferenceController.java @@ -63,6 +63,11 @@ public class TopLevelWallpaperPreferenceController extends BasePreferenceControl super.displayPreference(screen); Preference preference = screen.findPreference(getPreferenceKey()); preference.setTitle(getTitle()); + ActivityEmbeddingRulesController.registerTwoPanePairRuleForSettingsHome( + mContext, + getComponentName(), + null /* secondaryIntentAction */, + true /* clearTop */); } public String getTitle() { @@ -103,11 +108,6 @@ public class TopLevelWallpaperPreferenceController extends BasePreferenceControl mContext)) { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); } - ActivityEmbeddingRulesController.registerTwoPanePairRuleForSettingsHome( - mContext, - intent.getComponent(), - null /* secondaryIntentAction */, - true /* clearTop */); preference.getContext().startActivity(intent); return true; } diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 31cc7aa9523..e25e1f50730 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -76,7 +76,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA = "settings_large_screen_deep_link_intent_data"; - private static final int DEFAULT_HIGHLIGHT_MENU_KEY = R.string.menu_key_network; + static final int DEFAULT_HIGHLIGHT_MENU_KEY = R.string.menu_key_network; private static final long HOMEPAGE_LOADING_TIMEOUT_MS = 300; private TopLevelSettings mMainFragment; @@ -94,6 +94,10 @@ public class SettingsHomepageActivity extends FragmentActivity implements void onHomepageLoaded(); } + private interface FragmentBuilder { + T build(); + } + /** * Try to add a {@link HomepageLoadedListener}. If homepage is already loaded, the listener * will not be notified. @@ -168,13 +172,15 @@ public class SettingsHomepageActivity extends FragmentActivity implements initAvatarView(); showSuggestionFragment(); if (FeatureFlagUtils.isEnabled(this, FeatureFlags.CONTEXTUAL_HOME)) { - showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content); + showFragment(() -> new ContextualCardsFragment(), R.id.contextual_cards_content); } } - mMainFragment = new TopLevelSettings(); - mMainFragment.getArguments().putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, - getHighlightMenuKey()); - showFragment(mMainFragment, R.id.main_content); + mMainFragment = showFragment(() -> { + final TopLevelSettings fragment = new TopLevelSettings(); + fragment.getArguments().putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, + getHighlightMenuKey()); + return fragment; + }, R.id.main_content); ((FrameLayout) findViewById(R.id.main_content)) .getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); @@ -260,9 +266,9 @@ public class SettingsHomepageActivity extends FragmentActivity implements } private void showSuggestionFragment() { - final Class fragment = FeatureFactory.getFactory(this) + final Class fragmentClass = FeatureFactory.getFactory(this) .getSuggestionFeatureProvider(this).getContextualSuggestionFragment(); - if (fragment == null) { + if (fragmentClass == null) { return; } @@ -274,28 +280,33 @@ public class SettingsHomepageActivity extends FragmentActivity implements // Schedule a timer to show the homepage and hide the suggestion on timeout. mHomepageView.postDelayed(() -> showHomepageWithSuggestion(false), HOMEPAGE_LOADING_TIMEOUT_MS); - try { - showFragment(fragment.getConstructor().newInstance(), R.id.suggestion_content); - if (mIsEmbeddingActivityEnabled) { - showFragment(fragment.getConstructor().newInstance(), - R.id.two_pane_suggestion_content); + final FragmentBuilder fragmentBuilder = () -> { + try { + return fragmentClass.getConstructor().newInstance(); + } catch (Exception e) { + Log.w(TAG, "Cannot show fragment", e); } - } catch (Exception e) { - Log.w(TAG, "Cannot show fragment", e); + return null; + }; + showFragment(fragmentBuilder, R.id.suggestion_content); + if (mIsEmbeddingActivityEnabled) { + showFragment(fragmentBuilder, R.id.two_pane_suggestion_content); } } - private void showFragment(Fragment fragment, int id) { + private T showFragment(FragmentBuilder fragmentBuilder, int id) { final FragmentManager fragmentManager = getSupportFragmentManager(); final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); - final Fragment showFragment = fragmentManager.findFragmentById(id); + T showFragment = (T) fragmentManager.findFragmentById(id); if (showFragment == null) { - fragmentTransaction.add(id, fragment); + showFragment = fragmentBuilder.build(); + fragmentTransaction.add(id, showFragment); } else { fragmentTransaction.show(showFragment); } fragmentTransaction.commit(); + return showFragment; } private void launchDeepLinkIntentToRight() { @@ -363,14 +374,14 @@ public class SettingsHomepageActivity extends FragmentActivity implements targetIntent.getAction(), SplitRule.FINISH_ALWAYS, SplitRule.FINISH_ALWAYS, - true /* clearTop*/); + true /* clearTop */); ActivityEmbeddingRulesController.registerTwoPanePairRule(this, - new ComponentName(Settings.class.getPackageName(), Settings.class.getName()), + new ComponentName(getApplicationContext(), Settings.class), targetComponentName, targetIntent.getAction(), SplitRule.FINISH_ALWAYS, SplitRule.FINISH_ALWAYS, - true /* clearTop*/); + true /* clearTop */); startActivity(targetIntent); } diff --git a/src/com/android/settings/homepage/TopLevelHighlightMixin.java b/src/com/android/settings/homepage/TopLevelHighlightMixin.java new file mode 100644 index 00000000000..ebfe7f204a6 --- /dev/null +++ b/src/com/android/settings/homepage/TopLevelHighlightMixin.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2021 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.homepage; + +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; + +import androidx.fragment.app.FragmentActivity; +import androidx.preference.PreferenceScreen; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.settings.SettingsActivity; +import com.android.settings.widget.HighlightableTopLevelPreferenceAdapter; + +/** A highlight mixin for the top level settings fragment. */ +public class TopLevelHighlightMixin implements Parcelable, DialogInterface.OnShowListener, + DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { + + private static final String TAG = "TopLevelHighlightMixin"; + + private String mCurrentKey; + // Stores the previous key for the profile select dialog cancel event + private String mPreviousKey; + // Stores the key hidden for the search page presence + private String mHiddenKey; + private DialogInterface mDialog; + private HighlightableTopLevelPreferenceAdapter mTopLevelAdapter; + + public TopLevelHighlightMixin() { + } + + public TopLevelHighlightMixin(Parcel source) { + mCurrentKey = source.readString(); + mPreviousKey = source.readString(); + mHiddenKey = source.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mCurrentKey); + dest.writeString(mPreviousKey); + dest.writeString(mHiddenKey); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public TopLevelHighlightMixin createFromParcel(Parcel source) { + return new TopLevelHighlightMixin(source); + } + + @Override + public TopLevelHighlightMixin[] newArray(int size) { + return new TopLevelHighlightMixin[size]; + } + }; + + @Override + public void onShow(DialogInterface dialog) { + mDialog = dialog; + } + + @Override + public void onDismiss(DialogInterface dialog) { + mDialog = null; + } + + @Override + public void onCancel(DialogInterface dialog) { + if (mTopLevelAdapter != null) { + mCurrentKey = mPreviousKey; + mPreviousKey = null; + mTopLevelAdapter.highlightPreference(mCurrentKey, /* scrollNeeded= */ false); + } + } + + RecyclerView.Adapter onCreateAdapter(TopLevelSettings topLevelSettings, + PreferenceScreen preferenceScreen) { + if (TextUtils.isEmpty(mCurrentKey)) { + mCurrentKey = getHighlightPrefKeyFromArguments(topLevelSettings.getArguments()); + } + + Log.d(TAG, "onCreateAdapter, pref key: " + mCurrentKey); + mTopLevelAdapter = new HighlightableTopLevelPreferenceAdapter( + (SettingsHomepageActivity) topLevelSettings.getActivity(), preferenceScreen, + topLevelSettings.getListView(), mCurrentKey); + return mTopLevelAdapter; + } + + void reloadHighlightMenuKey(Bundle arguments) { + if (mTopLevelAdapter == null) { + return; + } + ensureDialogDismissed(); + + mCurrentKey = getHighlightPrefKeyFromArguments(arguments); + Log.d(TAG, "reloadHighlightMenuKey, pref key: " + mCurrentKey); + mTopLevelAdapter.highlightPreference(mCurrentKey, /* scrollNeeded= */ true); + } + + void setHighlightPreferenceKey(String prefKey) { + if (mTopLevelAdapter != null) { + ensureDialogDismissed(); + mPreviousKey = mCurrentKey; + mCurrentKey = prefKey; + mTopLevelAdapter.highlightPreference(prefKey, /* scrollNeeded= */ false); + } + } + + void highlightPreferenceIfNeeded(FragmentActivity activity) { + if (mTopLevelAdapter != null) { + mTopLevelAdapter.requestHighlight(); + } + } + + void setMenuHighlightShowed(boolean show) { + if (mTopLevelAdapter == null) { + return; + } + ensureDialogDismissed(); + + if (show) { + mCurrentKey = mHiddenKey; + mHiddenKey = null; + } else { + if (mHiddenKey == null) { + mHiddenKey = mCurrentKey; + } + mCurrentKey = null; + } + mTopLevelAdapter.highlightPreference(mCurrentKey, /* scrollNeeded= */ show); + } + + void setHighlightMenuKey(String menuKey, boolean scrollNeeded) { + if (mTopLevelAdapter == null) { + return; + } + ensureDialogDismissed(); + + final String prefKey = HighlightableMenu.lookupPreferenceKey(menuKey); + if (TextUtils.isEmpty(prefKey)) { + Log.e(TAG, "Invalid highlight menu key: " + menuKey); + } else { + Log.d(TAG, "Menu key: " + menuKey); + mCurrentKey = prefKey; + mTopLevelAdapter.highlightPreference(prefKey, scrollNeeded); + } + } + + private static String getHighlightPrefKeyFromArguments(Bundle arguments) { + final String menuKey = arguments.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); + final String prefKey = HighlightableMenu.lookupPreferenceKey(menuKey); + if (TextUtils.isEmpty(prefKey)) { + Log.e(TAG, "Invalid highlight menu key: " + menuKey); + } else { + Log.d(TAG, "Menu key: " + menuKey); + } + return prefKey; + } + + private void ensureDialogDismissed() { + if (mDialog != null) { + onCancel(mDialog); + mDialog.dismiss(); + } + } +} diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java index eb1a0664a88..2bb8e34b408 100644 --- a/src/com/android/settings/homepage/TopLevelSettings.java +++ b/src/com/android/settings/homepage/TopLevelSettings.java @@ -25,7 +25,6 @@ import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.TextUtils; -import android.util.Log; import androidx.fragment.app.Fragment; import androidx.preference.Preference; @@ -34,7 +33,6 @@ import androidx.preference.PreferenceScreen; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; -import com.android.settings.SettingsActivity; import com.android.settings.Utils; import com.android.settings.activityembedding.ActivityEmbeddingRulesController; import com.android.settings.activityembedding.ActivityEmbeddingUtils; @@ -42,7 +40,6 @@ import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.support.SupportPreferenceController; -import com.android.settings.widget.HighlightableTopLevelPreferenceAdapter; import com.android.settings.widget.HomepagePreference; import com.android.settingslib.core.instrumentation.Instrumentable; import com.android.settingslib.drawer.Tile; @@ -53,13 +50,10 @@ public class TopLevelSettings extends DashboardFragment implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { private static final String TAG = "TopLevelSettings"; - private static final String SAVED_HIGHLIGHTED_PREF = "highlighted_pref"; - private static final String SAVED_CACHED_PREF = "cached_pref"; + private static final String SAVED_HIGHLIGHT_MIXIN = "highlight_mixin"; + private static final String PREF_KEY_SUPPORT = "top_level_support"; - private HighlightableTopLevelPreferenceAdapter mTopLevelAdapter; - - private String mHighlightedPreferenceKey; - private String mCachedPreferenceKey; + private TopLevelHighlightMixin mHighlightMixin; public TopLevelSettings() { final Bundle args = new Bundle(); @@ -127,17 +121,35 @@ public class TopLevelSettings extends DashboardFragment implements @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - if (icicle != null) { - mHighlightedPreferenceKey = icicle.getString(SAVED_HIGHLIGHTED_PREF); - mCachedPreferenceKey = icicle.getString(SAVED_CACHED_PREF); + if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(getContext())) { + return; } + + if (icicle != null) { + mHighlightMixin = icicle.getParcelable(SAVED_HIGHLIGHT_MIXIN); + } + if (mHighlightMixin == null) { + mHighlightMixin = new TopLevelHighlightMixin(); + } + } + + @Override + public void onStart() { + // Set default highlight menu key for 1-pane homepage since it will show the placeholder + // page once changing back to 2-pane. + if (!ActivityEmbeddingUtils.isTwoPaneResolution(getActivity())) { + setHighlightMenuKey(getString(SettingsHomepageActivity.DEFAULT_HIGHLIGHT_MENU_KEY), + /* scrollNeeded= */ false); + } + super.onStart(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putString(SAVED_HIGHLIGHTED_PREF, mHighlightedPreferenceKey); - outState.putString(SAVED_CACHED_PREF, mCachedPreferenceKey); + if (mHighlightMixin != null) { + outState.putParcelable(SAVED_HIGHLIGHT_MIXIN, mHighlightMixin); + } } @Override @@ -170,58 +182,35 @@ public class TopLevelSettings extends DashboardFragment implements @Override public void highlightPreferenceIfNeeded() { - if (mTopLevelAdapter != null) { - mTopLevelAdapter.requestHighlight(); + if (mHighlightMixin != null) { + mHighlightMixin.highlightPreferenceIfNeeded(getActivity()); } } + /** Returns a {@link TopLevelHighlightMixin} that performs highlighting */ + public TopLevelHighlightMixin getHighlightMixin() { + return mHighlightMixin; + } + /** Highlight a preference with specified preference key */ public void setHighlightPreferenceKey(String prefKey) { - if (mTopLevelAdapter != null) { - mCachedPreferenceKey = null; - mHighlightedPreferenceKey = prefKey; - mTopLevelAdapter.highlightPreference(prefKey, /* scrollNeeded= */ false); + // Skip Tips & support since it's full screen + if (mHighlightMixin != null && !TextUtils.equals(prefKey, PREF_KEY_SUPPORT)) { + mHighlightMixin.setHighlightPreferenceKey(prefKey); } } - /** Highlight the previous preference */ - public void restorePreviousHighlight() { - if (mTopLevelAdapter != null) { - mTopLevelAdapter.restorePreviousHighlight(); - } - } - - /** Show/hide the highlight on the menu entry */ + /** Show/hide the highlight on the menu entry for the search page presence */ public void setMenuHighlightShowed(boolean show) { - if (mTopLevelAdapter == null) { - return; + if (mHighlightMixin != null) { + mHighlightMixin.setMenuHighlightShowed(show); } - - if (show) { - mHighlightedPreferenceKey = mCachedPreferenceKey; - mCachedPreferenceKey = null; - } else { - if (mCachedPreferenceKey == null) { - mCachedPreferenceKey = mHighlightedPreferenceKey; - } - mHighlightedPreferenceKey = null; - } - mTopLevelAdapter.highlightPreference(mHighlightedPreferenceKey, /* scrollNeeded= */ show); } /** Highlight and scroll to a preference with specified menu key */ - public void setHighlightMenuKey(String menuKey) { - if (mTopLevelAdapter == null) { - return; - } - - final String prefKey = HighlightableMenu.lookupPreferenceKey(menuKey); - if (TextUtils.isEmpty(prefKey)) { - Log.e(TAG, "Invalid highlight menu key: " + menuKey); - } else { - Log.d(TAG, "Menu key: " + menuKey); - mHighlightedPreferenceKey = prefKey; - mTopLevelAdapter.highlightPreference(prefKey, /* scrollNeeded= */ true); + public void setHighlightMenuKey(String menuKey, boolean scrollNeeded) { + if (mHighlightMixin != null) { + mHighlightMixin.setHighlightMenuKey(menuKey, scrollNeeded); } } @@ -237,16 +226,7 @@ public class TopLevelSettings extends DashboardFragment implements || !(getActivity() instanceof SettingsHomepageActivity)) { return super.onCreateAdapter(preferenceScreen); } - - if (TextUtils.isEmpty(mHighlightedPreferenceKey)) { - mHighlightedPreferenceKey = getHighlightPrefKeyFromArguments(); - } - - Log.d(TAG, "onCreateAdapter, pref key: " + mHighlightedPreferenceKey); - mTopLevelAdapter = new HighlightableTopLevelPreferenceAdapter( - (SettingsHomepageActivity) getActivity(), preferenceScreen, getListView(), - mHighlightedPreferenceKey); - return mTopLevelAdapter; + return mHighlightMixin.onCreateAdapter(this, preferenceScreen); } @Override @@ -255,25 +235,9 @@ public class TopLevelSettings extends DashboardFragment implements } void reloadHighlightMenuKey() { - if (mTopLevelAdapter == null) { - return; + if (mHighlightMixin != null) { + mHighlightMixin.reloadHighlightMenuKey(getArguments()); } - - mHighlightedPreferenceKey = getHighlightPrefKeyFromArguments(); - Log.d(TAG, "reloadHighlightMenuKey, pref key: " + mHighlightedPreferenceKey); - mTopLevelAdapter.highlightPreference(mHighlightedPreferenceKey, /* scrollNeeded= */ true); - } - - private String getHighlightPrefKeyFromArguments() { - final Bundle arguments = getArguments(); - final String menuKey = arguments.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); - final String prefKey = HighlightableMenu.lookupPreferenceKey(menuKey); - if (TextUtils.isEmpty(prefKey)) { - Log.e(TAG, "Invalid highlight menu key: " + menuKey); - } else { - Log.d(TAG, "Menu key: " + menuKey); - } - return prefKey; } public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = diff --git a/src/com/android/settings/search/SearchResultTrampoline.java b/src/com/android/settings/search/SearchResultTrampoline.java index f9cbc362419..8b041b67f87 100644 --- a/src/com/android/settings/search/SearchResultTrampoline.java +++ b/src/com/android/settings/search/SearchResultTrampoline.java @@ -109,7 +109,8 @@ public class SearchResultTrampoline extends Activity { final SettingsHomepageActivity homeActivity = ((SettingsApplication) getApplicationContext()).getHomeActivity(); if (homeActivity != null) { - homeActivity.getMainFragment().setHighlightMenuKey(highlightMenuKey); + homeActivity.getMainFragment().setHighlightMenuKey(highlightMenuKey, + /* scrollNeeded= */ true); } } else { // Two-pane case diff --git a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java index b7f30158a1d..ff8f805b5e8 100644 --- a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java +++ b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.Log; +import android.util.SparseArray; import android.util.TypedValue; import android.view.View; import android.widget.ImageView; @@ -59,17 +60,18 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt private final int mNormalBackgroundRes; private final int mHighlightBackgroundRes; private String mHighlightKey; - private String mPreviousHighlightKey; private int mHighlightPosition = RecyclerView.NO_POSITION; private int mScrollPosition = RecyclerView.NO_POSITION; private boolean mHighlightNeeded; private boolean mScrolled; + private SparseArray mViewHolders; public HighlightableTopLevelPreferenceAdapter(SettingsHomepageActivity homepageActivity, PreferenceGroup preferenceGroup, RecyclerView recyclerView, String key) { super(preferenceGroup); mRecyclerView = recyclerView; mHighlightKey = key; + mViewHolders = new SparseArray<>(); mContext = preferenceGroup.getContext(); mHomepageActivity = homepageActivity; final TypedValue outValue = new TypedValue(); @@ -92,6 +94,7 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt @Override public void onBindViewHolder(PreferenceViewHolder holder, int position) { super.onBindViewHolder(holder, position); + mViewHolders.put(position, holder); updateBackground(holder, position); } @@ -120,9 +123,9 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt return; } + final int previousPosition = mHighlightPosition; if (TextUtils.isEmpty(mHighlightKey)) { // De-highlight previous preference. - final int previousPosition = mHighlightPosition; mHighlightPosition = RecyclerView.NO_POSITION; mScrolled = true; if (previousPosition >= 0) { @@ -145,10 +148,14 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt // Turn on/off highlight when screen split mode is changed. if (highlightNeeded != mHighlightNeeded) { - Log.d(TAG, "Highlight change needed: " + highlightNeeded); + Log.d(TAG, "Highlight needed change: " + highlightNeeded); mHighlightNeeded = highlightNeeded; mHighlightPosition = position; notifyItemChanged(position); + if (!highlightNeeded) { + // De-highlight to prevent a flicker + removeHighlightAt(previousPosition); + } return; } @@ -156,7 +163,6 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt return; } - final int previousPosition = mHighlightPosition; mHighlightPosition = position; Log.d(TAG, "Request highlight position " + position); Log.d(TAG, "Is highlight needed: " + highlightNeeded); @@ -178,20 +184,11 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt * preference is clicked. */ public void highlightPreference(String key, boolean scrollNeeded) { - mPreviousHighlightKey = mHighlightKey; mHighlightKey = key; mScrolled = !scrollNeeded; requestHighlight(); } - /** - * A function that restores the previous highlighted setting. - */ - public void restorePreviousHighlight() { - mHighlightKey = mPreviousHighlightKey; - requestHighlight(); - } - @Override public void onHomepageLoaded() { scroll(); @@ -224,6 +221,17 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt } } + private void removeHighlightAt(int position) { + if (position >= 0) { + // De-highlight the existing preference view holder at an early stage + final PreferenceViewHolder holder = mViewHolders.get(position); + if (holder != null) { + removeHighlightBackground(holder); + } + notifyItemChanged(position); + } + } + private void addHighlightBackground(PreferenceViewHolder holder) { final View v = holder.itemView; v.setBackgroundResource(mHighlightBackgroundRes);