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
This commit is contained in:
@@ -36,10 +36,8 @@ import android.os.Bundle;
|
|||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.FeatureFlagUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -55,7 +53,6 @@ import androidx.preference.PreferenceManager;
|
|||||||
import com.android.internal.util.ArrayUtils;
|
import com.android.internal.util.ArrayUtils;
|
||||||
import com.android.settings.Settings.WifiSettingsActivity;
|
import com.android.settings.Settings.WifiSettingsActivity;
|
||||||
import com.android.settings.applications.manageapplications.ManageApplications;
|
import com.android.settings.applications.manageapplications.ManageApplications;
|
||||||
import com.android.settings.core.FeatureFlags;
|
|
||||||
import com.android.settings.core.OnActivityResultListener;
|
import com.android.settings.core.OnActivityResultListener;
|
||||||
import com.android.settings.core.SettingsBaseActivity;
|
import com.android.settings.core.SettingsBaseActivity;
|
||||||
import com.android.settings.core.SubSettingLauncher;
|
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.development.DevelopmentSettingsEnabler;
|
||||||
import com.android.settingslib.drawer.DashboardCategory;
|
import com.android.settingslib.drawer.DashboardCategory;
|
||||||
|
|
||||||
import com.google.android.material.transition.platform.MaterialSharedAxis;
|
|
||||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -689,7 +685,7 @@ public class SettingsActivity extends SettingsBaseActivity
|
|||||||
if (somethingChanged) {
|
if (somethingChanged) {
|
||||||
Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories "
|
Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories "
|
||||||
+ changedList.toString());
|
+ changedList.toString());
|
||||||
updateCategories();
|
mCategoryMixin.updateCategories();
|
||||||
} else {
|
} else {
|
||||||
Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call");
|
Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call");
|
||||||
}
|
}
|
||||||
|
225
src/com/android/settings/core/CategoryMixin.java
Normal file
225
src/com/android/settings/core/CategoryMixin.java
Normal file
@@ -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 */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -16,21 +16,15 @@
|
|||||||
package com.android.settings.core;
|
package com.android.settings.core;
|
||||||
|
|
||||||
import android.annotation.LayoutRes;
|
import android.annotation.LayoutRes;
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.ActivityOptions;
|
import android.app.ActivityOptions;
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.ArraySet;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -40,14 +34,14 @@ import android.view.Window;
|
|||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.SubSettings;
|
import com.android.settings.SubSettings;
|
||||||
import com.android.settings.Utils;
|
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.core.lifecycle.HideNonSystemOverlayMixin;
|
||||||
import com.android.settingslib.drawer.Tile;
|
|
||||||
import com.android.settingslib.transition.SettingsTransitionHelper;
|
import com.android.settingslib.transition.SettingsTransitionHelper;
|
||||||
import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType;
|
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.setupcompat.util.WizardManagerHelper;
|
||||||
import com.google.android.setupdesign.util.ThemeHelper;
|
import com.google.android.setupdesign.util.ThemeHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
/** Base activity for Settings pages */
|
||||||
import java.util.List;
|
public class SettingsBaseActivity extends FragmentActivity implements CategoryHandler {
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class SettingsBaseActivity extends FragmentActivity {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* What type of page transition should be apply.
|
* What type of page transition should be apply.
|
||||||
@@ -70,20 +60,17 @@ public class SettingsBaseActivity extends FragmentActivity {
|
|||||||
|
|
||||||
protected static final boolean DEBUG_TIMING = false;
|
protected static final boolean DEBUG_TIMING = false;
|
||||||
private static final String TAG = "SettingsBaseActivity";
|
private static final String TAG = "SettingsBaseActivity";
|
||||||
private static final String DATA_SCHEME_PKG = "package";
|
|
||||||
private static final int DEFAULT_REQUEST = -1;
|
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
|
protected CategoryMixin mCategoryMixin;
|
||||||
// are disabled.
|
|
||||||
private static ArraySet<ComponentName> sTileDenylist = new ArraySet<>();
|
|
||||||
|
|
||||||
private final PackageReceiver mPackageReceiver = new PackageReceiver();
|
|
||||||
private final List<CategoryListener> mCategoryListeners = new ArrayList<>();
|
|
||||||
|
|
||||||
protected CollapsingToolbarLayout mCollapsingToolbarLayout;
|
protected CollapsingToolbarLayout mCollapsingToolbarLayout;
|
||||||
private int mCategoriesUpdateTaskCount;
|
|
||||||
private Toolbar mToolbar;
|
private Toolbar mToolbar;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CategoryMixin getCategoryMixin() {
|
||||||
|
return mCategoryMixin;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
if (Utils.isPageTransitionEnabled(this)) {
|
if (Utils.isPageTransitionEnabled(this)) {
|
||||||
@@ -102,6 +89,9 @@ public class SettingsBaseActivity extends FragmentActivity {
|
|||||||
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
|
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
|
||||||
TextAppearanceConfig.setShouldLoadFontSynchronously(true);
|
TextAppearanceConfig.setShouldLoadFontSynchronously(true);
|
||||||
|
|
||||||
|
mCategoryMixin = new CategoryMixin(this);
|
||||||
|
getLifecycle().addObserver(mCategoryMixin);
|
||||||
|
|
||||||
final TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme);
|
final TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme);
|
||||||
if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
|
if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
@@ -192,37 +182,15 @@ public class SettingsBaseActivity extends FragmentActivity {
|
|||||||
userHandle);
|
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
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
// For accessibility activities launched from setup wizard.
|
// For accessibility activities launched from setup wizard.
|
||||||
if (getTransitionType(getIntent()) == TransitionType.TRANSITION_FADE) {
|
if (getTransitionType(getIntent()) == TransitionType.TRANSITION_FADE) {
|
||||||
overridePendingTransition(R.anim.sud_stay, android.R.anim.fade_out);
|
overridePendingTransition(R.anim.sud_stay, android.R.anim.fade_out);
|
||||||
}
|
}
|
||||||
unregisterReceiver(mPackageReceiver);
|
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCategoryListener(CategoryListener listener) {
|
|
||||||
mCategoryListeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remCategoryListener(CategoryListener listener) {
|
|
||||||
mCategoryListeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setContentView(@LayoutRes int layoutResID) {
|
public void setContentView(@LayoutRes int layoutResID) {
|
||||||
final ViewGroup parent = findViewById(R.id.content_frame);
|
final ViewGroup parent = findViewById(R.id.content_frame);
|
||||||
@@ -270,13 +238,6 @@ public class SettingsBaseActivity extends FragmentActivity {
|
|||||||
return true;
|
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() {
|
private boolean isLockTaskModePinned() {
|
||||||
final ActivityManager activityManager =
|
final ActivityManager activityManager =
|
||||||
getApplicationContext().getSystemService(ActivityManager.class);
|
getApplicationContext().getSystemService(ActivityManager.class);
|
||||||
@@ -300,9 +261,9 @@ public class SettingsBaseActivity extends FragmentActivity {
|
|||||||
boolean isEnabled = state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
boolean isEnabled = state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
||||||
if (isEnabled != enabled || state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
|
if (isEnabled != enabled || state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
sTileDenylist.remove(component);
|
mCategoryMixin.removeFromDenylist(component);
|
||||||
} else {
|
} else {
|
||||||
sTileDenylist.add(component);
|
mCategoryMixin.addToDenylist(component);
|
||||||
}
|
}
|
||||||
pm.setComponentEnabledSetting(component, enabled
|
pm.setComponentEnabledSetting(component, enabled
|
||||||
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||||
@@ -313,29 +274,12 @@ public class SettingsBaseActivity extends FragmentActivity {
|
|||||||
return false;
|
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) {
|
private int getTransitionType(Intent intent) {
|
||||||
return intent.getIntExtra(EXTRA_PAGE_TRANSITION_TYPE,
|
return intent.getIntExtra(EXTRA_PAGE_TRANSITION_TYPE,
|
||||||
SettingsTransitionHelper.TransitionType.TRANSITION_SHARED_AXIS);
|
SettingsTransitionHelper.TransitionType.TRANSITION_SHARED_AXIS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@androidx.annotation.Nullable
|
@Nullable
|
||||||
private Bundle createActivityOptionsBundleForTransition(
|
private Bundle createActivityOptionsBundleForTransition(
|
||||||
@androidx.annotation.Nullable Bundle options) {
|
@androidx.annotation.Nullable Bundle options) {
|
||||||
if (mToolbar == null) {
|
if (mToolbar == null) {
|
||||||
@@ -352,87 +296,4 @@ public class SettingsBaseActivity extends FragmentActivity {
|
|||||||
return mergedOptions;
|
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 */);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -35,8 +35,9 @@ import androidx.preference.SwitchPreference;
|
|||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.SettingsPreferenceFragment;
|
import com.android.settings.SettingsPreferenceFragment;
|
||||||
import com.android.settings.core.BasePreferenceController;
|
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.PreferenceControllerListHelper;
|
||||||
import com.android.settings.core.SettingsBaseActivity;
|
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
import com.android.settings.widget.PrimarySwitchPreference;
|
import com.android.settings.widget.PrimarySwitchPreference;
|
||||||
import com.android.settingslib.core.AbstractPreferenceController;
|
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.
|
* Base fragment for dashboard style UI containing a list of static and dynamic setting items.
|
||||||
*/
|
*/
|
||||||
public abstract class DashboardFragment extends SettingsPreferenceFragment
|
public abstract class DashboardFragment extends SettingsPreferenceFragment
|
||||||
implements SettingsBaseActivity.CategoryListener, Indexable,
|
implements CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener,
|
||||||
PreferenceGroup.OnExpandButtonClickListener,
|
|
||||||
BasePreferenceController.UiBlockListener {
|
BasePreferenceController.UiBlockListener {
|
||||||
public static final String CATEGORY = "category";
|
public static final String CATEGORY = "category";
|
||||||
private static final String TAG = "DashboardFragment";
|
private static final String TAG = "DashboardFragment";
|
||||||
@@ -198,9 +198,9 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
if (activity instanceof SettingsBaseActivity) {
|
if (activity instanceof CategoryHandler) {
|
||||||
mListeningToCategoryChange = true;
|
mListeningToCategoryChange = true;
|
||||||
((SettingsBaseActivity) activity).addCategoryListener(this);
|
((CategoryHandler) activity).getCategoryMixin().addCategoryListener(this);
|
||||||
}
|
}
|
||||||
final ContentResolver resolver = getContentResolver();
|
final ContentResolver resolver = getContentResolver();
|
||||||
mDashboardTilePrefKeys.values().stream()
|
mDashboardTilePrefKeys.values().stream()
|
||||||
@@ -243,8 +243,8 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
|
|||||||
unregisterDynamicDataObservers(new ArrayList<>(mRegisteredObservers));
|
unregisterDynamicDataObservers(new ArrayList<>(mRegisteredObservers));
|
||||||
if (mListeningToCategoryChange) {
|
if (mListeningToCategoryChange) {
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
if (activity instanceof SettingsBaseActivity) {
|
if (activity instanceof CategoryHandler) {
|
||||||
((SettingsBaseActivity) activity).remCategoryListener(this);
|
((CategoryHandler) activity).getCategoryMixin().removeCategoryListener(this);
|
||||||
}
|
}
|
||||||
mListeningToCategoryChange = false;
|
mListeningToCategoryChange = false;
|
||||||
}
|
}
|
||||||
|
@@ -38,13 +38,16 @@ import androidx.fragment.app.FragmentTransaction;
|
|||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.Utils;
|
import com.android.settings.Utils;
|
||||||
import com.android.settings.accounts.AvatarViewMixin;
|
import com.android.settings.accounts.AvatarViewMixin;
|
||||||
|
import com.android.settings.core.CategoryMixin;
|
||||||
import com.android.settings.core.FeatureFlags;
|
import com.android.settings.core.FeatureFlags;
|
||||||
import com.android.settings.homepage.contextualcards.ContextualCardsFragment;
|
import com.android.settings.homepage.contextualcards.ContextualCardsFragment;
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin;
|
import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin;
|
||||||
import com.android.settingslib.transition.SettingsTransitionHelper;
|
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";
|
private static final String TAG = "SettingsHomepageActivity";
|
||||||
|
|
||||||
@@ -52,6 +55,12 @@ public class SettingsHomepageActivity extends FragmentActivity {
|
|||||||
|
|
||||||
private View mHomepageView;
|
private View mHomepageView;
|
||||||
private View mSuggestionView;
|
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
|
* 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);
|
.initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);
|
||||||
|
|
||||||
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
|
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
|
||||||
|
mCategoryMixin = new CategoryMixin(this);
|
||||||
|
getLifecycle().addObserver(mCategoryMixin);
|
||||||
|
|
||||||
if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
|
if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
|
||||||
// Only allow features on high ram devices.
|
// Only allow features on high ram devices.
|
||||||
|
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.anySet;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.android.settings.core.CategoryMixin.CategoryListener;
|
||||||
|
import com.android.settingslib.R;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.Robolectric;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.android.controller.ActivityController;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class CategoryMixinTest {
|
||||||
|
private ActivityController<TestActivity> mActivityController;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mActivityController = Robolectric.buildActivity(TestActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resumeActivity_shouldRegisterReceiver() {
|
||||||
|
mActivityController.setup();
|
||||||
|
|
||||||
|
final TestActivity activity = mActivityController.get();
|
||||||
|
assertThat(activity.getRegisteredReceivers()).isNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void pauseActivity_shouldUnregisterReceiver() {
|
||||||
|
mActivityController.setup().pause();
|
||||||
|
|
||||||
|
final TestActivity activity = mActivityController.get();
|
||||||
|
assertThat(activity.getRegisteredReceivers()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onCategoriesChanged_listenerAdded_shouldNotifyChanged() {
|
||||||
|
mActivityController.setup().pause();
|
||||||
|
final CategoryMixin categoryMixin = mActivityController.get().getCategoryMixin();
|
||||||
|
final CategoryListener listener = mock(CategoryListener.class);
|
||||||
|
categoryMixin.addCategoryListener(listener);
|
||||||
|
|
||||||
|
categoryMixin.onCategoriesChanged(new ArraySet<>());
|
||||||
|
|
||||||
|
verify(listener).onCategoriesChanged(anySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestActivity extends AppCompatActivity implements CategoryMixin.CategoryHandler {
|
||||||
|
|
||||||
|
private CategoryMixin mCategoryMixin;
|
||||||
|
private List<BroadcastReceiver> mRegisteredReceivers = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setTheme(R.style.Theme_AppCompat);
|
||||||
|
mCategoryMixin = new CategoryMixin(this);
|
||||||
|
getLifecycle().addObserver(mCategoryMixin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CategoryMixin getCategoryMixin() {
|
||||||
|
return mCategoryMixin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
|
||||||
|
mRegisteredReceivers.add(receiver);
|
||||||
|
return super.registerReceiver(receiver, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterReceiver(BroadcastReceiver receiver) {
|
||||||
|
mRegisteredReceivers.remove(receiver);
|
||||||
|
super.unregisterReceiver(receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BroadcastReceiver> getRegisteredReceivers() {
|
||||||
|
return mRegisteredReceivers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user