diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java index 4dc8f1af207..578493a47d2 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java @@ -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) { diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index 6076a25ddc1..20836145cf7 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -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() - ? new PrimarySwitchPreference(getPrefContext()) - : new Preference(getPrefContext()); + if (tile.hasSwitch()) { + return (tile instanceof ActivityTile || tile.hasPendingIntent()) + ? new PrimarySwitchPreference(getPrefContext()) + : new SwitchPreference(getPrefContext()); + } else { + return new Preference(getPrefContext()); + } } @VisibleForTesting diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java index ef6ad832477..58a51cb32dc 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java @@ -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. - 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); + 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); + } + } + } } diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java index 4ba6eae9b48..f7a940f4a55 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java @@ -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); diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java index d7ac433b497..22bf6b3f5a3 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java index 4e81ceeb437..9a139615b84 100644 --- a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java @@ -24,7 +24,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Dialog; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.UserInfo; import android.os.UserHandle; @@ -118,6 +120,28 @@ public class ProfileSelectDialogTest { verify(mUserManager, times(1)).getUserInfo(CLONE_USER.getIdentifier()); } + @Test + public void updatePendingIntentsIfNeeded_removesUsersWithNoPendingIntentsAndCloneProfile() { + final UserInfo userInfo = new UserInfo(CLONE_USER.getIdentifier(), "clone_user", null, + UserInfo.FLAG_PROFILE, UserManager.USER_TYPE_PROFILE_CLONE); + when(mUserManager.getUserInfo(CLONE_USER.getIdentifier())).thenReturn(userInfo); + final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); + tile.userHandle.add(CLONE_USER); + tile.userHandle.add(NORMAL_USER); + tile.userHandle.add(new UserHandle(10)); + PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0); + tile.pendingIntentMap.put(CLONE_USER, pendingIntent); + tile.pendingIntentMap.put(NORMAL_USER, pendingIntent); + + ProfileSelectDialog.updatePendingIntentsIfNeeded(mContext, tile); + + assertThat(tile.userHandle).hasSize(1); + assertThat(tile.userHandle).containsExactly(NORMAL_USER); + assertThat(tile.pendingIntentMap).hasSize(1); + assertThat(tile.pendingIntentMap).containsKey(NORMAL_USER); + verify(mUserManager, times(1)).getUserInfo(CLONE_USER.getIdentifier()); + } + @Test public void createDialog_showsCorrectTitle() { mContext.setTheme(R.style.Theme_AppCompat);