diff --git a/src/com/android/settings/notification/modes/CircularIconsPreference.java b/src/com/android/settings/notification/modes/CircularIconsPreference.java index 0766ccd5623..ccf7f529e46 100644 --- a/src/com/android/settings/notification/modes/CircularIconsPreference.java +++ b/src/com/android/settings/notification/modes/CircularIconsPreference.java @@ -49,7 +49,9 @@ public class CircularIconsPreference extends RestrictedPreference { private static final float DISABLED_ITEM_ALPHA = 0.3f; - record LoadedIcons(ImmutableList icons, int extraItems) { } + record LoadedIcons(ImmutableList icons, int extraItems) { + static final LoadedIcons EMPTY = new LoadedIcons(ImmutableList.of(), 0); + } private Executor mUiExecutor; @@ -126,6 +128,7 @@ public class CircularIconsPreference extends RestrictedPreference { // We know what icons we want, but haven't yet loaded them. if (mIconSet.size() == 0) { container.setVisibility(View.GONE); + mLoadedIcons = LoadedIcons.EMPTY; return; } container.setVisibility(View.VISIBLE); @@ -137,7 +140,7 @@ public class CircularIconsPreference extends RestrictedPreference { @Override public void onGlobalLayout() { container.getViewTreeObserver().removeOnGlobalLayoutListener(this); - startLoadingIcons(container, mIconSet); + notifyChanged(); } } ); diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java index 84d3c09a628..45287abdc61 100644 --- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java @@ -22,6 +22,8 @@ import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID; import android.app.Application; import android.app.settings.SettingsEnums; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -49,6 +51,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; +import java.util.function.Function; /** * Preference with a link and summary about what apps can break through the mode @@ -65,24 +68,26 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr private ZenMode mZenMode; private CircularIconsPreference mPreference; private final Fragment mHost; + private final Function mAppIconRetriever; ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host, ZenModesBackend backend, ZenHelperBackend helperBackend) { this(context, key, host, ApplicationsState.getInstance((Application) context.getApplicationContext()), - backend, helperBackend); + backend, helperBackend, appInfo -> Utils.getBadgedIcon(context, appInfo)); } @VisibleForTesting ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host, ApplicationsState applicationsState, ZenModesBackend backend, - ZenHelperBackend helperBackend) { + ZenHelperBackend helperBackend, Function appIconRetriever) { super(context, key, backend); mSummaryHelper = new ZenModeSummaryHelper(mContext, helperBackend); mHelperBackend = helperBackend; mApplicationsState = applicationsState; mUserManager = context.getSystemService(UserManager.class); mHost = host; + mAppIconRetriever = appIconRetriever; } @Override @@ -105,13 +110,18 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) { mPreference.setSummary(R.string.zen_mode_apps_none_apps); mPreference.displayIcons(CircularIconSet.EMPTY); + if (mAppSession != null) { + mAppSession.deactivateSession(); + } } else { if (TextUtils.isEmpty(mPreference.getSummary())) { mPreference.setSummary(R.string.zen_mode_apps_calculating); } - if (mApplicationsState != null && mHost != null) { + if (mAppSession == null) { mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, mHost.getLifecycle()); + } else { + mAppSession.activateSession(); } triggerUpdateAppsBypassingDnd(); } @@ -133,12 +143,16 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr } private void displayAppsBypassingDnd(List allApps) { + if (mZenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) { + // Can get this callback when resuming, if we had CHANNEL_POLICY_PRIORITY and just + // switched to CHANNEL_POLICY_NONE. + return; + } + ImmutableList apps = getAppsBypassingDndSortedByName(allApps); - mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps)); - mPreference.displayIcons(new CircularIconSet<>(apps, - app -> Utils.getBadgedIcon(mContext, app.info)), + app -> mAppIconRetriever.apply(app.info)), APP_ENTRY_EQUIVALENCE); } diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java index d439111946a..29e9cf9db07 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java @@ -39,6 +39,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.UserInfo; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -103,11 +104,12 @@ public final class ZenModeAppsLinkPreferenceControllerTest { mContext = RuntimeEnvironment.application; CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService(); mPreference = new TestableCircularIconsPreference(mContext); - when(mApplicationsState.newSession(any(), any())).thenReturn(mSession); + mController = new ZenModeAppsLinkPreferenceController( mContext, "controller_key", mock(Fragment.class), mApplicationsState, - mZenModesBackend, mHelperBackend); + mZenModesBackend, mHelperBackend, + /* appIconRetriever= */ appInfo -> new ColorDrawable()); // Ensure the preference view is bound & measured (needed to add child ImageViews). View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(), @@ -296,6 +298,89 @@ public final class ZenModeAppsLinkPreferenceControllerTest { verify(mSession, times(2)).rebuild(any(), any(), eq(false)); } + @Test + public void updateState_noneToPriority_loadsBypassingAppsAndListensForChanges() { + ZenMode zenModeWithNone = new TestModeBuilder() + .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(false).build()) + .build(); + ZenMode zenModeWithPriority = new TestModeBuilder() + .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(true).build()) + .build(); + ArrayList appEntries = new ArrayList<>(); + appEntries.add(createAppEntry("test", mContext.getUserId())); + when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId(), false)) + .thenReturn(List.of("test")); + + mController.updateState(mPreference, zenModeWithNone); + + assertThat(mPreference.getLoadedIcons().icons()).hasSize(0); + verifyNoMoreInteractions(mApplicationsState); + verifyNoMoreInteractions(mSession); + + mController.updateState(mPreference, zenModeWithPriority); + + verify(mApplicationsState).newSession(any(), any()); + verify(mSession).rebuild(any(), any(), anyBoolean()); + mController.mAppSessionCallbacks.onRebuildComplete(appEntries); + assertThat(mPreference.getLoadedIcons().icons()).hasSize(1); + } + + @Test + public void updateState_priorityToNone_clearsBypassingAppsAndStopsListening() { + ZenMode zenModeWithNone = new TestModeBuilder() + .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(false).build()) + .build(); + ZenMode zenModeWithPriority = new TestModeBuilder() + .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(true).build()) + .build(); + ArrayList appEntries = new ArrayList<>(); + appEntries.add(createAppEntry("test", mContext.getUserId())); + when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId(), false)) + .thenReturn(List.of("test")); + + mController.updateState(mPreference, zenModeWithPriority); + + verify(mApplicationsState).newSession(any(), any()); + verify(mSession).rebuild(any(), any(), anyBoolean()); + mController.mAppSessionCallbacks.onRebuildComplete(appEntries); + assertThat(mPreference.getLoadedIcons().icons()).hasSize(1); + + mController.updateState(mPreference, zenModeWithNone); + + assertThat(mPreference.getLoadedIcons().icons()).hasSize(0); + verify(mSession).deactivateSession(); + verifyNoMoreInteractions(mSession); + verifyNoMoreInteractions(mApplicationsState); + + // An errant callback (triggered by onResume and received asynchronously after + // updateState()) is ignored. + mController.mAppSessionCallbacks.onRebuildComplete(appEntries); + + assertThat(mPreference.getLoadedIcons().icons()).hasSize(0); + } + + @Test + public void updateState_priorityToNoneToPriority_restartsListening() { + ZenMode zenModeWithNone = new TestModeBuilder() + .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(false).build()) + .build(); + ZenMode zenModeWithPriority = new TestModeBuilder() + .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(true).build()) + .build(); + + mController.updateState(mPreference, zenModeWithPriority); + verify(mApplicationsState).newSession(any(), any()); + verify(mSession).rebuild(any(), any(), anyBoolean()); + + mController.updateState(mPreference, zenModeWithNone); + verifyNoMoreInteractions(mApplicationsState); + verify(mSession).deactivateSession(); + + mController.updateState(mPreference, zenModeWithPriority); + verifyNoMoreInteractions(mApplicationsState); + verify(mSession).activateSession(); + } + @Test public void testNoCrashIfAppsReadyBeforeRuleAvailable() { mController.mAppSessionCallbacks.onLoadEntriesCompleted();