Loading src/com/android/settings/notification/modes/ZenHelperBackend.java +13 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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<String> getPackagesBypassingDnd(int userId, boolean includeConversationChannels) { Map<String, Boolean> getPackagesBypassingDnd(int userId) { Map<String, Boolean> bypassingAppsMap = new HashMap<>(); try { return mInm.getPackagesBypassingDnd(userId, includeConversationChannels); List<ZenBypassingApp> 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. */ Loading src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceController.java +27 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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; Loading @@ -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()); Loading Loading @@ -140,19 +144,25 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere } boolean doAnyAppsPassCriteria = false; Map<Integer, Map<String, Boolean>> 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<String, Boolean> 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); Loading @@ -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); } Loading src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java +1 −2 Original line number Diff line number Diff line Loading @@ -161,8 +161,7 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr Multimap<Integer, String> 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( Loading src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java +6 −4 Original line number Diff line number Diff line Loading @@ -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<AbstractPreferenceController> buildPreferenceControllers(Context context, @Nullable Application app, @Nullable Fragment host, @Nullable NotificationBackend notificationBackend) { @Nullable NotificationBackend notificationBackend, @Nullable ZenHelperBackend zenHelperBackend) { final List<AbstractPreferenceController> controllers = new ArrayList<>(); controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host, notificationBackend)); zenHelperBackend)); controllers.add(new ZenModeAddBypassingAppsPreferenceController(context, app, host, notificationBackend)); return controllers; Loading Loading @@ -86,7 +88,7 @@ public class ZenModeSelectBypassingAppsFragment extends ZenModeFragmentBase impl @Override public List<AbstractPreferenceController> createPreferenceControllers( Context context) { return buildPreferenceControllers(context, null, null, null); return buildPreferenceControllers(context, null, null, null, null); } }; } tests/robotests/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceControllerTest.java +15 −11 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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) Loading @@ -66,7 +63,7 @@ public class ZenModeAllBypassingAppsPreferenceControllerTest { private Context mContext; @Mock private NotificationBackend mBackend; private ZenHelperBackend mBackend; @Mock private PreferenceCategory mPreferenceCategory; @Mock Loading Loading @@ -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<ApplicationsState.AppEntry> appEntries = new ArrayList<>(); appEntries.add(entry1); appEntries.add(entry2); List<NotificationChannel> 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<Preference> captor = ArgumentCaptor.forClass(Preference.class); verify(mPreferenceCategory, times(2)).addPreference(captor.capture()); List<Preference> prefs = captor.getAllValues(); assertThat(prefs.get(0).getSummary().toString()).isEqualTo("All notifications"); assertThat(prefs.get(1).getSummary().toString()).isEqualTo("Some notifications"); } @Test Loading Loading
src/com/android/settings/notification/modes/ZenHelperBackend.java +13 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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<String> getPackagesBypassingDnd(int userId, boolean includeConversationChannels) { Map<String, Boolean> getPackagesBypassingDnd(int userId) { Map<String, Boolean> bypassingAppsMap = new HashMap<>(); try { return mInm.getPackagesBypassingDnd(userId, includeConversationChannels); List<ZenBypassingApp> 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. */ Loading
src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceController.java +27 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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; Loading @@ -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()); Loading Loading @@ -140,19 +144,25 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere } boolean doAnyAppsPassCriteria = false; Map<Integer, Map<String, Boolean>> 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<String, Boolean> 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); Loading @@ -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); } Loading
src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java +1 −2 Original line number Diff line number Diff line Loading @@ -161,8 +161,7 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr Multimap<Integer, String> 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( Loading
src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java +6 −4 Original line number Diff line number Diff line Loading @@ -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<AbstractPreferenceController> buildPreferenceControllers(Context context, @Nullable Application app, @Nullable Fragment host, @Nullable NotificationBackend notificationBackend) { @Nullable NotificationBackend notificationBackend, @Nullable ZenHelperBackend zenHelperBackend) { final List<AbstractPreferenceController> controllers = new ArrayList<>(); controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host, notificationBackend)); zenHelperBackend)); controllers.add(new ZenModeAddBypassingAppsPreferenceController(context, app, host, notificationBackend)); return controllers; Loading Loading @@ -86,7 +88,7 @@ public class ZenModeSelectBypassingAppsFragment extends ZenModeFragmentBase impl @Override public List<AbstractPreferenceController> createPreferenceControllers( Context context) { return buildPreferenceControllers(context, null, null, null); return buildPreferenceControllers(context, null, null, null, null); } }; }
tests/robotests/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceControllerTest.java +15 −11 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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) Loading @@ -66,7 +63,7 @@ public class ZenModeAllBypassingAppsPreferenceControllerTest { private Context mContext; @Mock private NotificationBackend mBackend; private ZenHelperBackend mBackend; @Mock private PreferenceCategory mPreferenceCategory; @Mock Loading Loading @@ -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<ApplicationsState.AppEntry> appEntries = new ArrayList<>(); appEntries.add(entry1); appEntries.add(entry2); List<NotificationChannel> 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<Preference> captor = ArgumentCaptor.forClass(Preference.class); verify(mPreferenceCategory, times(2)).addPreference(captor.capture()); List<Preference> prefs = captor.getAllValues(); assertThat(prefs.get(0).getSummary().toString()).isEqualTo("All notifications"); assertThat(prefs.get(1).getSummary().toString()).isEqualTo("Some notifications"); } @Test Loading