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

Commit bf1b95e0 authored by Matías Hernández's avatar Matías Hernández
Browse files

Don't allow setting channels to bypass DND if the app cannot show notifications

The fix makes the flow more consistent with preexisting behavior:
* Apps appear in the "Apps that can interrupt" section only if some channels are set to bypass DND *and* the app is able to show notifications.
* Channels cannot be set to bypass if those individual channels are blocked.

Fixes: 265064188
Test: atest AppChannelsBypassingDndPreferenceControllerTest
Change-Id: If10f086fd60f0f70f53adb8f5cd67f90936bc35f
parent cb24cb29
Loading
Loading
Loading
Loading
+8 −4
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;

import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.lifecycle.LifecycleObserver;
import androidx.preference.Preference;
@@ -54,7 +55,7 @@ import java.util.List;
public class AppChannelsBypassingDndPreferenceController extends NotificationPreferenceController
        implements PreferenceControllerMixin, LifecycleObserver {

    private static final String KEY = "zen_mode_bypassing_app_channels_list";
    @VisibleForTesting static final String KEY = "zen_mode_bypassing_app_channels_list";
    private static final String ARG_FROM_SETTINGS = "fromSettings";

    private RestrictedSwitchPreference mAllNotificationsToggle;
@@ -74,8 +75,8 @@ public class AppChannelsBypassingDndPreferenceController extends NotificationPre
        mAllNotificationsToggle = new RestrictedSwitchPreference(mPreferenceCategory.getContext());
        mAllNotificationsToggle.setTitle(R.string.zen_mode_bypassing_app_channels_toggle_all);
        mAllNotificationsToggle.setDisabledByAdmin(mAdmin);
        mAllNotificationsToggle.setEnabled(
                (mAdmin == null || !mAllNotificationsToggle.isDisabledByAdmin()));
        mAllNotificationsToggle.setEnabled(!mAppRow.banned
                && (mAdmin == null || !mAllNotificationsToggle.isDisabledByAdmin()));
        mAllNotificationsToggle.setOnPreferenceClickListener(
                new Preference.OnPreferenceClickListener() {
                    @Override
@@ -206,6 +207,9 @@ public class AppChannelsBypassingDndPreferenceController extends NotificationPre
    }

    private boolean areAllChannelsBypassing() {
        if (mAppRow.banned) {
            return false;
        }
        boolean allChannelsBypassing = true;
        for (NotificationChannel channel : mChannels) {
            if (showNotification(channel)) {
@@ -226,7 +230,7 @@ public class AppChannelsBypassingDndPreferenceController extends NotificationPre
     * Whether notifications from this channel would show if DND weren't on.
     */
    private boolean showNotification(NotificationChannel channel) {
        return channel.getImportance() != IMPORTANCE_NONE;
        return !mAppRow.banned && channel.getImportance() != IMPORTANCE_NONE;
    }

    /**
+151 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.notification.app;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ParceledListSlice;

import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;

import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.PrimarySwitchPreference;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadows.ShadowApplication;

import java.util.ArrayList;
import java.util.Collections;

@RunWith(RobolectricTestRunner.class)
public class AppChannelsBypassingDndPreferenceControllerTest {

    @Mock
    private NotificationBackend mBackend;

    private NotificationBackend.AppRow mAppRow;
    private AppChannelsBypassingDndPreferenceController mController;

    private PreferenceScreen mPreferenceScreen;
    private PreferenceCategory mCategory;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        Context context = ApplicationProvider.getApplicationContext();

        mAppRow = new NotificationBackend.AppRow();
        mAppRow.uid = 42;
        mAppRow.pkg = "com.example.exampling";

        mController = new AppChannelsBypassingDndPreferenceController(context, mBackend);
        mController.onResume(mAppRow, null, null, null, null, null, new ArrayList<>());

        PreferenceManager preferenceManager = new PreferenceManager(context);
        mPreferenceScreen = preferenceManager.createPreferenceScreen(context);
        mCategory = new PreferenceCategory(context);
        mCategory.setKey(AppChannelsBypassingDndPreferenceController.KEY);
        mPreferenceScreen.addPreference(mCategory);
    }

    @Test
    public void displayPreference_showsAllAndChannels() {
        when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn(
                buildGroupList(true, true, false));

        mController.displayPreference(mPreferenceScreen);
        ShadowApplication.runBackgroundTasks();

        assertThat(mCategory.getPreferenceCount()).isEqualTo(4); // "All" + 3 channels
        assertThat(mCategory.getPreference(0).getTitle().toString()).isEqualTo(
                "Allow all notifications");
        assertThat(mCategory.getPreference(1).getTitle().toString()).isEqualTo("Channel 1");
        assertThat(mCategory.getPreference(2).getTitle().toString()).isEqualTo("Channel 2");
        assertThat(mCategory.getPreference(3).getTitle().toString()).isEqualTo("Channel 3");
    }

    @Test
    public void displayPreference_canToggleAllInterrupt() {
        when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn(
                buildGroupList(true, true, false));

        mController.displayPreference(mPreferenceScreen);
        ShadowApplication.runBackgroundTasks();

        assertThat(mCategory.getPreference(0).isEnabled()).isTrue();
    }

    @Test
    public void displayPreference_canToggleInterruptIfChannelEnabled() {
        when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn(
                buildGroupList(true, false, true));

        mController.displayPreference(mPreferenceScreen);
        ShadowApplication.runBackgroundTasks();

        assertThat(((PrimarySwitchPreference) mCategory.getPreference(
                1)).isSwitchEnabled()).isTrue();
        assertThat(((PrimarySwitchPreference) mCategory.getPreference(
                2)).isSwitchEnabled()).isFalse();
        assertThat(((PrimarySwitchPreference) mCategory.getPreference(
                3)).isSwitchEnabled()).isTrue();
    }

    @Test
    public void displayPreference_appBlocked_cannotToggleAllOrChannelInterrupts() {
        mAppRow.banned = true;
        when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn(
                buildGroupList(true, false, true));

        mController.displayPreference(mPreferenceScreen);
        ShadowApplication.runBackgroundTasks();

        assertThat(mCategory.getPreference(0).isEnabled()).isFalse();
        assertThat(((PrimarySwitchPreference) mCategory.getPreference(
                1)).isSwitchEnabled()).isFalse();
        assertThat(((PrimarySwitchPreference) mCategory.getPreference(
                2)).isSwitchEnabled()).isFalse();
        assertThat(((PrimarySwitchPreference) mCategory.getPreference(
                3)).isSwitchEnabled()).isFalse();
    }

    private static ParceledListSlice<NotificationChannelGroup> buildGroupList(
            boolean... enabledByChannel) {
        NotificationChannelGroup group = new NotificationChannelGroup("group", "The Group");
        for (int i = 0; i < enabledByChannel.length; i++) {
            group.addChannel(new NotificationChannel("channel-" + (i + 1), "Channel " + (i + 1),
                    enabledByChannel[i] ? NotificationManager.IMPORTANCE_DEFAULT
                            : NotificationManager.IMPORTANCE_NONE));
        }
        return new ParceledListSlice<>(Collections.singletonList(group));
    }
}