Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bac5987c authored by Jason Chiu's avatar Jason Chiu
Browse files

[Settings] Support pure switch of inline toggle of Settings Injection v2

Bug: 132808482
Test: robotest
Change-Id: Ib24614fb46fe990925edad721e3b7d5d032854fc
parent 9096329b
Loading
Loading
Loading
Loading
+1 −7
Original line number Diff line number Diff line
@@ -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() {
@@ -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) {
+16 −5
Original line number Diff line number Diff line
@@ -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;

@@ -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);
@@ -209,3 +219,4 @@ public class CategoryManager {
            }
        }
    }
}
+7 −4
Original line number Diff line number Diff line
@@ -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.
+153 −30
Original line number Diff line number Diff line
@@ -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;

@@ -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;

@@ -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;
@@ -141,6 +167,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
                    return true;
                });
            }
        }

        if (tile.hasOrder()) {
            final String skipOffsetPackageName = activity.getPackageName();
@@ -153,6 +180,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
                pref.setOrder(order + baseOrder);
            }
        }
        return outObservers.isEmpty() ? null : outObservers;
    }

    @Override
@@ -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)) {
@@ -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) {
@@ -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);
@@ -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) {
@@ -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
@@ -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) {
+75 −21
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;

@@ -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.
@@ -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;
@@ -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
@@ -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) {
@@ -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) {
@@ -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();
        }

@@ -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());
        }
    }

@@ -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