From bb46bb79bff376b710e2086b69151ff5c94f58b4 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Fri, 19 Nov 2021 12:36:15 +0800 Subject: [PATCH 1/3] Disable animations of deep link homepages to reduce the shortcut flicker Bug: 203744424 Test: manual Change-Id: I7806d732be99425e98db615474bdd83e42dc072e --- AndroidManifest.xml | 4 ++-- res/values/themes.xml | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ffac933aca0..4a0dc51914d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -147,7 +147,7 @@ ?attr/colorPrimaryDark + + From a992b611bfafe3469e2ca33fb0533d6825b185c9 Mon Sep 17 00:00:00 2001 From: ryanlwlin Date: Thu, 2 Dec 2021 20:51:21 +0800 Subject: [PATCH 2/3] Fix incorrect UI after triple-tap is disabled The preference summary is not changed after users disable the triple tap shortcut by the confirm dialog. It is due to the fact that the fragment doesn't observe the settings value change. To fix it, we add the settings key to observe the change. Bug: 208755360 Test: manual test Change-Id: Ie5b7fc5e67c5491b53485af0d3d68d1789aeace4 --- .../ToggleFeaturePreferenceFragment.java | 11 ++++++++--- .../ToggleScreenMagnificationPreferenceFragment.java | 7 +++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java index c25abf3486d..233b7ee2997 100644 --- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java @@ -143,9 +143,7 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference setPreferenceScreen(preferenceScreen); } - final List shortcutFeatureKeys = new ArrayList<>(); - shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); - shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); + final List shortcutFeatureKeys = getFeatureSettingsKeys(); mSettingsContentObserver = new SettingsContentObserver(new Handler(), shortcutFeatureKeys) { @Override public void onChange(boolean selfChange, Uri uri) { @@ -155,6 +153,13 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference }; } + protected List getFeatureSettingsKeys() { + final List shortcutFeatureKeys = new ArrayList<>(); + shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); + shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); + return shortcutFeatureKeys; + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index 6b4a5f2b0ec..1586c5af56a 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -275,6 +275,13 @@ public class ToggleScreenMagnificationPreferenceFragment extends return context.getText(resId); } + @Override + protected List getFeatureSettingsKeys() { + final List shortcutKeys = super.getFeatureSettingsKeys(); + shortcutKeys.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); + return shortcutKeys; + } + @Override protected CharSequence getShortcutTypeSummary(Context context) { if (!mShortcutPreference.isChecked()) { From a305c23f5e53f590fbfc9612c89ac6d788633ec2 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Tue, 30 Nov 2021 16:13:37 +0800 Subject: [PATCH 3/3] 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);