1 package com.android.settings.notification.zen;
2 
3 import android.app.Application;
4 import android.app.NotificationChannel;
5 import android.content.Context;
6 import android.icu.text.MessageFormat;
7 import android.provider.Settings;
8 import android.text.TextUtils;
9 import android.util.ArraySet;
10 
11 import androidx.core.text.BidiFormatter;
12 import androidx.fragment.app.Fragment;
13 import androidx.preference.Preference;
14 import androidx.preference.PreferenceScreen;
15 
16 import com.android.internal.annotations.VisibleForTesting;
17 import com.android.settings.R;
18 import com.android.settings.core.PreferenceControllerMixin;
19 import com.android.settings.notification.NotificationBackend;
20 import com.android.settingslib.applications.ApplicationsState;
21 import com.android.settingslib.core.lifecycle.Lifecycle;
22 
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28 import java.util.Set;
29 
30 /**
31  * Controls the summary for preference found at:
32  *  Settings > Sound > Do Not Disturb > Apps
33  */
34 public class ZenModeBypassingAppsPreferenceController extends AbstractZenModePreferenceController
35         implements PreferenceControllerMixin {
36 
37     protected static final String KEY = "zen_mode_behavior_apps";
38 
39     @VisibleForTesting protected Preference mPreference;
40     private ApplicationsState.Session mAppSession;
41     private NotificationBackend mNotificationBackend = new NotificationBackend();
42 
43     private String mSummary;
44 
ZenModeBypassingAppsPreferenceController(Context context, Application app, Fragment host, Lifecycle lifecycle)45     public ZenModeBypassingAppsPreferenceController(Context context, Application app,
46             Fragment host, Lifecycle lifecycle) {
47         this(context, app == null ? null : ApplicationsState.getInstance(app), host, lifecycle);
48     }
49 
ZenModeBypassingAppsPreferenceController(Context context, ApplicationsState appState, Fragment host, Lifecycle lifecycle)50     private ZenModeBypassingAppsPreferenceController(Context context, ApplicationsState appState,
51             Fragment host, Lifecycle lifecycle) {
52         super(context, KEY, lifecycle);
53         if (appState != null && host != null) {
54             mAppSession = appState.newSession(mAppSessionCallbacks, host.getLifecycle());
55         }
56     }
57 
58     @Override
isAvailable()59     public boolean isAvailable() {
60         return true;
61     }
62 
63     @Override
getPreferenceKey()64     public String getPreferenceKey() {
65         return KEY;
66     }
67 
68     @Override
displayPreference(PreferenceScreen screen)69     public void displayPreference(PreferenceScreen screen) {
70         mPreference = screen.findPreference(KEY);
71         updateAppsBypassingDndSummaryText();
72         super.displayPreference(screen);
73     }
74 
75     @Override
getSummary()76     public String getSummary() {
77         return mSummary;
78     }
79 
updateAppsBypassingDndSummaryText()80     private void updateAppsBypassingDndSummaryText() {
81         if (mAppSession == null) {
82             return;
83         }
84 
85         ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED;
86         List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter,
87                 ApplicationsState.ALPHA_COMPARATOR);
88         updateAppsBypassingDndSummaryText(apps);
89     }
90 
91     @VisibleForTesting
updateAppsBypassingDndSummaryText(List<ApplicationsState.AppEntry> apps)92     void updateAppsBypassingDndSummaryText(List<ApplicationsState.AppEntry> apps) {
93         switch (getZenMode()) {
94             case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
95             case Settings.Global.ZEN_MODE_ALARMS:
96                 // users cannot change their DND settings when an app puts the device total
97                 // silence or alarms only (both deprecated) modes
98                 mPreference.setEnabled(false);
99                 mSummary = mContext.getResources().getString(
100                         R.string.zen_mode_bypassing_apps_subtext_none);
101                 return;
102             default:
103                 mPreference.setEnabled(true);
104         }
105 
106         if (apps == null) {
107             return;
108         }
109 
110         Set<String> appsBypassingDnd = new ArraySet<>();
111         for (ApplicationsState.AppEntry entry : apps) {
112             String pkg = entry.info.packageName;
113             for (NotificationChannel channel : mNotificationBackend
114                     .getNotificationChannelsBypassingDnd(pkg, entry.info.uid).getList()) {
115                 if (!TextUtils.isEmpty(channel.getConversationId()) && !channel.isDemoted()) {
116                     // conversation channels that bypass dnd will be shown on the People page
117                     continue;
118                 }
119                 appsBypassingDnd.add(BidiFormatter.getInstance().unicodeWrap(entry.label));
120                 continue;
121             }
122         }
123 
124         final int numAppsBypassingDnd = appsBypassingDnd.size();
125         String[] appsBypassingDndArr = appsBypassingDnd.toArray(new String[numAppsBypassingDnd]);
126         MessageFormat msgFormat = new MessageFormat(
127                 mContext.getString(R.string.zen_mode_bypassing_apps_subtext),
128                 Locale.getDefault());
129         Map<String, Object> args = new HashMap<>();
130         args.put("count", numAppsBypassingDnd);
131         if (numAppsBypassingDnd >= 1) {
132             args.put("app_1", appsBypassingDndArr[0]);
133             if (numAppsBypassingDnd >= 2) {
134                 args.put("app_2", appsBypassingDndArr[1]);
135                 if (numAppsBypassingDnd == 3) {
136                     args.put("app_3", appsBypassingDndArr[2]);
137                 }
138             }
139         }
140 
141         mSummary = msgFormat.format(args);
142         refreshSummary(mPreference);
143     }
144 
145     private final ApplicationsState.Callbacks mAppSessionCallbacks =
146             new ApplicationsState.Callbacks() {
147 
148                 @Override
149                 public void onRunningStateChanged(boolean running) {
150                     updateAppsBypassingDndSummaryText();
151                 }
152 
153                 @Override
154                 public void onPackageListChanged() {
155                     updateAppsBypassingDndSummaryText();
156                 }
157 
158                 @Override
159                 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
160                     updateAppsBypassingDndSummaryText(apps);
161                 }
162 
163                 @Override
164                 public void onPackageIconChanged() { }
165 
166                 @Override
167                 public void onPackageSizeChanged(String packageName) {
168                     updateAppsBypassingDndSummaryText();
169                 }
170 
171                 @Override
172                 public void onAllSizesComputed() { }
173 
174                 @Override
175                 public void onLauncherInfoChanged() { }
176 
177                 @Override
178                 public void onLoadEntriesCompleted() {
179                     updateAppsBypassingDndSummaryText();
180                 }
181             };
182 }
183