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
This commit is contained in:
Yuri Lin
2025-02-10 13:25:00 -05:00
parent 883070758b
commit 1500f3ecc0
4 changed files with 97 additions and 15 deletions

View File

@@ -23,6 +23,7 @@ import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY
import static com.android.server.notification.Flags.notificationHideUnusedChannels; import static com.android.server.notification.Flags.notificationHideUnusedChannels;
import android.annotation.FlaggedApi;
import android.app.Flags; import android.app.Flags;
import android.app.INotificationManager; import android.app.INotificationManager;
import android.app.NotificationChannel; import android.app.NotificationChannel;
@@ -54,6 +55,7 @@ import android.text.format.DateUtils;
import android.util.IconDrawableFactory; import android.util.IconDrawableFactory;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.android.internal.util.CollectionUtils; import com.android.internal.util.CollectionUtils;
@@ -66,10 +68,13 @@ import com.android.settingslib.utils.StringUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
public class NotificationBackend { public class NotificationBackend {
private static final String TAG = "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<String> getPackagesWithAnyChannels(int userId) {
try {
List<String> 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) { public void updateChannel(String pkg, int uid, NotificationChannel channel) {
try { try {
sINM.updateNotificationChannelForPackage(pkg, uid, channel); sINM.updateNotificationChannelForPackage(pkg, uid, channel);

View File

@@ -17,11 +17,13 @@
package com.android.settings.notification.modes; package com.android.settings.notification.modes;
import android.app.Application; import android.app.Application;
import android.app.Flags;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
@@ -44,7 +46,11 @@ import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.AppPreference; import com.android.settingslib.widget.AppPreference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; 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 = "zen_mode_non_bypassing_apps_list";
private static final String KEY_ADD = "zen_mode_bypassing_apps_add"; private static final String KEY_ADD = "zen_mode_bypassing_apps_add";
@Nullable private final NotificationBackend mNotificationBackend; @Nullable private final NotificationBackend mNotificationBackend;
@Nullable private final ZenHelperBackend mHelperBackend;
@Nullable private final UserManager mUserManager;
@Nullable @VisibleForTesting ApplicationsState mApplicationsState; @Nullable @VisibleForTesting ApplicationsState mApplicationsState;
@VisibleForTesting PreferenceScreen mPreferenceScreen; @VisibleForTesting PreferenceScreen mPreferenceScreen;
@@ -69,18 +77,22 @@ public class ZenModeAddBypassingAppsPreferenceController extends AbstractPrefere
@Nullable private Fragment mHostFragment; @Nullable private Fragment mHostFragment;
public ZenModeAddBypassingAppsPreferenceController(Context context, @Nullable Application app, 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, this(context, app == null ? null : ApplicationsState.getInstance(app), host,
notificationBackend); notificationBackend, helperBackend);
} }
private ZenModeAddBypassingAppsPreferenceController(Context context, private ZenModeAddBypassingAppsPreferenceController(Context context,
@Nullable ApplicationsState appState, @Nullable Fragment host, @Nullable ApplicationsState appState, @Nullable Fragment host,
@Nullable NotificationBackend notificationBackend) { @Nullable NotificationBackend notificationBackend,
@Nullable ZenHelperBackend helperBackend) {
super(context); super(context);
mNotificationBackend = notificationBackend; mNotificationBackend = notificationBackend;
mApplicationsState = appState; mApplicationsState = appState;
mHostFragment = host; mHostFragment = host;
mHelperBackend = helperBackend;
mUserManager = context.getSystemService(UserManager.class);
} }
@Override @Override
@@ -147,7 +159,7 @@ public class ZenModeAddBypassingAppsPreferenceController extends AbstractPrefere
@VisibleForTesting @VisibleForTesting
void updateAppList(List<ApplicationsState.AppEntry> apps) { void updateAppList(List<ApplicationsState.AppEntry> apps) {
if (apps == null) { if (apps == null || mNotificationBackend == null) {
return; return;
} }
@@ -157,21 +169,49 @@ public class ZenModeAddBypassingAppsPreferenceController extends AbstractPrefere
mPreferenceScreen.addPreference(mPreferenceCategory); mPreferenceScreen.addPreference(mPreferenceCategory);
} }
Map<Integer, Set<String>> packagesByUser = new HashMap<>();
Map<Integer, Map<String, Boolean>> 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; boolean doAnyAppsPassCriteria = false;
for (ApplicationsState.AppEntry app : apps) { for (ApplicationsState.AppEntry app : apps) {
String pkg = app.info.packageName; String pkg = app.info.packageName;
final String key = getKey(pkg, app.info.uid); final String key = getKey(pkg, app.info.uid);
int userId = UserHandle.getUserId(app.info.uid);
boolean doesAppBypassDnd, doesAppHaveAnyChannels;
if (Flags.nmBinderPerfGetAppsWithChannels()) {
Set<String> packagesWithChannels = packagesByUser.getOrDefault(userId,
Collections.EMPTY_SET);
Map<String, Boolean> 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 appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid);
final int appChannelsBypassingDnd = mNotificationBackend final int appChannelsBypassingDnd = mNotificationBackend
.getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size(); .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size();
if (appChannelsBypassingDnd == 0 && appChannels > 0) { doesAppBypassDnd = (appChannelsBypassingDnd != 0);
doesAppHaveAnyChannels = (appChannels > 0);
}
if (!doesAppBypassDnd && doesAppHaveAnyChannels) {
doAnyAppsPassCriteria = true; doAnyAppsPassCriteria = true;
} }
Preference pref = mPreferenceCategory.findPreference(key); Preference pref = mPreferenceCategory.findPreference(key);
if (pref == null) { if (pref == null) {
if (appChannelsBypassingDnd == 0 && appChannels > 0) { if (!doesAppBypassDnd && doesAppHaveAnyChannels) {
// does not exist but should // does not exist but should
pref = new AppPreference(mPrefContext); pref = new AppPreference(mPrefContext);
pref.setKey(key); pref.setKey(key);
@@ -193,7 +233,7 @@ public class ZenModeAddBypassingAppsPreferenceController extends AbstractPrefere
updateIcon(pref, app); updateIcon(pref, app);
mPreferenceCategory.addPreference(pref); mPreferenceCategory.addPreference(pref);
} }
} else if (appChannelsBypassingDnd != 0 || appChannels == 0) { } else if (doesAppBypassDnd || !doesAppHaveAnyChannels) {
// exists but shouldn't anymore // exists but shouldn't anymore
mPreferenceCategory.removePreference(pref); mPreferenceCategory.removePreference(pref);
} }

View File

@@ -60,7 +60,7 @@ public class ZenModeSelectBypassingAppsFragment extends ZenModeFragmentBase impl
controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host, controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host,
zenHelperBackend)); zenHelperBackend));
controllers.add(new ZenModeAddBypassingAppsPreferenceController(context, app, host, controllers.add(new ZenModeAddBypassingAppsPreferenceController(context, app, host,
notificationBackend)); notificationBackend, zenHelperBackend));
return controllers; return controllers;
} }

