diff options
author | Jason Chiu <chiujason@google.com> | 2021-06-17 17:48:02 +0800 |
---|---|---|
committer | Jason Chiu <chiujason@google.com> | 2021-06-18 16:00:29 +0800 |
commit | c713c3e8ea27e0b9ab9ebda12007f7f307d1b44b (patch) | |
tree | e7396215cfeb47722fd5006fb437034237e78e19 /src/com/android | |
parent | 5a8476a709b7de01045f614c1c5815aeea64a816 (diff) | |
download | packages_apps_Settings-c713c3e8ea27e0b9ab9ebda12007f7f307d1b44b.tar.gz packages_apps_Settings-c713c3e8ea27e0b9ab9ebda12007f7f307d1b44b.tar.bz2 packages_apps_Settings-c713c3e8ea27e0b9ab9ebda12007f7f307d1b44b.zip |
Support category changed mechanism in homepage
- Homepage cannot referesh UI whenever an injected component is changed
- Extract categories related codes to a mixin
Test: manual, robotest
Fixes: 179792445
Change-Id: I1c13c541ce07b9c36fe984a035623985b5603560
Diffstat (limited to 'src/com/android')
5 files changed, 261 insertions, 168 deletions
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 7dd5fe40e4..12f63ea226 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -36,10 +36,8 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; -import android.util.FeatureFlagUtils; import android.util.Log; import android.view.View; -import android.view.Window; import android.widget.Button; import androidx.annotation.Nullable; @@ -55,7 +53,6 @@ import androidx.preference.PreferenceManager; import com.android.internal.util.ArrayUtils; import com.android.settings.Settings.WifiSettingsActivity; import com.android.settings.applications.manageapplications.ManageApplications; -import com.android.settings.core.FeatureFlags; import com.android.settings.core.OnActivityResultListener; import com.android.settings.core.SettingsBaseActivity; import com.android.settings.core.SubSettingLauncher; @@ -70,7 +67,6 @@ import com.android.settingslib.core.instrumentation.SharedPreferencesLogger; import com.android.settingslib.development.DevelopmentSettingsEnabler; import com.android.settingslib.drawer.DashboardCategory; -import com.google.android.material.transition.platform.MaterialSharedAxis; import com.google.android.setupcompat.util.WizardManagerHelper; import java.util.ArrayList; @@ -689,7 +685,7 @@ public class SettingsActivity extends SettingsBaseActivity if (somethingChanged) { Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories " + changedList.toString()); - updateCategories(); + mCategoryMixin.updateCategories(); } else { Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call"); } diff --git a/src/com/android/settings/core/CategoryMixin.java b/src/com/android/settings/core/CategoryMixin.java new file mode 100644 index 0000000000..8d0a412a60 --- /dev/null +++ b/src/com/android/settings/core/CategoryMixin.java @@ -0,0 +1,225 @@ +/* + * 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.core; + +import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE; +import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; + +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.AsyncTask; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.OnLifecycleEvent; + +import com.android.settings.dashboard.CategoryManager; +import com.android.settingslib.drawer.Tile; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A mixin that handles live categories for Injection + */ +public class CategoryMixin implements LifecycleObserver { + + private static final String TAG = "CategoryMixin"; + private static final String DATA_SCHEME_PKG = "package"; + + // Serves as a temporary list of tiles to ignore until we heard back from the PM that they + // are disabled. + private static final ArraySet<ComponentName> sTileDenylist = new ArraySet<>(); + + private final Context mContext; + private final PackageReceiver mPackageReceiver = new PackageReceiver(); + private final List<CategoryListener> mCategoryListeners = new ArrayList<>(); + private int mCategoriesUpdateTaskCount; + + public CategoryMixin(Context context) { + mContext = context; + } + + /** + * Resume Lifecycle event + */ + @OnLifecycleEvent(ON_RESUME) + public void onResume() { + final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); + filter.addDataScheme(DATA_SCHEME_PKG); + mContext.registerReceiver(mPackageReceiver, filter); + + updateCategories(); + } + + /** + * Pause Lifecycle event + */ + @OnLifecycleEvent(ON_PAUSE) + public void onPause() { + mContext.unregisterReceiver(mPackageReceiver); + } + + /** + * Add a category listener + */ + public void addCategoryListener(CategoryListener listener) { + mCategoryListeners.add(listener); + } + + /** + * Remove a category listener + */ + public void removeCategoryListener(CategoryListener listener) { + mCategoryListeners.remove(listener); + } + + /** + * Updates dashboard categories. + */ + public void updateCategories() { + updateCategories(false /* fromBroadcast */); + } + + void addToDenylist(ComponentName component) { + sTileDenylist.add(component); + } + + void removeFromDenylist(ComponentName component) { + sTileDenylist.remove(component); + } + + @VisibleForTesting + void onCategoriesChanged(Set<String> categories) { + mCategoryListeners.forEach(listener -> listener.onCategoriesChanged(categories)); + } + + private void updateCategories(boolean fromBroadcast) { + // Only allow at most 2 tasks existing at the same time since when the first one is + // executing, there may be new data from the second update request. + // Ignore the third update request because the second task is still waiting for the first + // task to complete in a serial thread, which will get the latest data. + if (mCategoriesUpdateTaskCount < 2) { + new CategoriesUpdateTask().execute(fromBroadcast); + } + } + + /** + * A handler implementing a {@link CategoryMixin} + */ + public interface CategoryHandler { + /** returns a {@link CategoryMixin} */ + CategoryMixin getCategoryMixin(); + } + + /** + * A listener receiving category change events. + */ + public interface CategoryListener { + /** + * @param categories the changed categories that have to be refreshed, or null to force + * refreshing all. + */ + void onCategoriesChanged(@Nullable Set<String> categories); + } + + private class CategoriesUpdateTask extends AsyncTask<Boolean, Void, Set<String>> { + + private final CategoryManager mCategoryManager; + private Map<ComponentName, Tile> mPreviousTileMap; + + CategoriesUpdateTask() { + mCategoriesUpdateTaskCount++; + mCategoryManager = CategoryManager.get(mContext); + } + + @Override + protected Set<String> doInBackground(Boolean... params) { + mPreviousTileMap = mCategoryManager.getTileByComponentMap(); + mCategoryManager.reloadAllCategories(mContext); + mCategoryManager.updateCategoryFromDenylist(sTileDenylist); + return getChangedCategories(params[0]); + } + + @Override + protected void onPostExecute(Set<String> categories) { + if (categories == null || !categories.isEmpty()) { + onCategoriesChanged(categories); + } + mCategoriesUpdateTaskCount--; + } + + // Return the changed categories that have to be refreshed, or null to force refreshing all. + private Set<String> getChangedCategories(boolean fromBroadcast) { + if (!fromBroadcast) { + // Always refresh for non-broadcast case. + return null; + } + + final Set<String> changedCategories = new ArraySet<>(); + final Map<ComponentName, Tile> currentTileMap = + mCategoryManager.getTileByComponentMap(); + currentTileMap.forEach((component, currentTile) -> { + final Tile previousTile = mPreviousTileMap.get(component); + // Check if the tile is newly added. + if (previousTile == null) { + Log.i(TAG, "Tile added: " + component.flattenToShortString()); + changedCategories.add(currentTile.getCategory()); + return; + } + + // Check if the title or summary has changed. + if (!TextUtils.equals(currentTile.getTitle(mContext), + previousTile.getTitle(mContext)) + || !TextUtils.equals(currentTile.getSummary(mContext), + previousTile.getSummary(mContext))) { + Log.i(TAG, "Tile changed: " + component.flattenToShortString()); + changedCategories.add(currentTile.getCategory()); + } + }); + + // Check if any previous tile is removed. + final Set<ComponentName> removal = new ArraySet(mPreviousTileMap.keySet()); + removal.removeAll(currentTileMap.keySet()); + removal.forEach(component -> { + Log.i(TAG, "Tile removed: " + component.flattenToShortString()); + changedCategories.add(mPreviousTileMap.get(component).getCategory()); + }); + + return changedCategories; + } + } + + private class PackageReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + updateCategories(true /* fromBroadcast */); + } + } +} diff --git a/src/com/android/settings/core/SettingsBaseActivity.java b/src/com/android/settings/core/SettingsBaseActivity.java index 6dba83b8af..47993cfbd5 100644 --- a/src/com/android/settings/core/SettingsBaseActivity.java +++ b/src/com/android/settings/core/SettingsBaseActivity.java @@ -16,21 +16,15 @@ package com.android.settings.core; import android.annotation.LayoutRes; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; -import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.TypedArray; -import android.os.AsyncTask; import android.os.Bundle; import android.os.UserHandle; import android.text.TextUtils; -import android.util.ArraySet; import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; @@ -40,14 +34,14 @@ import android.view.Window; import android.widget.Toolbar; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; import com.android.settings.R; import com.android.settings.SubSettings; import com.android.settings.Utils; -import com.android.settings.dashboard.CategoryManager; +import com.android.settings.core.CategoryMixin.CategoryHandler; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; -import com.android.settingslib.drawer.Tile; import com.android.settingslib.transition.SettingsTransitionHelper; import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType; @@ -56,12 +50,8 @@ import com.google.android.material.resources.TextAppearanceConfig; import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupdesign.util.ThemeHelper; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class SettingsBaseActivity extends FragmentActivity { +/** Base activity for Settings pages */ +public class SettingsBaseActivity extends FragmentActivity implements CategoryHandler { /** * What type of page transition should be apply. @@ -70,21 +60,18 @@ public class SettingsBaseActivity extends FragmentActivity { protected static final boolean DEBUG_TIMING = false; private static final String TAG = "SettingsBaseActivity"; - private static final String DATA_SCHEME_PKG = "package"; private static final int DEFAULT_REQUEST = -1; - // Serves as a temporary list of tiles to ignore until we heard back from the PM that they - // are disabled. - private static ArraySet<ComponentName> sTileDenylist = new ArraySet<>(); - - private final PackageReceiver mPackageReceiver = new PackageReceiver(); - private final List<CategoryListener> mCategoryListeners = new ArrayList<>(); - + protected CategoryMixin mCategoryMixin; protected CollapsingToolbarLayout mCollapsingToolbarLayout; - private int mCategoriesUpdateTaskCount; private Toolbar mToolbar; @Override + public CategoryMixin getCategoryMixin() { + return mCategoryMixin; + } + + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { if (Utils.isPageTransitionEnabled(this)) { // Enable Activity transitions @@ -102,6 +89,9 @@ public class SettingsBaseActivity extends FragmentActivity { getLifecycle().addObserver(new HideNonSystemOverlayMixin(this)); TextAppearanceConfig.setShouldLoadFontSynchronously(true); + mCategoryMixin = new CategoryMixin(this); + getLifecycle().addObserver(mCategoryMixin); + final TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme); if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) { requestWindowFeature(Window.FEATURE_NO_TITLE); @@ -193,36 +183,14 @@ public class SettingsBaseActivity extends FragmentActivity { } @Override - protected void onResume() { - super.onResume(); - final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(Intent.ACTION_PACKAGE_CHANGED); - filter.addAction(Intent.ACTION_PACKAGE_REPLACED); - filter.addDataScheme(DATA_SCHEME_PKG); - registerReceiver(mPackageReceiver, filter); - - updateCategories(); - } - - @Override protected void onPause() { // For accessibility activities launched from setup wizard. if (getTransitionType(getIntent()) == TransitionType.TRANSITION_FADE) { overridePendingTransition(R.anim.sud_stay, android.R.anim.fade_out); } - unregisterReceiver(mPackageReceiver); super.onPause(); } - public void addCategoryListener(CategoryListener listener) { - mCategoryListeners.add(listener); - } - - public void remCategoryListener(CategoryListener listener) { - mCategoryListeners.remove(listener); - } - @Override public void setContentView(@LayoutRes int layoutResID) { final ViewGroup parent = findViewById(R.id.content_frame); @@ -270,13 +238,6 @@ public class SettingsBaseActivity extends FragmentActivity { return true; } - private void onCategoriesChanged(Set<String> categories) { - final int N = mCategoryListeners.size(); - for (int i = 0; i < N; i++) { - mCategoryListeners.get(i).onCategoriesChanged(categories); - } - } - private boolean isLockTaskModePinned() { final ActivityManager activityManager = getApplicationContext().getSystemService(ActivityManager.class); @@ -300,9 +261,9 @@ public class SettingsBaseActivity extends FragmentActivity { boolean isEnabled = state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; if (isEnabled != enabled || state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { if (enabled) { - sTileDenylist.remove(component); + mCategoryMixin.removeFromDenylist(component); } else { - sTileDenylist.add(component); + mCategoryMixin.addToDenylist(component); } pm.setComponentEnabledSetting(component, enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED @@ -313,29 +274,12 @@ public class SettingsBaseActivity extends FragmentActivity { return false; } - /** - * Updates dashboard categories. Only necessary to call this after setTileEnabled - */ - public void updateCategories() { - updateCategories(false /* fromBroadcast */); - } - - private void updateCategories(boolean fromBroadcast) { - // Only allow at most 2 tasks existing at the same time since when the first one is - // executing, there may be new data from the second update request. - // Ignore the third update request because the second task is still waiting for the first - // task to complete in a serial thread, which will get the latest data. - if (mCategoriesUpdateTaskCount < 2) { - new CategoriesUpdateTask().execute(fromBroadcast); - } - } - private int getTransitionType(Intent intent) { return intent.getIntExtra(EXTRA_PAGE_TRANSITION_TYPE, SettingsTransitionHelper.TransitionType.TRANSITION_SHARED_AXIS); } - @androidx.annotation.Nullable + @Nullable private Bundle createActivityOptionsBundleForTransition( @androidx.annotation.Nullable Bundle options) { if (mToolbar == null) { @@ -352,87 +296,4 @@ public class SettingsBaseActivity extends FragmentActivity { return mergedOptions; } - public interface CategoryListener { - /** - * @param categories the changed categories that have to be refreshed, or null to force - * refreshing all. - */ - void onCategoriesChanged(@Nullable Set<String> categories); - } - - private class CategoriesUpdateTask extends AsyncTask<Boolean, Void, Set<String>> { - - private final Context mContext; - private final CategoryManager mCategoryManager; - private Map<ComponentName, Tile> mPreviousTileMap; - - public CategoriesUpdateTask() { - mCategoriesUpdateTaskCount++; - mContext = SettingsBaseActivity.this; - mCategoryManager = CategoryManager.get(mContext); - } - - @Override - protected Set<String> doInBackground(Boolean... params) { - mPreviousTileMap = mCategoryManager.getTileByComponentMap(); - mCategoryManager.reloadAllCategories(mContext); - mCategoryManager.updateCategoryFromDenylist(sTileDenylist); - return getChangedCategories(params[0]); - } - - @Override - protected void onPostExecute(Set<String> categories) { - if (categories == null || !categories.isEmpty()) { - onCategoriesChanged(categories); - } - mCategoriesUpdateTaskCount--; - } - - // Return the changed categories that have to be refreshed, or null to force refreshing all. - private Set<String> getChangedCategories(boolean fromBroadcast) { - if (!fromBroadcast) { - // Always refresh for non-broadcast case. - return null; - } - - final Set<String> changedCategories = new ArraySet<>(); - final Map<ComponentName, Tile> currentTileMap = - mCategoryManager.getTileByComponentMap(); - currentTileMap.forEach((component, currentTile) -> { - final Tile previousTile = mPreviousTileMap.get(component); - // Check if the tile is newly added. - if (previousTile == null) { - Log.i(TAG, "Tile added: " + component.flattenToShortString()); - changedCategories.add(currentTile.getCategory()); - return; - } - - // Check if the title or summary has changed. - if (!TextUtils.equals(currentTile.getTitle(mContext), - previousTile.getTitle(mContext)) - || !TextUtils.equals(currentTile.getSummary(mContext), - previousTile.getSummary(mContext))) { - Log.i(TAG, "Tile changed: " + component.flattenToShortString()); - changedCategories.add(currentTile.getCategory()); - } - }); - - // Check if any previous tile is removed. - final Set<ComponentName> removal = new ArraySet(mPreviousTileMap.keySet()); - removal.removeAll(currentTileMap.keySet()); - removal.forEach(component -> { - Log.i(TAG, "Tile removed: " + component.flattenToShortString()); - changedCategories.add(mPreviousTileMap.get(component).getCategory()); - }); - - return changedCategories; - } - } - - private class PackageReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - updateCategories(true /* fromBroadcast */); - } - } } diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index 29a11a3e05..dfd931db7e 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -35,8 +35,9 @@ import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.CategoryMixin.CategoryHandler; +import com.android.settings.core.CategoryMixin.CategoryListener; import com.android.settings.core.PreferenceControllerListHelper; -import com.android.settings.core.SettingsBaseActivity; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.PrimarySwitchPreference; import com.android.settingslib.core.AbstractPreferenceController; @@ -61,8 +62,7 @@ import java.util.concurrent.ExecutionException; * Base fragment for dashboard style UI containing a list of static and dynamic setting items. */ public abstract class DashboardFragment extends SettingsPreferenceFragment - implements SettingsBaseActivity.CategoryListener, Indexable, - PreferenceGroup.OnExpandButtonClickListener, + implements CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener, BasePreferenceController.UiBlockListener { public static final String CATEGORY = "category"; private static final String TAG = "DashboardFragment"; @@ -198,9 +198,9 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment return; } final Activity activity = getActivity(); - if (activity instanceof SettingsBaseActivity) { + if (activity instanceof CategoryHandler) { mListeningToCategoryChange = true; - ((SettingsBaseActivity) activity).addCategoryListener(this); + ((CategoryHandler) activity).getCategoryMixin().addCategoryListener(this); } final ContentResolver resolver = getContentResolver(); mDashboardTilePrefKeys.values().stream() @@ -243,8 +243,8 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment unregisterDynamicDataObservers(new ArrayList<>(mRegisteredObservers)); if (mListeningToCategoryChange) { final Activity activity = getActivity(); - if (activity instanceof SettingsBaseActivity) { - ((SettingsBaseActivity) activity).remCategoryListener(this); + if (activity instanceof CategoryHandler) { + ((CategoryHandler) activity).getCategoryMixin().removeCategoryListener(this); } mListeningToCategoryChange = false; } diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 5950e4b8ba..f3fdf5a6bc 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -38,13 +38,16 @@ import androidx.fragment.app.FragmentTransaction; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.accounts.AvatarViewMixin; +import com.android.settings.core.CategoryMixin; import com.android.settings.core.FeatureFlags; import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; import com.android.settingslib.transition.SettingsTransitionHelper; -public class SettingsHomepageActivity extends FragmentActivity { +/** Settings homepage activity */ +public class SettingsHomepageActivity extends FragmentActivity implements + CategoryMixin.CategoryHandler { private static final String TAG = "SettingsHomepageActivity"; @@ -52,6 +55,12 @@ public class SettingsHomepageActivity extends FragmentActivity { private View mHomepageView; private View mSuggestionView; + private CategoryMixin mCategoryMixin; + + @Override + public CategoryMixin getCategoryMixin() { + return mCategoryMixin; + } /** * Shows the homepage and shows/hides the suggestion together. Only allows to be executed once @@ -87,6 +96,8 @@ public class SettingsHomepageActivity extends FragmentActivity { .initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE); getLifecycle().addObserver(new HideNonSystemOverlayMixin(this)); + mCategoryMixin = new CategoryMixin(this); + getLifecycle().addObserver(mCategoryMixin); if (!getSystemService(ActivityManager.class).isLowRamDevice()) { // Only allow features on high ram devices. |