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

Commit 7973c635 authored by Beverly's avatar Beverly
Browse files

Add new settings page for apps overriding dnd

Close starred contacts cursor

Test: atest ZenModeAllBypassingAppsPreferenceControllerTest
Test: atest ZenModeBypassingAppsPreferenceControllerTest
Bug: 111475013
Change-Id: I84d259aaef1bc5cc907ec5bef08beade91a8b904
parent 2ebf61cd
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -8025,6 +8025,19 @@
    <!-- [CHAR LIMIT=80] Zen mode settings: Allow event notifications/sounds to bypass DND -->
    <string name="zen_mode_events">Allow events</string>
    <!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND -->
    <string name="zen_mode_bypassing_apps">Allow apps to override</string>
    <!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND title-->
    <string name="zen_mode_bypassing_apps_title">Overrides Do Not Disturb</string>
    <!-- [CHAR LIMIT=80] Zen mode settings: Allow apps to bypass DND -->
    <plurals name="zen_mode_bypassing_apps_subtext">
        <item quantity="zero">No apps can override Do Not Disturb</item>
        <item quantity="one">1 app can override Do Not Disturb</item>
        <item quantity="other"><xliff:g id="number" example="2">%1$d</xliff:g> apps can override Do Not Disturb</item>
    </plurals>
    <!-- [CHAR LIMIT=50] Zen mode settings: Events (ie: calendar events) -->
    <string name="zen_mode_events_list">events</string>
+21 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright (C) 2018 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.
-->

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:key="zen_mode_bypassing_apps_screen"
    android:title="@string/zen_mode_bypassing_apps_title" />
+50 −45
Original line number Diff line number Diff line
@@ -61,7 +61,12 @@
            android:key="zen_mode_events"
            android:title="@string/zen_mode_events"/>

      <!-- TODO: beverlyt, add "Allow apps to override" -->
        <!-- Apps overriding DND -->
        <Preference
            android:key="zen_mode_bypassing_apps"
            android:title="@string/zen_mode_bypassing_apps"
            android:fragment="com.android.settings.notification.ZenModeBypassingAppsSettings"/>

    </PreferenceCategory>

    <com.android.settingslib.widget.FooterPreference/>
+25 −2
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import android.app.INotificationManager;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.content.Context;
@@ -38,8 +39,6 @@ import android.text.format.DateUtils;
import android.util.IconDrawableFactory;
import android.util.Log;

import androidx.annotation.VisibleForTesting;

import com.android.settingslib.R;
import com.android.settingslib.Utils;
import com.android.settingslib.utils.StringUtil;
@@ -49,6 +48,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;

import androidx.annotation.VisibleForTesting;

