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

Commit 41902bb0 authored by Alexander Roederer's avatar Alexander Roederer
Browse files

Modify Summary for Mode's Apps settings page

Adds call to SummaryHelper to set Apps preference summary.

Bug: 308819928
Test: atest ZenModeAppsLinkPreferenceControllerTest
Flag: android.app.modes_ui
Change-Id: Iebec11afc62ecb79386e1866af57cd4e68461a95
parent a6b1d7cb
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -357,6 +357,19 @@ public class NotificationBackend {
        }
    }

    /**
     * Returns all of a user's packages that have at least one channel that will bypass DND
     */
    public List<String> getPackagesBypassingDnd(int userId,
            boolean includeConversationChannels) {
        try {
            return sINM.getPackagesBypassingDnd(userId, includeConversationChannels);
        } catch (Exception e) {
            Log.w(TAG, "Error calling NoMan", e);
            return new ArrayList<>();
        }
    }

    public void updateChannel(String pkg, int uid, NotificationChannel channel) {
        try {
            sINM.updateNotificationChannelForPackage(pkg, uid, channel);
+103 −4
Original line number Diff line number Diff line
@@ -20,23 +20,44 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I

import android.content.Context;
import android.os.Bundle;
import android.util.ArraySet;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;

import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.applications.ApplicationsState;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Preference with a link and summary about what apps can break through the mode
 */
public class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceController {
class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceController {

    private static final String TAG = "ZenModeAppsLinkPreferenceController";

    private final ZenModeSummaryHelper mSummaryHelper;
    private ApplicationsState.Session mAppSession;
    private NotificationBackend mNotificationBackend = new NotificationBackend();
    private ZenMode mZenMode;
    private Preference mPreference;

    public ZenModeAppsLinkPreferenceController(Context context, String key,
            ZenModesBackend backend) {
    ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host,
            ApplicationsState applicationsState, ZenModesBackend backend) {
        super(context, key, backend);
        mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend);
        if (applicationsState != null && host != null) {
            mAppSession = applicationsState.newSession(mAppSessionCallbacks, host.getLifecycle());
        }
    }

    @Override
@@ -49,6 +70,84 @@ public class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferen
                .setSourceMetricsCategory(0)
                .setArguments(bundle)
                .toIntent());
        preference.setSummary(mSummaryHelper.getAppsSummary(zenMode));
        mZenMode = zenMode;
        mPreference = preference;
        triggerUpdateAppsBypassingDndSummaryText();
    }

    private void triggerUpdateAppsBypassingDndSummaryText() {
        if (mAppSession == null) {
            return;
        }

        ApplicationsState.AppFilter filter = android.multiuser.Flags.enablePrivateSpaceFeatures()
                && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()
                ? ApplicationsState.FILTER_ENABLED_NOT_QUIET
                : ApplicationsState.FILTER_ALL_ENABLED;
        // We initiate a rebuild in the background here. Once the rebuild is completed,
        // the onRebuildComplete() callback will be invoked, which will trigger the summary text
        // to be initialized.
        mAppSession.rebuild(filter, ApplicationsState.ALPHA_COMPARATOR, false);
    }

    private void updateAppsBypassingDndSummaryText(List<ApplicationsState.AppEntry> apps) {
        Set<String> appNames = getAppsBypassingDnd(apps);
        mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, appNames));
    }

    @VisibleForTesting
    ArraySet<String> getAppsBypassingDnd(@NonNull List<ApplicationsState.AppEntry> apps) {
        ArraySet<String> appsBypassingDnd = new ArraySet<>();

        Map<String, String> pkgLabelMap = new HashMap<String, String>();
        for (ApplicationsState.AppEntry entry : apps) {
            if (entry.info != null) {
                pkgLabelMap.put(entry.info.packageName, entry.label);
            }
        }
        for (String pkg : mNotificationBackend.getPackagesBypassingDnd(mContext.getUserId(),
                /* includeConversationChannels= */ false)) {
            // Settings may hide some packages from the user, so if they're not present here
            // we skip displaying them, even if they bypass dnd.
            if (pkgLabelMap.get(pkg) == null) {
                continue;
            }
            appsBypassingDnd.add(BidiFormatter.getInstance().unicodeWrap(pkgLabelMap.get(pkg)));
        }
        return appsBypassingDnd;
    }

    @VisibleForTesting final ApplicationsState.Callbacks mAppSessionCallbacks =
            new ApplicationsState.Callbacks() {

                @Override
                public void onRunningStateChanged(boolean running) { }

                @Override
                public void onPackageListChanged() {
                    triggerUpdateAppsBypassingDndSummaryText();
                }

                @Override
                public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
                    updateAppsBypassingDndSummaryText(apps);
                }

                @Override
                public void onPackageIconChanged() { }

                @Override
                public void onPackageSizeChanged(String packageName) { }

                @Override
                public void onAllSizesComputed() { }

                @Override
                public void onLauncherInfoChanged() { }

                @Override
                public void onLoadEntriesCompleted() {
                    triggerUpdateAppsBypassingDndSummaryText();
                }
            };
}
+5 −1
Original line number Diff line number Diff line
@@ -16,11 +16,13 @@

package com.android.settings.notification.modes;

import android.app.Application;
import android.app.AutomaticZenRule;
import android.app.settings.SettingsEnums;
import android.content.Context;

import com.android.settings.R;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;

