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

Commit ddb65e56 authored by Peter Zhang's avatar Peter Zhang
Browse files

Expand SettingsLib ProviderTile to support non-switch type of Preferences

- Added new metadata allowing to set PendingIntent onto a Tile, which will be executed on click;
- Update the rendering logic to render with SwitchPreference only when Tile.hasSwitch() == true.

Test: robotest, manual
Bug: 281517110
Change-Id: I1253029be1e172792679f80be24bd58e368b9e73
parent 860002b4
Loading
Loading
Loading
Loading
+38 −1
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWIT
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;

import android.app.PendingIntent;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
@@ -75,6 +76,8 @@ import com.android.settingslib.drawer.TileUtils;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.AdaptiveIcon;

import com.google.common.collect.Iterables;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -152,7 +155,14 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
        }
        bindIcon(pref, tile, forceRoundedIcon);

        if (tile instanceof ActivityTile) {
        if (tile.hasPendingIntent()) {
            // Pending intent cannot be launched within the settings app panel, and will thus always
            // be executed directly.
            pref.setOnPreferenceClickListener(preference -> {
                launchPendingIntentOrSelectProfile(activity, tile, fragment.getMetricsCategory());
                return true;
            });
        } else if (tile instanceof ActivityTile) {
            final int sourceMetricsCategory = fragment.getMetricsCategory();
            final Bundle metadata = tile.getMetaData();
            String clsName = null;
@@ -441,6 +451,33 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
        preference.setIcon(iconDrawable);
    }

    private void launchPendingIntentOrSelectProfile(FragmentActivity activity, Tile tile,
            int sourceMetricCategory) {
        ProfileSelectDialog.updatePendingIntentsIfNeeded(mContext, tile);

        if (tile.pendingIntentMap.isEmpty()) {
            Log.w(TAG, "Cannot resolve pendingIntent, skipping. " + tile.getIntent());
            return;
        }

        mMetricsFeatureProvider.logSettingsTileClick(tile.getKey(mContext), sourceMetricCategory);

        // Launch the pending intent directly if there's only one available.
        if (tile.pendingIntentMap.size() == 1) {
            PendingIntent pendingIntent = Iterables.getOnlyElement(tile.pendingIntentMap.values());
            try {
                pendingIntent.send();
            } catch (PendingIntent.CanceledException e) {
                Log.w(TAG, "Failed executing pendingIntent. " + pendingIntent.getIntent(), e);
            }
            return;
        }

        ProfileSelectDialog.show(activity.getSupportFragmentManager(), tile,
                sourceMetricCategory, /* onShowListener= */ null,
                /* onDismissListener= */ null, /* onCancelListener= */ null);
    }

    private void launchIntentOrSelectProfile(FragmentActivity activity, Tile tile, Intent intent,
            int sourceMetricCategory, TopLevelHighlightMixin highlightMixin,
            boolean isDuplicateClick) {
+8 −6
Original line number Diff line number Diff line
@@ -46,8 +46,8 @@ import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.drawer.ActivityTile;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.ProviderTile;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.search.Indexable;

@@ -569,11 +569,13 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
    }

    protected Preference createPreference(Tile tile) {
        return tile instanceof ProviderTile
                ? new SwitchPreference(getPrefContext())
                : tile.hasSwitch()
        if (tile.hasSwitch()) {
            return (tile instanceof ActivityTile || tile.hasPendingIntent())
                    ? new PrimarySwitchPreference(getPrefContext())
                        : new Preference(getPrefContext());
                    : new SwitchPreference(getPrefContext());
        } else {
            return new Preference(getPrefContext());
        }
    }

    @VisibleForTesting
+52 −7
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.settings.dashboard.profileselector;

import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
@@ -127,13 +128,25 @@ public class ProfileSelectDialog extends DialogFragment implements UserAdapter.O
    @Override
    public void onClick(int position) {
        final UserHandle user = mSelectedTile.userHandle.get(position);
        // Show menu on top level items.
        if (!mSelectedTile.hasPendingIntent()) {
            final Intent intent = new Intent(mSelectedTile.getIntent());
            FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()
                    .logStartedIntentWithProfile(intent, mSourceMetricCategory,
                            position == 1 /* isWorkProfile */);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
            getActivity().startActivityAsUser(intent, user);
        } else {
            PendingIntent pendingIntent = mSelectedTile.pendingIntentMap.get(user);
            FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()
                    .logSettingsTileClickWithProfile(mSelectedTile.getKey(getContext()),
                            mSourceMetricCategory,
                            position == 1 /* isWorkProfile */);
            try {
                pendingIntent.send();
            } catch (PendingIntent.CanceledException e) {
                Log.w(TAG, "Failed executing pendingIntent. " + pendingIntent.getIntent(), e);
            }
        }
        dismiss();
    }

@@ -178,4 +191,36 @@ public class ProfileSelectDialog extends DialogFragment implements UserAdapter.O
            }
        }
    }

    /**
     * Checks the userHandle and pendingIntentMap in the provided tile, and remove the invalid
     * entries if any.
     */
    public static void updatePendingIntentsIfNeeded(Context context, Tile tile) {
        if (tile.userHandle == null || tile.userHandle.size() <= 1
                || tile.pendingIntentMap.size() <= 1) {
            return;
        }
        for (UserHandle userHandle : List.copyOf(tile.userHandle)) {
            if (!tile.pendingIntentMap.containsKey(userHandle)) {
                if (DEBUG) {
                    Log.d(TAG, "Delete the user without pending intent: "
                            + userHandle.getIdentifier());
                }
                tile.userHandle.remove(userHandle);
            }
        }

        final UserManager userManager = UserManager.get(context);
        for (UserHandle userHandle : List.copyOf(tile.pendingIntentMap.keySet())) {
            UserInfo userInfo = userManager.getUserInfo(userHandle.getIdentifier());
            if (userInfo == null || userInfo.isCloneProfile()) {
                if (DEBUG) {
                    Log.d(TAG, "Delete the user: " + userHandle.getIdentifier());
                }
                tile.userHandle.remove(userHandle);
                tile.pendingIntentMap.remove(userHandle);
            }
        }
    }
}
+72 −0
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -57,6 +58,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.util.Pair;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
@@ -199,6 +201,27 @@ public class DashboardFeatureProviderImplTest {
        assertThat(observers.get(0).getUri().toString()).isEqualTo(SWITCH_URI);
    }

    @Test
    public void bindPreference_providerTileWithPendingIntent_shouldBindIntent() {
        final Preference preference = new SwitchPreference(RuntimeEnvironment.application);
        Bundle metaData = new Bundle();
        metaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
        metaData.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.about_settings_summary);
        metaData.putInt(META_DATA_KEY_ORDER, 10);
        metaData.putString(META_DATA_PREFERENCE_KEYHINT, KEY);
        final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE, metaData);
        PendingIntent pendingIntent =
                PendingIntent.getActivity(RuntimeEnvironment.application, 0, new Intent("test"), 0);
        tile.pendingIntentMap.put(UserHandle.CURRENT, pendingIntent);

        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
                preference, tile, "123", Preference.DEFAULT_ORDER);

        assertThat(preference.getFragment()).isNull();
        assertThat(preference.getOnPreferenceClickListener()).isNotNull();
        assertThat(preference.getOrder()).isEqualTo(tile.getOrder());
    }

    @Test
    public void bindPreference_noFragmentMetadata_shouldBindIntent() {
        final Preference preference = new Preference(RuntimeEnvironment.application);
@@ -630,6 +653,55 @@ public class DashboardFeatureProviderImplTest {
        assertThat(launchIntent).isNull();
    }

    @Test
    public void clickPreference_providerTileWithPendingIntent_singleUser_executesPendingIntent() {
        final Preference preference = new SwitchPreference(RuntimeEnvironment.application);
        Bundle metaData = new Bundle();
        metaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
        metaData.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.about_settings_summary);
        metaData.putInt(META_DATA_KEY_ORDER, 10);
        metaData.putString(META_DATA_PREFERENCE_KEYHINT, KEY);
        final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE, metaData);
        PendingIntent pendingIntent =
                PendingIntent.getActivity(RuntimeEnvironment.application, 0, new Intent("test"), 0);
        tile.pendingIntentMap.put(UserHandle.CURRENT, pendingIntent);

        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
                preference, tile, "123", Preference.DEFAULT_ORDER);
        preference.performClick();

        Intent nextStartedActivity =
                Shadows.shadowOf(RuntimeEnvironment.application).peekNextStartedActivity();
        assertThat(nextStartedActivity).isNotNull();
        assertThat(nextStartedActivity.getAction()).isEqualTo("test");
    }

    @Test
    public void clickPreference_providerTileWithPendingIntent_multiUser_showsProfileDialog() {
        final Preference preference = new SwitchPreference(RuntimeEnvironment.application);
        Bundle metaData = new Bundle();
        metaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
        metaData.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.about_settings_summary);
        metaData.putInt(META_DATA_KEY_ORDER, 10);
        metaData.putString(META_DATA_PREFERENCE_KEYHINT, KEY);
        final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE, metaData);
        PendingIntent pendingIntent =
                PendingIntent.getActivity(RuntimeEnvironment.application, 0, new Intent("test"), 0);
        tile.pendingIntentMap.put(UserHandle.CURRENT, pendingIntent);
        tile.pendingIntentMap.put(new UserHandle(10), pendingIntent);

        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
                preference, tile, "123", Preference.DEFAULT_ORDER);
        preference.performClick();

        Fragment dialogFragment =
                mActivity.getSupportFragmentManager().findFragmentByTag("select_profile");
        assertThat(dialogFragment).isNotNull();
        Intent nextStartedActivity =
                Shadows.shadowOf(RuntimeEnvironment.application).peekNextStartedActivity();
        assertThat(nextStartedActivity).isNull();
    }

    @Test
    public void openTileIntent_profileSelectionDialog_shouldShow() {
        ShadowUserManager.getShadow().addUser(10, "Someone", 0);
+32 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.PendingIntent;
import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
@@ -38,6 +39,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.preference.PreferenceManager.OnActivityResultListener;

import androidx.preference.Preference;
@@ -358,6 +360,36 @@ public class DashboardFragmentTest {
        assertThat(pref).isInstanceOf(PrimarySwitchPreference.class);
    }

    @Test
    public void createPreference_isProviderTileWithPendingIntent_returnPreference() {
        final ProviderInfo providerInfo = new ProviderInfo();
        providerInfo.packageName = "pkg";
        providerInfo.name = "provider";
        providerInfo.authority = "authority";
        final Bundle metaData = new Bundle();
        metaData.putString(META_DATA_PREFERENCE_KEYHINT, "injected_tile_key2");
        ProviderTile providerTile = new ProviderTile(providerInfo, mDashboardCategory.key,
                metaData);
        providerTile.pendingIntentMap.put(
                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));

        final Preference pref = mTestFragment.createPreference(providerTile);

        assertThat(pref).isInstanceOf(Preference.class);
        assertThat(pref).isNotInstanceOf(PrimarySwitchPreference.class);
        assertThat(pref).isNotInstanceOf(SwitchPreference.class);
    }

    @Test
    public void createPreference_isProviderTileWithPendingIntentAndSwitch_returnPrimarySwitch() {
        mProviderTile.pendingIntentMap.put(
                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));

        final Preference pref = mTestFragment.createPreference(mProviderTile);

        assertThat(pref).isInstanceOf(PrimarySwitchPreference.class);
    }

    @Test
    public void onActivityResult_test() {
        final int requestCode = 10;
Loading