public class NotificationBackend {
    private static final String TAG = "NotificationBackend";

@@ -208,6 +209,19 @@ public class NotificationBackend {
        }
    }

    /**
     * Returns all notification channels associated with the package and uid that will bypass DND
     */
    public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
            int uid) {
        try {
            return sINM.getNotificationChannelsBypassingDnd(pkg, uid);
        } catch (Exception e) {
            Log.w(TAG, "Error calling NoMan", e);
            return ParceledListSlice.emptyList();
        }
    }

    public void updateChannel(String pkg, int uid, NotificationChannel channel) {
        try {
            sINM.updateNotificationChannelForPackage(pkg, uid, channel);
@@ -260,6 +274,15 @@ public class NotificationBackend {
        }
    }

    public int getNumAppsBypassingDnd(int uid) {
        try {
            return sINM.getAppsBypassingDndCount(uid);
        } catch (Exception e) {
            Log.w(TAG, "Error calling NoMan", e);
            return 0;
        }
    }

    public List<NotifyingApp> getRecentApps() {
        try {
            return sINM.getRecentNotifyingAppsForUser(UserHandle.myUserId()).getList();
+200 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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;

import android.app.Application;
import android.app.NotificationChannel;
import android.content.Context;
import android.os.Bundle;
import android.provider.Settings;

import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
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.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.apppreference.AppPreference;

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

import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

/**
 * Adds a preference to the PreferenceScreen for each notification channel that can bypass DND.
 */
public class ZenModeAllBypassingAppsPreferenceController extends AbstractPreferenceController
        implements PreferenceControllerMixin {

    private final String KEY = "zen_mode_bypassing_apps_category";

    @VisibleForTesting ApplicationsState mApplicationsState;
    @VisibleForTesting PreferenceScreen mPreferenceScreen;
    @VisibleForTesting Context mPrefContext;

    private ApplicationsState.Session mAppSession;
    private NotificationBackend mNotificationBackend = new NotificationBackend();
    private Fragment mHostFragment;

    public ZenModeAllBypassingAppsPreferenceController(Context context, Application app,
            Fragment host) {

        this(context, app == null ? null : ApplicationsState.getInstance(app), host);
    }

    private ZenModeAllBypassingAppsPreferenceController(Context context, ApplicationsState appState,
            Fragment host) {
        super(context);
        mApplicationsState = appState;
        mHostFragment = host;

        if (mApplicationsState != null && host != null) {
            mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, host.getLifecycle());
        }
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        mPreferenceScreen = screen;
        mPrefContext = mPreferenceScreen.getContext();
        updateNotificationChannelList();
        super.displayPreference(screen);
    }

    @Override
    public boolean isAvailable() {
        return true;
    }

    @Override
    public String getPreferenceKey() {
        return KEY;
    }

    /**
     * Call this method to trigger the notification channels list to refresh.
     */
    public void updateNotificationChannelList() {
        if (mAppSession == null) {
            return;
        }

        ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED;
        List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter,
                ApplicationsState.ALPHA_COMPARATOR);
        if (apps != null) {
            updateNotificationChannelList(apps);
        }
    }

    @VisibleForTesting
    void updateNotificationChannelList(List<ApplicationsState.AppEntry> apps) {
        if (mPreferenceScreen == null || apps == null) {
            return;
        }

        List<Preference> channelsBypassingDnd = new ArrayList<>();
        for (ApplicationsState.AppEntry entry : apps) {
            String pkg = entry.info.packageName;
            mApplicationsState.ensureIcon(entry);
            for (NotificationChannel channel : mNotificationBackend
                    .getNotificationChannelsBypassingDnd(pkg, entry.info.uid).getList()) {
                Preference pref = new AppPreference(mPrefContext);
                pref.setKey(pkg + "|" + channel.getId());
                pref.setTitle(BidiFormatter.getInstance().unicodeWrap(entry.label));
                pref.setIcon(entry.icon);
                pref.setSummary(BidiFormatter.getInstance().unicodeWrap(channel.getName()));

                pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
                    @Override
                    public boolean onPreferenceClick(Preference preference) {
                        Bundle args = new Bundle();
                        args.putString(AppInfoBase.ARG_PACKAGE_NAME, entry.info.packageName);
                        args.putInt(AppInfoBase.ARG_PACKAGE_UID, entry.info.uid);
                        args.putString(Settings.EXTRA_CHANNEL_ID, channel.getId());
                        new SubSettingLauncher(mContext)
                                .setDestination(ChannelNotificationSettings.class.getName())
                                .setArguments(args)
                                .setTitleRes(R.string.notification_channel_title)
                                .setResultListener(mHostFragment, 0)
                                .setSourceMetricsCategory(
                                        MetricsEvent.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
                                .launch();
                        return true;
                    }
                });
                channelsBypassingDnd.add(pref);
            }

            mPreferenceScreen.removeAll();
            if (channelsBypassingDnd.size() > 0) {
                for (Preference prefToAdd : channelsBypassingDnd) {
                    mPreferenceScreen.addPreference(prefToAdd);
                }
            }
        }
    }

    private final ApplicationsState.Callbacks mAppSessionCallbacks =
            new ApplicationsState.Callbacks() {

                @Override
                public void onRunningStateChanged(boolean running) {
                    updateNotificationChannelList();
                }

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

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

                @Override
                public void onPackageIconChanged() {
                    updateNotificationChannelList();
                }

                @Override
                public void onPackageSizeChanged(String packageName) {
                    updateNotificationChannelList();
                }

                @Override
                public void onAllSizesComputed() { }

                @Override
                public void onLauncherInfoChanged() {
                    updateNotificationChannelList();
                }

                @Override
                public void onLoadEntriesCompleted() {
                    updateNotificationChannelList();
                }
            };
}
Loading