Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1500f3ec authored by Yuri Lin's avatar Yuri Lin
Browse files

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
parent 88307075
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -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<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) {
        try {
            sINM.updateNotificationChannelForPackage(pkg, uid, channel);
+50 −10
Original line number Diff line number Diff line
@@ -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<ApplicationsState.AppEntry> apps) {
        if (apps == null) {
        if (apps == null || mNotificationBackend == null) {
            return;
        }

@@ -157,21 +169,49 @@ public class ZenModeAddBypassingAppsPreferenceController extends AbstractPrefere
            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;
        for (ApplicationsState.AppEntry app : apps) {
            String pkg = app.info.packageName;
            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 appChannelsBypassingDnd = mNotificationBackend
                        .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size();
            if (appChannelsBypassingDnd == 0 && appChannels > 0) {
                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);
            }
+1 −1
Original line number Diff line number Diff line
@@ -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;
    }

+27 −4
Original line number Diff line number Diff line
@@ -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<FlagsParameterization> 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<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
        appEntries.add(appWithBypassingChannels);
        appEntries.add(appWithoutChannels);