Loading src/com/android/settings/SettingsActivity.java +1 −7 Original line number Diff line number Diff line Loading @@ -583,12 +583,7 @@ public class SettingsActivity extends SettingsBaseActivity // Generally the items that are will be changing from these updates will // not be in the top list of tiles, so run it in the background and the // SettingsBaseActivity will pick up on the updates automatically. AsyncTask.execute(new Runnable() { @Override public void run() { doUpdateTilesList(); } }); AsyncTask.execute(() -> doUpdateTilesList()); } private void doUpdateTilesList() { Loading Loading @@ -648,7 +643,6 @@ public class SettingsActivity extends SettingsBaseActivity || somethingChanged; if (UserHandle.MU_ENABLED && !isAdmin) { // When on restricted users, disable all extra categories (but only the settings ones). final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories(); synchronized (categories) { Loading src/com/android/settings/dashboard/CategoryManager.java +16 −5 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import androidx.annotation.VisibleForTesting; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.ProviderTile; import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.TileUtils; Loading Loading @@ -189,16 +190,25 @@ public class CategoryManager { /** * Filter out duplicate tiles from category. Duplicate tiles are the ones pointing to the * same intent. * same intent for ActivityTile, and also the ones having the same description for ProviderTile. */ @VisibleForTesting synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) { for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) { final DashboardCategory category = categoryEntry.getValue(); final int count = category.getTilesCount(); final Set<String> descriptions = new ArraySet<>(); final Set<ComponentName> components = new ArraySet<>(); for (int i = count - 1; i >= 0; i--) { final Tile tile = category.getTile(i); if (tile instanceof ProviderTile) { final String desc = tile.getDescription(); if (descriptions.contains(desc)) { category.removeTile(i); } else { descriptions.add(desc); } } else { final ComponentName tileComponent = tile.getIntent().getComponent(); if (components.contains(tileComponent)) { category.removeTile(i); Loading @@ -209,3 +219,4 @@ public class CategoryManager { } } } } src/com/android/settings/dashboard/DashboardFeatureProvider.java +7 −4 Original line number Diff line number Diff line Loading @@ -44,19 +44,22 @@ public interface DashboardFeatureProvider { String getDashboardKeyForTile(Tile tile); /** * Binds preference to data provided by tile. * Binds preference to data provided by tile and gets dynamic data observers. * * @param activity If tile contains intent to launch, it will be launched from this activity * @param forceRoundedIcon Whether or not injected tiles from other packages should be forced to rounded icon. * @param forceRoundedIcon Whether or not injected tiles from other packages should be forced to * rounded icon. * @param sourceMetricsCategory The context (source) from which an action is performed * @param pref The preference to bind data * @param tile The binding data * @param key They key for preference. If null, we will generate one from tile data * @param baseOrder The order offset value. When binding, pref's order is determined by * both this value and tile's own priority. * @return The list of dynamic data observers */ void bindPreferenceToTile(FragmentActivity activity, boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder); List<DynamicDataObserver> bindPreferenceToTileAndGetObservers(FragmentActivity activity, boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder); /** * Opens a tile to its destination intent. Loading src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +153 −30 Original line number Diff line number Diff line Loading @@ -18,9 +18,18 @@ package com.android.settings.dashboard; import static android.content.Intent.EXTRA_USER; import static com.android.settingslib.drawer.SwitchesProvider.EXTRA_SWITCH_CHECKED_STATE; import static com.android.settingslib.drawer.SwitchesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR; import static com.android.settingslib.drawer.SwitchesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE; import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY; import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE; import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_PROVIDER_ICON; import static com.android.settingslib.drawer.SwitchesProvider.METHOD_IS_CHECKED; import static com.android.settingslib.drawer.SwitchesProvider.METHOD_ON_CHECKED_CHANGED; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI; Loading @@ -40,22 +49,26 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.widget.Toast; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.dashboard.profileselector.ProfileSelectDialog; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.drawer.ActivityTile; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.TileUtils; import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.widget.AdaptiveIcon; import java.util.ArrayList; import java.util.List; import java.util.Map; Loading Loading @@ -106,20 +119,33 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { } @Override public void bindPreferenceToTile(FragmentActivity activity, boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder) { public List<DynamicDataObserver> bindPreferenceToTileAndGetObservers(FragmentActivity activity, boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder) { if (pref == null) { return; return null; } if (!TextUtils.isEmpty(key)) { pref.setKey(key); } else { pref.setKey(getDashboardKeyForTile(tile)); } bindTitle(pref, tile); bindSummary(pref, tile); final List<DynamicDataObserver> outObservers = new ArrayList<>(); DynamicDataObserver observer = bindTitleAndGetObserver(pref, tile); if (observer != null) { outObservers.add(observer); } observer = bindSummaryAndGetObserver(pref, tile); if (observer != null) { outObservers.add(observer); } observer = bindSwitchAndGetObserver(pref, tile); if (observer != null) { outObservers.add(observer); } bindIcon(pref, tile, forceRoundedIcon); if (tile instanceof ActivityTile) { final Bundle metadata = tile.getMetaData(); String clsName = null; String action = null; Loading @@ -141,6 +167,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { return true; }); } } if (tile.hasOrder()) { final String skipOffsetPackageName = activity.getPackageName(); Loading @@ -153,6 +180,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { pref.setOrder(order + baseOrder); } } return outObservers.isEmpty() ? null : outObservers; } @Override Loading @@ -170,11 +198,35 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { launchIntentOrSelectProfile(activity, tile, intent, SettingsEnums.DASHBOARD_SUMMARY); } private void bindTitle(Preference preference, Tile tile) { private DynamicDataObserver createDynamicDataObserver(String method, Uri uri, Preference pref) { return new DynamicDataObserver() { @Override public Uri getUri() { return uri; } @Override public void onDataChanged() { switch (method) { case METHOD_GET_DYNAMIC_TITLE: refreshTitle(uri, pref); break; case METHOD_GET_DYNAMIC_SUMMARY: refreshSummary(uri, pref); break; case METHOD_IS_CHECKED: refreshSwitch(uri, pref); break; } } }; } private DynamicDataObserver bindTitleAndGetObserver(Preference preference, Tile tile) { final CharSequence title = tile.getTitle(mContext.getApplicationContext()); if (title != null) { preference.setTitle(title); return; return null; } if (tile.getMetaData() != null && tile.getMetaData().containsKey( META_DATA_PREFERENCE_TITLE_URI)) { Loading @@ -182,9 +234,12 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { // to avoid preference height change. preference.setTitle(R.string.summary_placeholder); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_TITLE_URI); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_TITLE_URI, METHOD_GET_DYNAMIC_TITLE); refreshTitle(uri, preference); return createDynamicDataObserver(METHOD_GET_DYNAMIC_TITLE, uri, preference); } return null; } private void refreshTitle(Uri uri, Preference preference) { Loading @@ -196,7 +251,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { }); } private void bindSummary(Preference preference, Tile tile) { private DynamicDataObserver bindSummaryAndGetObserver(Preference preference, Tile tile) { final CharSequence summary = tile.getSummary(mContext); if (summary != null) { preference.setSummary(summary); Loading @@ -206,11 +261,14 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { // to avoid preference height change. preference.setSummary(R.string.summary_placeholder); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SUMMARY_URI); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SUMMARY_URI, METHOD_GET_DYNAMIC_SUMMARY); refreshSummary(uri, preference); return createDynamicDataObserver(METHOD_GET_DYNAMIC_SUMMARY, uri, preference); } else { preference.setSummary(R.string.summary_placeholder); } return null; } private void refreshSummary(Uri uri, Preference preference) { Loading @@ -222,6 +280,70 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { }); } private DynamicDataObserver bindSwitchAndGetObserver(Preference preference, Tile tile) { if (!tile.hasSwitch()) { return null; } final Uri onCheckedChangedUri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SWITCH_URI, METHOD_ON_CHECKED_CHANGED); preference.setOnPreferenceChangeListener((pref, newValue) -> { onCheckedChanged(onCheckedChangedUri, pref, (boolean) newValue); return true; }); final Uri isCheckedUri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SWITCH_URI, METHOD_IS_CHECKED); setSwitchEnabled(preference, false); refreshSwitch(isCheckedUri, preference); return createDynamicDataObserver(METHOD_IS_CHECKED, isCheckedUri, preference); } private void onCheckedChanged(Uri uri, Preference pref, boolean checked) { setSwitchEnabled(pref, false); ThreadUtils.postOnBackgroundThread(() -> { final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final Bundle result = TileUtils.putBooleanToUri(mContext, uri, providerMap, EXTRA_SWITCH_CHECKED_STATE, checked); ThreadUtils.postOnMainThread(() -> { setSwitchEnabled(pref, true); final boolean error = result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR); if (!error) { return; } setSwitchChecked(pref, !checked); final String errorMsg = result.getString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE); if (!TextUtils.isEmpty(errorMsg)) { Toast.makeText(mContext, errorMsg, Toast.LENGTH_SHORT).show(); } }); }); } private void refreshSwitch(Uri uri, Preference preference) { ThreadUtils.postOnBackgroundThread(() -> { final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final boolean checked = TileUtils.getBooleanFromUri(mContext, uri, providerMap, EXTRA_SWITCH_CHECKED_STATE); ThreadUtils.postOnMainThread(() -> { setSwitchChecked(preference, checked); setSwitchEnabled(preference, true); }); }); } private void setSwitchChecked(Preference pref, boolean checked) { if (pref instanceof SwitchPreference) { ((SwitchPreference) pref).setChecked(checked); } } private void setSwitchEnabled(Preference pref, boolean enabled) { pref.setEnabled(enabled); } @VisibleForTesting void bindIcon(Preference preference, Tile tile, boolean forceRoundedIcon) { // Use preference context instead here when get icon from Tile, as we are using the context Loading @@ -246,7 +368,8 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { packageName = intent.getComponent().getPackageName(); } final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_ICON_URI); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_ICON_URI, METHOD_GET_PROVIDER_ICON); final Pair<String, Integer> iconInfo = TileUtils.getIconFromUri( mContext, packageName, uri, providerMap); if (iconInfo == null) { Loading src/com/android/settings/dashboard/DashboardFragment.java +75 −21 Original line number Diff line number Diff line Loading @@ -17,11 +17,11 @@ package com.android.settings.dashboard; import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.ContentResolver; import android.content.Context; import android.os.Bundle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import androidx.annotation.CallSuper; Loading @@ -30,6 +30,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; Loading @@ -41,6 +42,7 @@ import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.ProviderTile; import com.android.settingslib.drawer.Tile; import com.android.settingslib.search.Indexable; Loading @@ -49,7 +51,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Objects; /** * Base fragment for dashboard style UI containing a list of static and dynamic setting items. Loading @@ -60,9 +62,11 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment BasePreferenceController.UiBlockListener { private static final String TAG = "DashboardFragment"; @VisibleForTesting final ArrayMap<String, List<DynamicDataObserver>> mDashboardTilePrefKeys = new ArrayMap<>(); private final Map<Class, List<AbstractPreferenceController>> mPreferenceControllers = new ArrayMap<>(); private final Set<String> mDashboardTilePrefKeys = new ArraySet<>(); private final List<DynamicDataObserver> mRegisteredObservers = new ArrayList<>(); private final List<AbstractPreferenceController> mControllers = new ArrayList<>(); private DashboardFeatureProvider mDashboardFeatureProvider; Loading Loading @@ -171,6 +175,15 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment mListeningToCategoryChange = true; ((SettingsBaseActivity) activity).addCategoryListener(this); } final ContentResolver resolver = getContentResolver(); mDashboardTilePrefKeys.values().stream() .filter(Objects::nonNull) .flatMap(List::stream) .forEach(observer -> { if (!mRegisteredObservers.contains(observer)) { registerDynamicDataObserver(resolver, observer); } }); } @Override Loading Loading @@ -200,6 +213,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment @Override public void onStop() { super.onStop(); unregisterDynamicDataObservers(new ArrayList<>(mRegisteredObservers)); if (mListeningToCategoryChange) { final Activity activity = getActivity(); if (activity instanceof SettingsBaseActivity) { Loading Loading @@ -325,7 +339,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment * Refresh all preference items, including both static prefs from xml, and dynamic items from * DashboardCategory. */ private void refreshAllPreferences(final String TAG) { private void refreshAllPreferences(final String tag) { final PreferenceScreen screen = getPreferenceScreen(); // First remove old preferences. if (screen != null) { Loading @@ -336,11 +350,11 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment // Add resource based tiles. displayResourceTiles(); refreshDashboardTiles(TAG); refreshDashboardTiles(tag); final Activity activity = getActivity(); if (activity != null) { Log.d(TAG, "All preferences added, reporting fully drawn"); Log.d(tag, "All preferences added, reporting fully drawn"); activity.reportFullyDrawn(); } Loading Loading @@ -371,59 +385,62 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment /** * Refresh preference items backed by DashboardCategory. */ @VisibleForTesting void refreshDashboardTiles(final String TAG) { private void refreshDashboardTiles(final String tag) { final PreferenceScreen screen = getPreferenceScreen(); final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(getCategoryKey()); if (category == null) { Log.d(TAG, "NO dashboard tiles for " + TAG); Log.d(tag, "NO dashboard tiles for " + tag); return; } final List<Tile> tiles = category.getTiles(); if (tiles == null) { Log.d(TAG, "tile list is empty, skipping category " + category.key); Log.d(tag, "tile list is empty, skipping category " + category.key); return; } // Create a list to track which tiles are to be removed. final List<String> remove = new ArrayList<>(mDashboardTilePrefKeys); final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys); // Install dashboard tiles. final boolean forceRoundedIcons = shouldForceRoundedIcon(); for (Tile tile : tiles) { final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile); if (TextUtils.isEmpty(key)) { Log.d(TAG, "tile does not contain a key, skipping " + tile); Log.d(tag, "tile does not contain a key, skipping " + tile); continue; } if (!displayTile(tile)) { continue; } if (mDashboardTilePrefKeys.contains(key)) { if (mDashboardTilePrefKeys.containsKey(key)) { // Have the key already, will rebind. final Preference preference = screen.findPreference(key); mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons, getMetricsCategory(), preference, tile, key, mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(), forceRoundedIcons, getMetricsCategory(), preference, tile, key, mPlaceholderPreferenceController.getOrder()); } else { // Don't have this key, add it. final Preference pref = new Preference(getPrefContext()); mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons, getMetricsCategory(), pref, tile, key, final Preference pref = createPreference(tile); final List<DynamicDataObserver> observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(), forceRoundedIcons, getMetricsCategory(), pref, tile, key, mPlaceholderPreferenceController.getOrder()); screen.addPreference(pref); mDashboardTilePrefKeys.add(key); registerDynamicDataObservers(observers); mDashboardTilePrefKeys.put(key, observers); } remove.remove(key); } // Finally remove tiles that are gone. for (String key : remove) { for (Map.Entry<String, List<DynamicDataObserver>> entry : remove.entrySet()) { final String key = entry.getKey(); mDashboardTilePrefKeys.remove(key); final Preference preference = screen.findPreference(key); if (preference != null) { screen.removePreference(preference); } unregisterDynamicDataObservers(entry.getValue()); } } Loading @@ -431,4 +448,41 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment public void onBlockerWorkFinished(BasePreferenceController controller) { mBlockerController.countDown(controller.getPreferenceKey()); } @VisibleForTesting Preference createPreference(Tile tile) { return tile instanceof ProviderTile ? new SwitchPreference(getPrefContext()) : new Preference(getPrefContext()); } @VisibleForTesting void registerDynamicDataObservers(List<DynamicDataObserver> observers) { if (observers == null || observers.isEmpty()) { return; } final ContentResolver resolver = getContentResolver(); observers.forEach(observer -> registerDynamicDataObserver(resolver, observer)); } private void registerDynamicDataObserver(ContentResolver resolver, DynamicDataObserver observer) { Log.d(TAG, "register observer: @" + Integer.toHexString(observer.hashCode()) + ", uri: " + observer.getUri()); resolver.registerContentObserver(observer.getUri(), false, observer); mRegisteredObservers.add(observer); } private void unregisterDynamicDataObservers(List<DynamicDataObserver> observers) { if (observers == null || observers.isEmpty()) { return; } final ContentResolver resolver = getContentResolver(); observers.forEach(observer -> { Log.d(TAG, "unregister observer: @" + Integer.toHexString(observer.hashCode()) + ", uri: " + observer.getUri()); mRegisteredObservers.remove(observer); resolver.unregisterContentObserver(observer); }); } } Loading
src/com/android/settings/SettingsActivity.java +1 −7 Original line number Diff line number Diff line Loading @@ -583,12 +583,7 @@ public class SettingsActivity extends SettingsBaseActivity // Generally the items that are will be changing from these updates will // not be in the top list of tiles, so run it in the background and the // SettingsBaseActivity will pick up on the updates automatically. AsyncTask.execute(new Runnable() { @Override public void run() { doUpdateTilesList(); } }); AsyncTask.execute(() -> doUpdateTilesList()); } private void doUpdateTilesList() { Loading Loading @@ -648,7 +643,6 @@ public class SettingsActivity extends SettingsBaseActivity || somethingChanged; if (UserHandle.MU_ENABLED && !isAdmin) { // When on restricted users, disable all extra categories (but only the settings ones). final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories(); synchronized (categories) { Loading
src/com/android/settings/dashboard/CategoryManager.java +16 −5 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import androidx.annotation.VisibleForTesting; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.ProviderTile; import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.TileUtils; Loading Loading @@ -189,16 +190,25 @@ public class CategoryManager { /** * Filter out duplicate tiles from category. Duplicate tiles are the ones pointing to the * same intent. * same intent for ActivityTile, and also the ones having the same description for ProviderTile. */ @VisibleForTesting synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) { for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) { final DashboardCategory category = categoryEntry.getValue(); final int count = category.getTilesCount(); final Set<String> descriptions = new ArraySet<>(); final Set<ComponentName> components = new ArraySet<>(); for (int i = count - 1; i >= 0; i--) { final Tile tile = category.getTile(i); if (tile instanceof ProviderTile) { final String desc = tile.getDescription(); if (descriptions.contains(desc)) { category.removeTile(i); } else { descriptions.add(desc); } } else { final ComponentName tileComponent = tile.getIntent().getComponent(); if (components.contains(tileComponent)) { category.removeTile(i); Loading @@ -209,3 +219,4 @@ public class CategoryManager { } } } }
src/com/android/settings/dashboard/DashboardFeatureProvider.java +7 −4 Original line number Diff line number Diff line Loading @@ -44,19 +44,22 @@ public interface DashboardFeatureProvider { String getDashboardKeyForTile(Tile tile); /** * Binds preference to data provided by tile. * Binds preference to data provided by tile and gets dynamic data observers. * * @param activity If tile contains intent to launch, it will be launched from this activity * @param forceRoundedIcon Whether or not injected tiles from other packages should be forced to rounded icon. * @param forceRoundedIcon Whether or not injected tiles from other packages should be forced to * rounded icon. * @param sourceMetricsCategory The context (source) from which an action is performed * @param pref The preference to bind data * @param tile The binding data * @param key They key for preference. If null, we will generate one from tile data * @param baseOrder The order offset value. When binding, pref's order is determined by * both this value and tile's own priority. * @return The list of dynamic data observers */ void bindPreferenceToTile(FragmentActivity activity, boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder); List<DynamicDataObserver> bindPreferenceToTileAndGetObservers(FragmentActivity activity, boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder); /** * Opens a tile to its destination intent. Loading
src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +153 −30 Original line number Diff line number Diff line Loading @@ -18,9 +18,18 @@ package com.android.settings.dashboard; import static android.content.Intent.EXTRA_USER; import static com.android.settingslib.drawer.SwitchesProvider.EXTRA_SWITCH_CHECKED_STATE; import static com.android.settingslib.drawer.SwitchesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR; import static com.android.settingslib.drawer.SwitchesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE; import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY; import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE; import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_PROVIDER_ICON; import static com.android.settingslib.drawer.SwitchesProvider.METHOD_IS_CHECKED; import static com.android.settingslib.drawer.SwitchesProvider.METHOD_ON_CHECKED_CHANGED; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE; import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI; Loading @@ -40,22 +49,26 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.widget.Toast; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.dashboard.profileselector.ProfileSelectDialog; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.drawer.ActivityTile; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.TileUtils; import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.widget.AdaptiveIcon; import java.util.ArrayList; import java.util.List; import java.util.Map; Loading Loading @@ -106,20 +119,33 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { } @Override public void bindPreferenceToTile(FragmentActivity activity, boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder) { public List<DynamicDataObserver> bindPreferenceToTileAndGetObservers(FragmentActivity activity, boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder) { if (pref == null) { return; return null; } if (!TextUtils.isEmpty(key)) { pref.setKey(key); } else { pref.setKey(getDashboardKeyForTile(tile)); } bindTitle(pref, tile); bindSummary(pref, tile); final List<DynamicDataObserver> outObservers = new ArrayList<>(); DynamicDataObserver observer = bindTitleAndGetObserver(pref, tile); if (observer != null) { outObservers.add(observer); } observer = bindSummaryAndGetObserver(pref, tile); if (observer != null) { outObservers.add(observer); } observer = bindSwitchAndGetObserver(pref, tile); if (observer != null) { outObservers.add(observer); } bindIcon(pref, tile, forceRoundedIcon); if (tile instanceof ActivityTile) { final Bundle metadata = tile.getMetaData(); String clsName = null; String action = null; Loading @@ -141,6 +167,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { return true; }); } } if (tile.hasOrder()) { final String skipOffsetPackageName = activity.getPackageName(); Loading @@ -153,6 +180,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { pref.setOrder(order + baseOrder); } } return outObservers.isEmpty() ? null : outObservers; } @Override Loading @@ -170,11 +198,35 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { launchIntentOrSelectProfile(activity, tile, intent, SettingsEnums.DASHBOARD_SUMMARY); } private void bindTitle(Preference preference, Tile tile) { private DynamicDataObserver createDynamicDataObserver(String method, Uri uri, Preference pref) { return new DynamicDataObserver() { @Override public Uri getUri() { return uri; } @Override public void onDataChanged() { switch (method) { case METHOD_GET_DYNAMIC_TITLE: refreshTitle(uri, pref); break; case METHOD_GET_DYNAMIC_SUMMARY: refreshSummary(uri, pref); break; case METHOD_IS_CHECKED: refreshSwitch(uri, pref); break; } } }; } private DynamicDataObserver bindTitleAndGetObserver(Preference preference, Tile tile) { final CharSequence title = tile.getTitle(mContext.getApplicationContext()); if (title != null) { preference.setTitle(title); return; return null; } if (tile.getMetaData() != null && tile.getMetaData().containsKey( META_DATA_PREFERENCE_TITLE_URI)) { Loading @@ -182,9 +234,12 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { // to avoid preference height change. preference.setTitle(R.string.summary_placeholder); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_TITLE_URI); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_TITLE_URI, METHOD_GET_DYNAMIC_TITLE); refreshTitle(uri, preference); return createDynamicDataObserver(METHOD_GET_DYNAMIC_TITLE, uri, preference); } return null; } private void refreshTitle(Uri uri, Preference preference) { Loading @@ -196,7 +251,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { }); } private void bindSummary(Preference preference, Tile tile) { private DynamicDataObserver bindSummaryAndGetObserver(Preference preference, Tile tile) { final CharSequence summary = tile.getSummary(mContext); if (summary != null) { preference.setSummary(summary); Loading @@ -206,11 +261,14 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { // to avoid preference height change. preference.setSummary(R.string.summary_placeholder); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SUMMARY_URI); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SUMMARY_URI, METHOD_GET_DYNAMIC_SUMMARY); refreshSummary(uri, preference); return createDynamicDataObserver(METHOD_GET_DYNAMIC_SUMMARY, uri, preference); } else { preference.setSummary(R.string.summary_placeholder); } return null; } private void refreshSummary(Uri uri, Preference preference) { Loading @@ -222,6 +280,70 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { }); } private DynamicDataObserver bindSwitchAndGetObserver(Preference preference, Tile tile) { if (!tile.hasSwitch()) { return null; } final Uri onCheckedChangedUri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SWITCH_URI, METHOD_ON_CHECKED_CHANGED); preference.setOnPreferenceChangeListener((pref, newValue) -> { onCheckedChanged(onCheckedChangedUri, pref, (boolean) newValue); return true; }); final Uri isCheckedUri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SWITCH_URI, METHOD_IS_CHECKED); setSwitchEnabled(preference, false); refreshSwitch(isCheckedUri, preference); return createDynamicDataObserver(METHOD_IS_CHECKED, isCheckedUri, preference); } private void onCheckedChanged(Uri uri, Preference pref, boolean checked) { setSwitchEnabled(pref, false); ThreadUtils.postOnBackgroundThread(() -> { final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final Bundle result = TileUtils.putBooleanToUri(mContext, uri, providerMap, EXTRA_SWITCH_CHECKED_STATE, checked); ThreadUtils.postOnMainThread(() -> { setSwitchEnabled(pref, true); final boolean error = result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR); if (!error) { return; } setSwitchChecked(pref, !checked); final String errorMsg = result.getString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE); if (!TextUtils.isEmpty(errorMsg)) { Toast.makeText(mContext, errorMsg, Toast.LENGTH_SHORT).show(); } }); }); } private void refreshSwitch(Uri uri, Preference preference) { ThreadUtils.postOnBackgroundThread(() -> { final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final boolean checked = TileUtils.getBooleanFromUri(mContext, uri, providerMap, EXTRA_SWITCH_CHECKED_STATE); ThreadUtils.postOnMainThread(() -> { setSwitchChecked(preference, checked); setSwitchEnabled(preference, true); }); }); } private void setSwitchChecked(Preference pref, boolean checked) { if (pref instanceof SwitchPreference) { ((SwitchPreference) pref).setChecked(checked); } } private void setSwitchEnabled(Preference pref, boolean enabled) { pref.setEnabled(enabled); } @VisibleForTesting void bindIcon(Preference preference, Tile tile, boolean forceRoundedIcon) { // Use preference context instead here when get icon from Tile, as we are using the context Loading @@ -246,7 +368,8 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { packageName = intent.getComponent().getPackageName(); } final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_ICON_URI); final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_ICON_URI, METHOD_GET_PROVIDER_ICON); final Pair<String, Integer> iconInfo = TileUtils.getIconFromUri( mContext, packageName, uri, providerMap); if (iconInfo == null) { Loading
src/com/android/settings/dashboard/DashboardFragment.java +75 −21 Original line number Diff line number Diff line Loading @@ -17,11 +17,11 @@ package com.android.settings.dashboard; import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.ContentResolver; import android.content.Context; import android.os.Bundle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import androidx.annotation.CallSuper; Loading @@ -30,6 +30,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; Loading @@ -41,6 +42,7 @@ import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.ProviderTile; import com.android.settingslib.drawer.Tile; import com.android.settingslib.search.Indexable; Loading @@ -49,7 +51,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Objects; /** * Base fragment for dashboard style UI containing a list of static and dynamic setting items. Loading @@ -60,9 +62,11 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment BasePreferenceController.UiBlockListener { private static final String TAG = "DashboardFragment"; @VisibleForTesting final ArrayMap<String, List<DynamicDataObserver>> mDashboardTilePrefKeys = new ArrayMap<>(); private final Map<Class, List<AbstractPreferenceController>> mPreferenceControllers = new ArrayMap<>(); private final Set<String> mDashboardTilePrefKeys = new ArraySet<>(); private final List<DynamicDataObserver> mRegisteredObservers = new ArrayList<>(); private final List<AbstractPreferenceController> mControllers = new ArrayList<>(); private DashboardFeatureProvider mDashboardFeatureProvider; Loading Loading @@ -171,6 +175,15 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment mListeningToCategoryChange = true; ((SettingsBaseActivity) activity).addCategoryListener(this); } final ContentResolver resolver = getContentResolver(); mDashboardTilePrefKeys.values().stream() .filter(Objects::nonNull) .flatMap(List::stream) .forEach(observer -> { if (!mRegisteredObservers.contains(observer)) { registerDynamicDataObserver(resolver, observer); } }); } @Override Loading Loading @@ -200,6 +213,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment @Override public void onStop() { super.onStop(); unregisterDynamicDataObservers(new ArrayList<>(mRegisteredObservers)); if (mListeningToCategoryChange) { final Activity activity = getActivity(); if (activity instanceof SettingsBaseActivity) { Loading Loading @@ -325,7 +339,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment * Refresh all preference items, including both static prefs from xml, and dynamic items from * DashboardCategory. */ private void refreshAllPreferences(final String TAG) { private void refreshAllPreferences(final String tag) { final PreferenceScreen screen = getPreferenceScreen(); // First remove old preferences. if (screen != null) { Loading @@ -336,11 +350,11 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment // Add resource based tiles. displayResourceTiles(); refreshDashboardTiles(TAG); refreshDashboardTiles(tag); final Activity activity = getActivity(); if (activity != null) { Log.d(TAG, "All preferences added, reporting fully drawn"); Log.d(tag, "All preferences added, reporting fully drawn"); activity.reportFullyDrawn(); } Loading Loading @@ -371,59 +385,62 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment /** * Refresh preference items backed by DashboardCategory. */ @VisibleForTesting void refreshDashboardTiles(final String TAG) { private void refreshDashboardTiles(final String tag) { final PreferenceScreen screen = getPreferenceScreen(); final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(getCategoryKey()); if (category == null) { Log.d(TAG, "NO dashboard tiles for " + TAG); Log.d(tag, "NO dashboard tiles for " + tag); return; } final List<Tile> tiles = category.getTiles(); if (tiles == null) { Log.d(TAG, "tile list is empty, skipping category " + category.key); Log.d(tag, "tile list is empty, skipping category " + category.key); return; } // Create a list to track which tiles are to be removed. final List<String> remove = new ArrayList<>(mDashboardTilePrefKeys); final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys); // Install dashboard tiles. final boolean forceRoundedIcons = shouldForceRoundedIcon(); for (Tile tile : tiles) { final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile); if (TextUtils.isEmpty(key)) { Log.d(TAG, "tile does not contain a key, skipping " + tile); Log.d(tag, "tile does not contain a key, skipping " + tile); continue; } if (!displayTile(tile)) { continue; } if (mDashboardTilePrefKeys.contains(key)) { if (mDashboardTilePrefKeys.containsKey(key)) { // Have the key already, will rebind. final Preference preference = screen.findPreference(key); mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons, getMetricsCategory(), preference, tile, key, mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(), forceRoundedIcons, getMetricsCategory(), preference, tile, key, mPlaceholderPreferenceController.getOrder()); } else { // Don't have this key, add it. final Preference pref = new Preference(getPrefContext()); mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons, getMetricsCategory(), pref, tile, key, final Preference pref = createPreference(tile); final List<DynamicDataObserver> observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(), forceRoundedIcons, getMetricsCategory(), pref, tile, key, mPlaceholderPreferenceController.getOrder()); screen.addPreference(pref); mDashboardTilePrefKeys.add(key); registerDynamicDataObservers(observers); mDashboardTilePrefKeys.put(key, observers); } remove.remove(key); } // Finally remove tiles that are gone. for (String key : remove) { for (Map.Entry<String, List<DynamicDataObserver>> entry : remove.entrySet()) { final String key = entry.getKey(); mDashboardTilePrefKeys.remove(key); final Preference preference = screen.findPreference(key); if (preference != null) { screen.removePreference(preference); } unregisterDynamicDataObservers(entry.getValue()); } } Loading @@ -431,4 +448,41 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment public void onBlockerWorkFinished(BasePreferenceController controller) { mBlockerController.countDown(controller.getPreferenceKey()); } @VisibleForTesting Preference createPreference(Tile tile) { return tile instanceof ProviderTile ? new SwitchPreference(getPrefContext()) : new Preference(getPrefContext()); } @VisibleForTesting void registerDynamicDataObservers(List<DynamicDataObserver> observers) { if (observers == null || observers.isEmpty()) { return; } final ContentResolver resolver = getContentResolver(); observers.forEach(observer -> registerDynamicDataObserver(resolver, observer)); } private void registerDynamicDataObserver(ContentResolver resolver, DynamicDataObserver observer) { Log.d(TAG, "register observer: @" + Integer.toHexString(observer.hashCode()) + ", uri: " + observer.getUri()); resolver.registerContentObserver(observer.getUri(), false, observer); mRegisteredObservers.add(observer); } private void unregisterDynamicDataObservers(List<DynamicDataObserver> observers) { if (observers == null || observers.isEmpty()) { return; } final ContentResolver resolver = getContentResolver(); observers.forEach(observer -> { Log.d(TAG, "unregister observer: @" + Integer.toHexString(observer.hashCode()) + ", uri: " + observer.getUri()); mRegisteredObservers.remove(observer); resolver.unregisterContentObserver(observer); }); } }