From 1500f3ecc05790e252f5d6c883e55bd3bfb0d97c Mon Sep 17 00:00:00 2001 From: Yuri Lin Date: Mon, 10 Feb 2025 13:25:00 -0500 Subject: [PATCH] Update ZenModeAddBypassingApps to not binder call for every app This update already happened for ZenModeAllBypassingApps to use the new single binder call to get all packages bypassing DND. This change uses that method for bypassing apps as well as collecting the set of all apps with a nonzero number of channels. Bug: 368623163 Bug: 394614704 Test: ZenModeAddBypassingAppsPreferenceControllerTest, manual to confirm correct behavior Flag: android.app.nm_binder_perf_get_apps_with_channels Change-Id: I72a1edcb07d18f5707591a5341d7a7338c23f42b --- .../notification/NotificationBackend.java | 19 ++++++ ...eAddBypassingAppsPreferenceController.java | 60 +++++++++++++++---- .../ZenModeSelectBypassingAppsFragment.java | 2 +- ...BypassingAppsPreferenceControllerTest.java | 31 ++++++++-- 4 files changed, 97 insertions(+), 15 deletions(-) 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);