View File

@@ -19,6 +19,7 @@ package com.android.settings.notification.modes;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -30,6 +31,8 @@ import android.content.Context;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice;
import android.platform.test.annotations.EnableFlags; 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 android.platform.test.flag.junit.SetFlagsRule;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -46,36 +49,50 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.ParameterizedRobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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) @EnableFlags(Flags.FLAG_MODES_UI)
public class ZenModeAddBypassingAppsPreferenceControllerTest { public class ZenModeAddBypassingAppsPreferenceControllerTest {
@Rule @Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock @Mock
private NotificationBackend mBackend; private NotificationBackend mBackend;
@Mock @Mock
private ZenHelperBackend mHelperBackend;
@Mock
private PreferenceCategory mPreferenceCategory; private PreferenceCategory mPreferenceCategory;
@Mock @Mock
private ApplicationsState mApplicationState; private ApplicationsState mApplicationState;
private ZenModeAddBypassingAppsPreferenceController mController; private ZenModeAddBypassingAppsPreferenceController mController;
private Context mContext; private Context mContext;
@ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
return FlagsParameterization.allCombinationsOf(
Flags.FLAG_NM_BINDER_PERF_GET_APPS_WITH_CHANNELS);
}
public ZenModeAddBypassingAppsPreferenceControllerTest(FlagsParameterization flags) {
mSetFlagsRule.setFlagsParameterization(flags);
}
@Before @Before
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application; mContext = RuntimeEnvironment.application;
mController = new ZenModeAddBypassingAppsPreferenceController( mController = new ZenModeAddBypassingAppsPreferenceController(
mContext, null, mock(Fragment.class), mBackend); mContext, null, mock(Fragment.class), mBackend, mHelperBackend);
mController.mPreferenceCategory = mPreferenceCategory; mController.mPreferenceCategory = mPreferenceCategory;
mController.mApplicationsState = mApplicationState; mController.mApplicationsState = mApplicationState;
mController.mPrefContext = mContext; mController.mPrefContext = mContext;
@@ -132,6 +149,12 @@ public class ZenModeAddBypassingAppsPreferenceControllerTest {
appWithChannelsNoneBypassing.info.uid)) appWithChannelsNoneBypassing.info.uid))
.thenReturn(new ParceledListSlice<>(new ArrayList<>())); .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<ApplicationsState.AppEntry> appEntries = new ArrayList<>(); List<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
appEntries.add(appWithBypassingChannels); appEntries.add(appWithBypassingChannels);
appEntries.add(appWithoutChannels); appEntries.add(appWithoutChannels);