Loading src/com/android/settings/SettingsActivity.java +1 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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"); } Loading src/com/android/settings/core/CategoryMixin.java 0 → 100644 +225 −0 Original line number Diff line number Diff line /* * 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 */); } } } src/com/android/settings/core/SettingsBaseActivity.java +16 −155 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -70,20 +60,17 @@ 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)) { Loading @@ -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); Loading Loading @@ -192,37 +182,15 @@ public class SettingsBaseActivity extends FragmentActivity { userHandle); } @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); Loading Loading @@ -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); Loading @@ -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 Loading @@ -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) { Loading @@ -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 */); } } } src/com/android/settings/dashboard/DashboardFragment.java +7 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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() Loading Loading @@ -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; } Loading src/com/android/settings/homepage/SettingsHomepageActivity.java +12 −1 Original line number Diff line number Diff line Loading @@ -34,12 +34,15 @@ import androidx.fragment.app.FragmentTransaction; import com.android.settings.R; 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; public class SettingsHomepageActivity extends FragmentActivity { /** Settings homepage activity */ public class SettingsHomepageActivity extends FragmentActivity implements CategoryMixin.CategoryHandler { private static final String TAG = "SettingsHomepageActivity"; Loading @@ -47,6 +50,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 Loading Loading @@ -76,6 +85,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. Loading Loading
src/com/android/settings/SettingsActivity.java +1 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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"); } Loading
src/com/android/settings/core/CategoryMixin.java 0 → 100644 +225 −0 Original line number Diff line number Diff line /* * 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 */); } } }
src/com/android/settings/core/SettingsBaseActivity.java +16 −155 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -70,20 +60,17 @@ 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)) { Loading @@ -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); Loading Loading @@ -192,37 +182,15 @@ public class SettingsBaseActivity extends FragmentActivity { userHandle); } @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); Loading Loading @@ -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); Loading @@ -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 Loading @@ -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) { Loading @@ -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 */); } } }
src/com/android/settings/dashboard/DashboardFragment.java +7 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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() Loading Loading @@ -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; } Loading
src/com/android/settings/homepage/SettingsHomepageActivity.java +12 −1 Original line number Diff line number Diff line Loading @@ -34,12 +34,15 @@ import androidx.fragment.app.FragmentTransaction; import com.android.settings.R; 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; public class SettingsHomepageActivity extends FragmentActivity { /** Settings homepage activity */ public class SettingsHomepageActivity extends FragmentActivity implements CategoryMixin.CategoryHandler { private static final String TAG = "SettingsHomepageActivity"; Loading @@ -47,6 +50,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 Loading Loading @@ -76,6 +85,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. Loading