diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index d7a747ce004..6994d80d9fd 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -23,6 +23,7 @@ import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY import static com.android.server.notification.Flags.notificationHideUnusedChannels; +import android.annotation.FlaggedApi; import android.app.Flags; import android.app.INotificationManager; import android.app.NotificationChannel; @@ -54,6 +55,7 @@ import android.text.format.DateUtils; import android.util.IconDrawableFactory; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.util.CollectionUtils; @@ -66,10 +68,13 @@ import com.android.settingslib.utils.StringUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; public class NotificationBackend { private static final String TAG = "NotificationBackend"; @@ -367,6 +372,20 @@ public class NotificationBackend { } } + /** + * Returns a set of all apps that have any notification channels (not including deleted ones). + */ + @FlaggedApi(Flags.FLAG_NM_BINDER_PERF_GET_APPS_WITH_CHANNELS) + public @NonNull Set getPackagesWithAnyChannels(int userId) { + try { + List packages = sINM.getPackagesWithAnyChannels(userId); + return new HashSet<>(packages); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return Collections.EMPTY_SET; + } + } + public void updateChannel(String pkg, int uid, NotificationChannel channel) { try { sINM.updateNotificationChannelForPackage(pkg, uid, channel); diff --git a/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceController.java index ccd35eca1d5..37f2205cc8a 100644 --- a/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceController.java @@ -17,11 +17,13 @@ package com.android.settings.notification.modes; import android.app.Application; +import android.app.Flags; import android.app.settings.SettingsEnums; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; +import android.os.UserManager; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -44,7 +46,11 @@ import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.widget.AppPreference; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; /** @@ -58,6 +64,8 @@ public class ZenModeAddBypassingAppsPreferenceController extends AbstractPrefere private static final String KEY = "zen_mode_non_bypassing_apps_list"; private static final String KEY_ADD = "zen_mode_bypassing_apps_add"; @Nullable private final NotificationBackend mNotificationBackend; + @Nullable private final ZenHelperBackend mHelperBackend; + @Nullable private final UserManager mUserManager; @Nullable @VisibleForTesting ApplicationsState mApplicationsState; @VisibleForTesting PreferenceScreen mPreferenceScreen; @@ -69,18 +77,22 @@ public class ZenModeAddBypassingAppsPreferenceController extends AbstractPrefere @Nullable private Fragment mHostFragment; public ZenModeAddBypassingAppsPreferenceController(Context context, @Nullable Application app, - @Nullable Fragment host, @Nullable NotificationBackend notificationBackend) { + @Nullable Fragment host, @Nullable NotificationBackend notificationBackend, + @Nullable ZenHelperBackend helperBackend) { this(context, app == null ? null : ApplicationsState.getInstance(app), host, - notificationBackend); + notificationBackend, helperBackend); } private ZenModeAddBypassingAppsPreferenceController(Context context, @Nullable ApplicationsState appState, @Nullable Fragment host, - @Nullable NotificationBackend notificationBackend) { + @Nullable NotificationBackend notificationBackend, + @Nullable ZenHelperBackend helperBackend) { super(context); mNotificationBackend = notificationBackend; mApplicationsState = appState; mHostFragment = host; + mHelperBackend = helperBackend; + mUserManager = context.getSystemService(UserManager.class); } @Override @@ -147,7 +159,7 @@ public class ZenModeAddBypassingAppsPreferenceController extends AbstractPrefere @VisibleForTesting void updateAppList(List apps) { - if (apps == null) { + if (apps == null || mNotificationBackend == null) { return; } @@ -157,21 +169,49 @@ public class ZenModeAddBypassingAppsPreferenceController extends AbstractPrefere mPreferenceScreen.addPreference(mPreferenceCategory); } + Map> packagesByUser = new HashMap<>(); + Map> packagesBypassingDndByUser = new HashMap<>(); + if (Flags.nmBinderPerfGetAppsWithChannels()) { + if (mHelperBackend == null || mUserManager == null) { + return; + } + for (UserHandle userHandle : mUserManager.getUserProfiles()) { + int userId = userHandle.getIdentifier(); + packagesByUser.put(userId, mNotificationBackend.getPackagesWithAnyChannels(userId)); + packagesBypassingDndByUser.put(userId, + mHelperBackend.getPackagesBypassingDnd(userId)); + } + } boolean doAnyAppsPassCriteria = false; 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 && appChannels > 0) { + int userId = UserHandle.getUserId(app.info.uid); + + boolean doesAppBypassDnd, doesAppHaveAnyChannels; + if (Flags.nmBinderPerfGetAppsWithChannels()) { + Set packagesWithChannels = packagesByUser.getOrDefault(userId, + Collections.EMPTY_SET); + Map packagesBypassingDnd = + packagesBypassingDndByUser.getOrDefault(userId, new HashMap<>()); + doesAppBypassDnd = packagesBypassingDnd.containsKey(pkg); + doesAppHaveAnyChannels = packagesWithChannels.contains(pkg); + } else { + final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid); + final int appChannelsBypassingDnd = mNotificationBackend + .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size(); + doesAppBypassDnd = (appChannelsBypassingDnd != 0); + doesAppHaveAnyChannels = (appChannels > 0); + } + + if (!doesAppBypassDnd && doesAppHaveAnyChannels) { doAnyAppsPassCriteria = true; } Preference pref = mPreferenceCategory.findPreference(key); if (pref == null) { - if (appChannelsBypassingDnd == 0 && appChannels > 0) { + if (!doesAppBypassDnd && doesAppHaveAnyChannels) { // does not exist but should pref = new AppPreference(mPrefContext); pref.setKey(key); @@ -193,7 +233,7 @@ public class ZenModeAddBypassingAppsPreferenceController extends AbstractPrefere updateIcon(pref, app); mPreferenceCategory.addPreference(pref); } - } else if (appChannelsBypassingDnd != 0 || appChannels == 0) { + } else if (doesAppBypassDnd || !doesAppHaveAnyChannels) { // exists but shouldn't anymore mPreferenceCategory.removePreference(pref); } diff --git a/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java index 21f34a2005d..05703fbcf52 100644 --- a/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java +++ b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java @@ -60,7 +60,7 @@ public class ZenModeSelectBypassingAppsFragment extends ZenModeFragmentBase impl controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host, zenHelperBackend)); controllers.add(new ZenModeAddBypassingAppsPreferenceController(context, app, host, - notificationBackend)); + notificationBackend, zenHelperBackend)); return controllers; } diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceControllerTest.java index c524ab957a7..e1049395947 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceControllerTest.java @@ -19,6 +19,7 @@ package com.android.settings.notification.modes; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -30,6 +31,8 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.ParceledListSlice; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.UsesFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; import androidx.fragment.app.Fragment; @@ -46,36 +49,50 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; +import org.robolectric.ParameterizedRobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.Set; -@RunWith(RobolectricTestRunner.class) +@RunWith(ParameterizedRobolectricTestRunner.class) +@UsesFlags(android.app.Flags.class) @EnableFlags(Flags.FLAG_MODES_UI) public class ZenModeAddBypassingAppsPreferenceControllerTest { - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private NotificationBackend mBackend; @Mock + private ZenHelperBackend mHelperBackend; + @Mock private PreferenceCategory mPreferenceCategory; @Mock private ApplicationsState mApplicationState; private ZenModeAddBypassingAppsPreferenceController mController; private Context mContext; + @ParameterizedRobolectricTestRunner.Parameters(name = "{0}") + public static List getParams() { + return FlagsParameterization.allCombinationsOf( + Flags.FLAG_NM_BINDER_PERF_GET_APPS_WITH_CHANNELS); + } + + public ZenModeAddBypassingAppsPreferenceControllerTest(FlagsParameterization flags) { + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setup() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mController = new ZenModeAddBypassingAppsPreferenceController( - mContext, null, mock(Fragment.class), mBackend); + mContext, null, mock(Fragment.class), mBackend, mHelperBackend); mController.mPreferenceCategory = mPreferenceCategory; mController.mApplicationsState = mApplicationState; mController.mPrefContext = mContext; @@ -132,6 +149,12 @@ public class ZenModeAddBypassingAppsPreferenceControllerTest { appWithChannelsNoneBypassing.info.uid)) .thenReturn(new ParceledListSlice<>(new ArrayList<>())); + // used when NM_BINDER_PERF_GET_APPS_WITH_CHANNELS flag is true + when(mBackend.getPackagesWithAnyChannels(anyInt())).thenReturn( + Set.of("appWithBypassingChannels", "appWithChannelsNoneBypassing")); + when(mHelperBackend.getPackagesBypassingDnd(anyInt())).thenReturn( + Map.of("appWithBypassingChannels", false)); + List appEntries = new ArrayList<>(); appEntries.add(appWithBypassingChannels); appEntries.add(appWithoutChannels);