diff --git a/src/com/android/settings/notification/modes/ZenHelperBackend.java b/src/com/android/settings/notification/modes/ZenHelperBackend.java index 31c1ce40b19..5761d361e33 100644 --- a/src/com/android/settings/notification/modes/ZenHelperBackend.java +++ b/src/com/android/settings/notification/modes/ZenHelperBackend.java @@ -18,6 +18,7 @@ package com.android.settings.notification.modes; import android.annotation.Nullable; import android.app.INotificationManager; +import android.app.ZenBypassingApp; import android.content.ContentProvider; import android.content.Context; import android.content.pm.ParceledListSlice; @@ -30,6 +31,7 @@ import android.os.UserManager; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.service.notification.ConversationChannelWrapper; +import android.util.ArrayMap; import android.util.Log; import androidx.annotation.NonNull; @@ -41,8 +43,9 @@ import com.google.common.collect.ImmutableList; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Function; /** @@ -76,16 +79,20 @@ class ZenHelperBackend { } /** - * Returns all of a user's packages that have at least one channel that will bypass DND + * Returns a mapping between a user's packages that have at least one channel that will + * bypass DND, and a Boolean indicating whether all of the package's channels bypass. */ - List getPackagesBypassingDnd(int userId, - boolean includeConversationChannels) { + Map getPackagesBypassingDnd(int userId) { + Map bypassingAppsMap = new HashMap<>(); try { - return mInm.getPackagesBypassingDnd(userId, includeConversationChannels); + List bypassingApps = mInm.getPackagesBypassingDnd(userId).getList(); + for (ZenBypassingApp zba : bypassingApps) { + bypassingAppsMap.put(zba.getPkg(), zba.doAllChannelsBypass()); + } } catch (Exception e) { Log.w(TAG, "Error calling NoMan", e); - return new ArrayList<>(); } + return bypassingAppsMap; } /** Returns all conversation channels for profiles of the current user. */ diff --git a/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceController.java index 922ac5ecf5b..a01406ff836 100644 --- a/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceController.java @@ -22,7 +22,9 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; +import android.os.UserManager; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.text.BidiFormatter; @@ -35,7 +37,6 @@ import com.android.settings.R; import com.android.settings.applications.AppInfoBase; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.core.SubSettingLauncher; -import com.android.settings.notification.NotificationBackend; import com.android.settings.notification.app.AppChannelsBypassingDndSettings; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; @@ -44,7 +45,9 @@ import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.widget.AppPreference; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Adds a preference to the PreferenceScreen for each notification channel that can bypass DND. @@ -54,7 +57,8 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere public static final String KEY_NO_APPS = "all_none"; private static final String KEY = "zen_mode_bypassing_apps_list"; - @Nullable private final NotificationBackend mNotificationBackend; + @Nullable private final ZenHelperBackend mHelperBackend; + private final UserManager mUserManager; @Nullable @VisibleForTesting ApplicationsState mApplicationsState; @VisibleForTesting PreferenceCategory mPreferenceCategory; @@ -64,18 +68,18 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere @Nullable private Fragment mHostFragment; public ZenModeAllBypassingAppsPreferenceController(Context context, @Nullable Application app, - @Nullable Fragment host, @Nullable NotificationBackend notificationBackend) { - this(context, app == null ? null : ApplicationsState.getInstance(app), host, - notificationBackend); + @Nullable Fragment host, @Nullable ZenHelperBackend helperBackend) { + this(context, app == null ? null : ApplicationsState.getInstance(app), host, helperBackend); } private ZenModeAllBypassingAppsPreferenceController(Context context, @Nullable ApplicationsState appState, @Nullable Fragment host, - @Nullable NotificationBackend notificationBackend) { + @Nullable ZenHelperBackend helperBackend) { super(context); - mNotificationBackend = notificationBackend; mApplicationsState = appState; mHostFragment = host; + mHelperBackend = helperBackend; + mUserManager = context.getSystemService(UserManager.class); if (mApplicationsState != null && host != null) { mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, host.getLifecycle()); @@ -140,19 +144,25 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere } boolean doAnyAppsPassCriteria = false; + Map> packagesBypassingDndByUser = new HashMap<>(); + for (UserHandle userHandle : mUserManager.getUserProfiles()) { + packagesBypassingDndByUser.put(userHandle.getIdentifier(), + mHelperBackend.getPackagesBypassingDnd(userHandle.getIdentifier())); + } for (ApplicationsState.AppEntry app : apps) { String pkg = app.info.packageName; final String key = getKey(pkg, app.info.uid); - final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid); - final int appChannelsBypassingDnd = mNotificationBackend - .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size(); - if (appChannelsBypassingDnd > 0) { + boolean doesAppBypassDnd = false; + int userId = UserHandle.getUserId(app.info.uid); + Map packagesBypassingDnd = + packagesBypassingDndByUser.getOrDefault(userId, new HashMap<>()); + if (packagesBypassingDnd.containsKey(pkg)) { doAnyAppsPassCriteria = true; + doesAppBypassDnd = true; } - Preference pref = mPreferenceCategory.findPreference(key); if (pref == null) { - if (appChannelsBypassingDnd > 0) { + if (doesAppBypassDnd) { // does not exist but should pref = new AppPreference(mPrefContext); pref.setKey(key); @@ -172,14 +182,14 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere }); pref.setTitle(BidiFormatter.getInstance().unicodeWrap(app.label)); updateIcon(pref, app); - if (appChannels > appChannelsBypassingDnd) { - pref.setSummary(R.string.zen_mode_bypassing_apps_summary_some); - } else { + if (packagesBypassingDnd.get(pkg)) { pref.setSummary(R.string.zen_mode_bypassing_apps_summary_all); + } else { + pref.setSummary(R.string.zen_mode_bypassing_apps_summary_some); } mPreferenceCategory.addPreference(pref); } - } else if (appChannelsBypassingDnd == 0) { + } else if (!doesAppBypassDnd) { // exists but shouldn't anymore mPreferenceCategory.removePreference(pref); } diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java index c133f515f30..e0e36f6495f 100644 --- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java @@ -161,8 +161,7 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr Multimap packagesBypassingDnd = HashMultimap.create(); for (UserHandle userHandle : mUserManager.getUserProfiles()) { packagesBypassingDnd.putAll(userHandle.getIdentifier(), - mHelperBackend.getPackagesBypassingDnd(userHandle.getIdentifier(), - /* includeConversationChannels= */ false)); + mHelperBackend.getPackagesBypassingDnd(userHandle.getIdentifier()).keySet()); } return ImmutableList.copyOf( diff --git a/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java index 1f5438d08eb..21f34a2005d 100644 --- a/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java +++ b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java @@ -48,15 +48,17 @@ public class ZenModeSelectBypassingAppsFragment extends ZenModeFragmentBase impl } else { app = null; } - return buildPreferenceControllers(context, app, this, new NotificationBackend()); + return buildPreferenceControllers(context, app, this, new NotificationBackend(), + new ZenHelperBackend(context)); } private static List buildPreferenceControllers(Context context, @Nullable Application app, @Nullable Fragment host, - @Nullable NotificationBackend notificationBackend) { + @Nullable NotificationBackend notificationBackend, + @Nullable ZenHelperBackend zenHelperBackend) { final List controllers = new ArrayList<>(); controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host, - notificationBackend)); + zenHelperBackend)); controllers.add(new ZenModeAddBypassingAppsPreferenceController(context, app, host, notificationBackend)); return controllers; @@ -86,7 +88,7 @@ public class ZenModeSelectBypassingAppsFragment extends ZenModeFragmentBase impl @Override public List createPreferenceControllers( Context context) { - return buildPreferenceControllers(context, null, null, null); + return buildPreferenceControllers(context, null, null, null, null); } }; } diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceControllerTest.java index a7d52b16575..f9601d70f6c 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceControllerTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -28,10 +27,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Flags; -import android.app.NotificationChannel; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.ParceledListSlice; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; @@ -39,7 +36,6 @@ import androidx.fragment.app.Fragment; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; -import com.android.settings.notification.NotificationBackend; import com.android.settingslib.applications.ApplicationsState; import org.junit.Before; @@ -54,6 +50,7 @@ import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; import java.util.List; +import java.util.Map; @RunWith(RobolectricTestRunner.class) @EnableFlags(Flags.FLAG_MODES_UI) @@ -66,7 +63,7 @@ public class ZenModeAllBypassingAppsPreferenceControllerTest { private Context mContext; @Mock - private NotificationBackend mBackend; + private ZenHelperBackend mBackend; @Mock private PreferenceCategory mPreferenceCategory; @Mock @@ -102,18 +99,25 @@ public class ZenModeAllBypassingAppsPreferenceControllerTest { entry2.info.packageName = "test2"; entry2.info.uid = 0; + ApplicationsState.AppEntry entry3= mock(ApplicationsState.AppEntry.class); + entry3.info = new ApplicationInfo(); + entry3.info.packageName = "test3"; + entry3.info.uid = 0; + List appEntries = new ArrayList<>(); appEntries.add(entry1); appEntries.add(entry2); - List channelsBypassing = new ArrayList<>(); - channelsBypassing.add(mock(NotificationChannel.class)); - channelsBypassing.add(mock(NotificationChannel.class)); - when(mBackend.getNotificationChannelsBypassingDnd(anyString(), - anyInt())).thenReturn(new ParceledListSlice<>(channelsBypassing)); + appEntries.add(entry3); + when(mBackend.getPackagesBypassingDnd(anyInt())).thenReturn( + Map.of("test", true, "test2", false)); // THEN there's are two preferences mController.updateAppList(appEntries); - verify(mPreferenceCategory, times(2)).addPreference(any()); + ArgumentCaptor captor = ArgumentCaptor.forClass(Preference.class); + verify(mPreferenceCategory, times(2)).addPreference(captor.capture()); + List prefs = captor.getAllValues(); + assertThat(prefs.get(0).getSummary().toString()).isEqualTo("All notifications"); + assertThat(prefs.get(1).getSummary().toString()).isEqualTo("Some notifications"); } @Test 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 fa83f309db1..e747b423af5 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java @@ -77,6 +77,7 @@ import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Random; @RunWith(RobolectricTestRunner.class) @@ -200,8 +201,8 @@ public final class ZenModeAppsLinkPreferenceControllerTest { ApplicationsState.AppEntry app2 = createAppEntry("app2", mContext.getUserId()); List allApps = List.of(app1, app2); - when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId(), - false)).thenReturn(List.of("app1")); + when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId())).thenReturn( + Map.of("app1", true)); assertThat(mController.getAppsBypassingDndSortedByName(allApps)).containsExactly(app1); } @@ -213,8 +214,8 @@ public final class ZenModeAppsLinkPreferenceControllerTest { ApplicationsState.AppEntry appB = createAppEntry("B", mContext.getUserId()); List allApps = List.of(appC, appA, appB); - when(mHelperBackend.getPackagesBypassingDnd(eq(mContext.getUserId()), anyBoolean())) - .thenReturn(List.of("B", "C", "A")); + when(mHelperBackend.getPackagesBypassingDnd(eq(mContext.getUserId()))) + .thenReturn(Map.of("B", true, "C", false, "A", true)); assertThat(mController.getAppsBypassingDndSortedByName(allApps)) .containsExactly(appA, appB, appC).inOrder(); @@ -234,10 +235,10 @@ public final class ZenModeAppsLinkPreferenceControllerTest { List allApps = List.of(workCopy, personalCopy, otherPersonal, otherWork); - when(mHelperBackend.getPackagesBypassingDnd(eq(mContext.getUserId()), anyBoolean())) - .thenReturn(List.of("app", "p2")); - when(mHelperBackend.getPackagesBypassingDnd(eq(10), anyBoolean())) - .thenReturn(List.of("app")); + when(mHelperBackend.getPackagesBypassingDnd(eq(mContext.getUserId()))) + .thenReturn(Map.of("app", true, "p2", true)); + when(mHelperBackend.getPackagesBypassingDnd(eq(10))) + .thenReturn(Map.of("app", false)); // Personal copy before work copy (names match). assertThat(mController.getAppsBypassingDndSortedByName(allApps)) @@ -253,7 +254,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest { mController.updateState(mPreference, zenMode); verifyNoMoreInteractions(mSession); - verify(mHelperBackend, never()).getPackagesBypassingDnd(anyInt(), anyBoolean()); + verify(mHelperBackend, never()).getPackagesBypassingDnd(anyInt()); assertThat(String.valueOf(mPreference.getSummary())).isEqualTo("None"); } @@ -266,9 +267,8 @@ public final class ZenModeAppsLinkPreferenceControllerTest { ArrayList appEntries = new ArrayList<>(); appEntries.add(createAppEntry("test", mContext.getUserId())); - when(mHelperBackend.getPackagesBypassingDnd( - mContext.getUserId(), false)) - .thenReturn(List.of("test")); + when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId())) + .thenReturn(Map.of("test", false)); // Updates the preference with the zen mode. We expect that this causes the app session // to trigger a rebuild (and display a temporary text in the meantime). @@ -286,8 +286,8 @@ public final class ZenModeAppsLinkPreferenceControllerTest { ZenMode zenMode = createPriorityChannelsZenMode(); mController.updateState(mPreference, zenMode); - when(mHelperBackend.getPackagesBypassingDnd(anyInt(), anyBoolean())) - .thenReturn(ImmutableList.of("test1", "test2")); + when(mHelperBackend.getPackagesBypassingDnd(anyInt())) + .thenReturn(Map.of("test1", false, "test2", false)); ArrayList appEntries = new ArrayList<>(); appEntries.add(createAppEntry("test1", mContext.getUserId())); appEntries.add(createAppEntry("test2", mContext.getUserId())); @@ -328,8 +328,8 @@ public final class ZenModeAppsLinkPreferenceControllerTest { .build(); ArrayList appEntries = new ArrayList<>(); appEntries.add(createAppEntry("test", mContext.getUserId())); - when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId(), false)) - .thenReturn(List.of("test")); + when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId())) + .thenReturn(Map.of("test", true)); mController.updateState(mPreference, zenModeWithNone); @@ -355,8 +355,8 @@ public final class ZenModeAppsLinkPreferenceControllerTest { .build(); ArrayList appEntries = new ArrayList<>(); appEntries.add(createAppEntry("test", mContext.getUserId())); - when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId(), false)) - .thenReturn(List.of("test")); + when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId())) + .thenReturn(Map.of("test", true)); mController.updateState(mPreference, zenModeWithPriority);