Loading src/com/android/settings/notification/modes/CircularIconsPreference.java +5 −2 Original line number Diff line number Diff line Loading @@ -49,7 +49,9 @@ public class CircularIconsPreference extends RestrictedPreference { private static final float DISABLED_ITEM_ALPHA = 0.3f; record LoadedIcons(ImmutableList<Drawable> icons, int extraItems) { } record LoadedIcons(ImmutableList<Drawable> icons, int extraItems) { static final LoadedIcons EMPTY = new LoadedIcons(ImmutableList.of(), 0); } private Executor mUiExecutor; Loading Loading @@ -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); Loading @@ -137,7 +140,7 @@ public class CircularIconsPreference extends RestrictedPreference { @Override public void onGlobalLayout() { container.getViewTreeObserver().removeOnGlobalLayoutListener(this); startLoadingIcons(container, mIconSet); notifyChanged(); } } ); Loading src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java +20 −6 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -65,24 +68,26 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr private ZenMode mZenMode; private CircularIconsPreference mPreference; private final Fragment mHost; private final Function<ApplicationInfo, Drawable> 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<ApplicationInfo, Drawable> appIconRetriever) { super(context, key, backend); mSummaryHelper = new ZenModeSummaryHelper(mContext, helperBackend); mHelperBackend = helperBackend; mApplicationsState = applicationsState; mUserManager = context.getSystemService(UserManager.class); mHost = host; mAppIconRetriever = appIconRetriever; } @Override Loading @@ -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(); } Loading @@ -133,12 +143,16 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr } private void displayAppsBypassingDnd(List<AppEntry> allApps) { ImmutableList<AppEntry> apps = getAppsBypassingDndSortedByName(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<AppEntry> 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); } Loading tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java +87 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(), Loading Loading @@ -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<ApplicationsState.AppEntry> 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<ApplicationsState.AppEntry> 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(); Loading Loading
src/com/android/settings/notification/modes/CircularIconsPreference.java +5 −2 Original line number Diff line number Diff line Loading @@ -49,7 +49,9 @@ public class CircularIconsPreference extends RestrictedPreference { private static final float DISABLED_ITEM_ALPHA = 0.3f; record LoadedIcons(ImmutableList<Drawable> icons, int extraItems) { } record LoadedIcons(ImmutableList<Drawable> icons, int extraItems) { static final LoadedIcons EMPTY = new LoadedIcons(ImmutableList.of(), 0); } private Executor mUiExecutor; Loading Loading @@ -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); Loading @@ -137,7 +140,7 @@ public class CircularIconsPreference extends RestrictedPreference { @Override public void onGlobalLayout() { container.getViewTreeObserver().removeOnGlobalLayoutListener(this); startLoadingIcons(container, mIconSet); notifyChanged(); } } ); Loading
src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java +20 −6 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -65,24 +68,26 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr private ZenMode mZenMode; private CircularIconsPreference mPreference; private final Fragment mHost; private final Function<ApplicationInfo, Drawable> 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<ApplicationInfo, Drawable> appIconRetriever) { super(context, key, backend); mSummaryHelper = new ZenModeSummaryHelper(mContext, helperBackend); mHelperBackend = helperBackend; mApplicationsState = applicationsState; mUserManager = context.getSystemService(UserManager.class); mHost = host; mAppIconRetriever = appIconRetriever; } @Override Loading @@ -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(); } Loading @@ -133,12 +143,16 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr } private void displayAppsBypassingDnd(List<AppEntry> allApps) { ImmutableList<AppEntry> apps = getAppsBypassingDndSortedByName(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<AppEntry> 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); } Loading
tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java +87 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(), Loading Loading @@ -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<ApplicationsState.AppEntry> 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<ApplicationsState.AppEntry> 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(); Loading