import java.util.ArrayList;
@@ -42,7 +44,9 @@ public class ZenModeFragment extends ZenModeFragmentBase {
        prefControllers.add(new ZenModePeopleLinkPreferenceController(
                context, "zen_mode_people", mBackend));
        prefControllers.add(new ZenModeAppsLinkPreferenceController(
                context, "zen_mode_apps", mBackend));
                context, "zen_mode_apps", this,
                ApplicationsState.getInstance((Application) context.getApplicationContext()),
                mBackend));
        prefControllers.add(new ZenModeOtherLinkPreferenceController(
                context, "zen_other_settings", mBackend));
        prefControllers.add(new ZenModeDisplayLinkPreferenceController(
+131 −12
Original line number Diff line number Diff line
@@ -16,28 +16,51 @@

package com.android.settings.notification.modes;

import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;

import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;

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

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.os.Bundle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;

import androidx.fragment.app.Fragment;
import androidx.preference.Preference;

import com.android.settings.SettingsActivity;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.widget.SelectorWithWidgetPreference;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.ReflectionHelpers;

import java.util.ArrayList;
import java.util.List;

@RunWith(RobolectricTestRunner.class)
@EnableFlags(Flags.FLAG_MODES_UI)
@@ -47,7 +70,15 @@ public final class ZenModeAppsLinkPreferenceControllerTest {

    private Context mContext;
    @Mock
    private ZenModesBackend mBackend;
    private ZenModesBackend mZenModesBackend;

    @Mock
    private NotificationBackend mNotificationBackend;

    @Mock
    private ApplicationsState mApplicationsState;
    @Mock
    private ApplicationsState.Session mSession;

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -56,21 +87,109 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mContext = RuntimeEnvironment.application;
        when(mApplicationsState.newSession(any(), any())).thenReturn(mSession);
        mController = new ZenModeAppsLinkPreferenceController(
                mContext, "controller_key", mBackend);
                mContext, "controller_key", mock(Fragment.class), mApplicationsState,
                mZenModesBackend);
        ReflectionHelpers.setField(mController, "mNotificationBackend", mNotificationBackend);
    }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI)
    public void testHasSummary() {
        Preference pref = mock(Preference.class);
        ZenMode zenMode = new ZenMode("id",
                new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
                        .setType(AutomaticZenRule.TYPE_DRIVING)
                        .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
    private ApplicationsState.AppEntry createAppEntry(String packageName, String label) {
        ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class);
        entry.info = new ApplicationInfo();
        entry.info.packageName = packageName;
        entry.label = label;
        entry.info.uid = 0;
        return entry;
    }

    private ZenMode createPriorityChannelsZenMode() {
        return new ZenMode("id", new AutomaticZenRule.Builder("Bedtime",
                Uri.parse("bed"))
                .setType(AutomaticZenRule.TYPE_BEDTIME)
                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                .setZenPolicy(new ZenPolicy.Builder()
                        .allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
                        .build())
                .build(), true);
        mController.updateZenMode(pref, zenMode);
        verify(pref).setSummary(any());
    }

    @Test
    public void testIsAvailable() {
        assertThat(mController.isAvailable()).isTrue();
    }

    @Test
    public void testUpdateSetsIntent() {
        // Creates the preference
        SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class);
        // Create a zen mode that allows priority channels to breakthrough.
        ZenMode zenMode = createPriorityChannelsZenMode();

        // Capture the intent
        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
        mController.updateState((Preference) preference, zenMode);
        verify(preference).setIntent(captor.capture());
        Intent launcherIntent = captor.getValue();

        assertThat(launcherIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
                .isEqualTo("com.android.settings.notification.modes.ZenModeAppsFragment");
        assertThat(launcherIntent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
                -1)).isEqualTo(0);

        Bundle bundle = launcherIntent.getBundleExtra(
                SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
        assertThat(bundle).isNotNull();
        assertThat(bundle.getString(MODE_ID)).isEqualTo("id");
    }

    @Test
    public void testGetAppsBypassingDnd() {
        ApplicationsState.AppEntry entry = createAppEntry("test", "testLabel");
        ApplicationsState.AppEntry entryConv = createAppEntry("test_conv", "test_convLabel");
        List<ApplicationsState.AppEntry> appEntries = List.of(entry, entryConv);

        when(mNotificationBackend.getPackagesBypassingDnd(mContext.getUserId(),
                false)).thenReturn(List.of("test"));

        assertThat(mController.getAppsBypassingDnd(appEntries)).containsExactly("testLabel");
    }

    @Test
    public void testUpdateTriggersRebuild() {
        // Creates the preference
        SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class);
        // Create a zen mode that allows priority channels to breakthrough.
        ZenMode zenMode = createPriorityChannelsZenMode();

        // Create some applications.
        ArrayList<ApplicationsState.AppEntry> appEntries =
                new ArrayList<ApplicationsState.AppEntry>();
        appEntries.add(createAppEntry("test", "pkgLabel"));

        when(mNotificationBackend.getPackagesBypassingDnd(
                mContext.getUserId(), false))
                .thenReturn(List.of("test"));

        // Updates the preference with the zen mode. We expect that this causes the app session
        // to trigger a rebuild.
        mController.updateZenMode((Preference) preference, zenMode);
        verify(mSession).rebuild(any(), any(), eq(false));

        // Manually triggers the callback that will happen on rebuild.
        mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
        verify(preference).setSummary("pkgLabel can interrupt");
    }

    @Test
    public void testOnPackageListChangedTriggersRebuild() {
        mController.mAppSessionCallbacks.onPackageListChanged();
        verify(mSession).rebuild(any(), any(), eq(false));
    }

    @Test
    public void testOnLoadEntriesCompletedTriggersRebuild() {
        mController.mAppSessionCallbacks.onLoadEntriesCompleted();
        verify(mSession).rebuild(any(), any(), eq(false));
    }
}