• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.notification.app;
18 
19 import static android.app.NotificationManager.IMPORTANCE_NONE;
20 
21 import android.app.NotificationChannel;
22 import android.app.NotificationChannelGroup;
23 import android.app.settings.SettingsEnums;
24 import android.content.Context;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.os.UserHandle;
28 import android.provider.Settings;
29 
30 import androidx.core.text.BidiFormatter;
31 import androidx.lifecycle.LifecycleObserver;
32 import androidx.preference.Preference;
33 import androidx.preference.PreferenceCategory;
34 import androidx.preference.PreferenceScreen;
35 import androidx.preference.SwitchPreference;
36 
37 import com.android.settings.R;
38 import com.android.settings.applications.AppInfoBase;
39 import com.android.settings.core.PreferenceControllerMixin;
40 import com.android.settings.core.SubSettingLauncher;
41 import com.android.settings.notification.NotificationBackend;
42 import com.android.settingslib.PrimarySwitchPreference;
43 import com.android.settingslib.RestrictedSwitchPreference;
44 
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.List;
48 
49 /**
50  * Populates the PreferenceCategory with notification channels associated with the given app.
51  * Users can allow/disallow notification channels from bypassing DND on a single settings
52  * page.
53  */
54 public class AppChannelsBypassingDndPreferenceController extends NotificationPreferenceController
55         implements PreferenceControllerMixin, LifecycleObserver {
56 
57     private static final String KEY = "zen_mode_bypassing_app_channels_list";
58     private static final String ARG_FROM_SETTINGS = "fromSettings";
59 
60     private RestrictedSwitchPreference mAllNotificationsToggle;
61     private PreferenceCategory mPreferenceCategory;
62     private List<NotificationChannel> mChannels = new ArrayList<>();
63 
AppChannelsBypassingDndPreferenceController( Context context, NotificationBackend backend)64     public AppChannelsBypassingDndPreferenceController(
65             Context context,
66             NotificationBackend backend) {
67         super(context, backend);
68     }
69 
70     @Override
displayPreference(PreferenceScreen screen)71     public void displayPreference(PreferenceScreen screen) {
72         mPreferenceCategory = screen.findPreference(KEY);
73 
74         mAllNotificationsToggle = new RestrictedSwitchPreference(mPreferenceCategory.getContext());
75         mAllNotificationsToggle.setTitle(R.string.zen_mode_bypassing_app_channels_toggle_all);
76         mAllNotificationsToggle.setDisabledByAdmin(mAdmin);
77         mAllNotificationsToggle.setEnabled(
78                 (mAdmin == null || !mAllNotificationsToggle.isDisabledByAdmin()));
79         mAllNotificationsToggle.setOnPreferenceClickListener(
80                 new Preference.OnPreferenceClickListener() {
81                     @Override
82                     public boolean onPreferenceClick(Preference pref) {
83                         SwitchPreference preference = (SwitchPreference) pref;
84                         final boolean bypassDnd = preference.isChecked();
85                         for (NotificationChannel channel : mChannels) {
86                             if (showNotification(channel) && isChannelConfigurable(channel)) {
87                                 channel.setBypassDnd(bypassDnd);
88                                 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
89                                 mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, channel);
90                             }
91                         }
92                         // the 0th index is the mAllNotificationsToggle which allows users to
93                         // toggle all notifications from this app to bypass DND
94                         for (int i = 1; i < mPreferenceCategory.getPreferenceCount(); i++) {
95                             PrimarySwitchPreference childPreference =
96                                     (PrimarySwitchPreference) mPreferenceCategory.getPreference(i);
97                             childPreference.setChecked(showNotificationInDnd(mChannels.get(i - 1)));
98                         }
99                         return true;
100                     }
101                 });
102 
103         loadAppChannels();
104         super.displayPreference(screen);
105     }
106 
107     @Override
getPreferenceKey()108     public String getPreferenceKey() {
109         return KEY;
110     }
111 
112     @Override
isAvailable()113     public boolean isAvailable() {
114         return mAppRow != null;
115     }
116 
117     @Override
isIncludedInFilter()118     boolean isIncludedInFilter() {
119         return false;
120     }
121 
122     @Override
updateState(Preference preference)123     public void updateState(Preference preference) {
124         if (mAppRow != null) {
125             loadAppChannels();
126         }
127     }
128 
loadAppChannels()129     private void loadAppChannels() {
130         // Load channel settings
131         new AsyncTask<Void, Void, Void>() {
132             @Override
133             protected Void doInBackground(Void... unused) {
134                 List<NotificationChannel> newChannelList = new ArrayList<>();
135                 List<NotificationChannelGroup> mChannelGroupList = mBackend.getGroups(mAppRow.pkg,
136                         mAppRow.uid).getList();
137                 for (NotificationChannelGroup channelGroup : mChannelGroupList) {
138                     for (NotificationChannel channel : channelGroup.getChannels()) {
139                         if (!isConversation(channel)) {
140                             newChannelList.add(channel);
141                         }
142                     }
143                 }
144                 Collections.sort(newChannelList, CHANNEL_COMPARATOR);
145                 mChannels = newChannelList;
146                 return null;
147             }
148 
149             @Override
150             protected void onPostExecute(Void unused) {
151                 if (mContext == null) {
152                     return;
153                 }
154                 populateList();
155             }
156         }.execute();
157     }
158 
populateList()159     private void populateList() {
160         if (mPreferenceCategory == null) {
161             return;
162         }
163 
164         mPreferenceCategory.removeAll();
165         mPreferenceCategory.addPreference(mAllNotificationsToggle);
166         for (NotificationChannel channel : mChannels) {
167             PrimarySwitchPreference channelPreference = new PrimarySwitchPreference(mContext);
168             channelPreference.setDisabledByAdmin(mAdmin);
169             channelPreference.setSwitchEnabled(
170                     (mAdmin == null || !channelPreference.isDisabledByAdmin())
171                             && isChannelConfigurable(channel)
172                             && showNotification(channel));
173             channelPreference.setTitle(BidiFormatter.getInstance().unicodeWrap(channel.getName()));
174             channelPreference.setChecked(showNotificationInDnd(channel));
175             channelPreference.setOnPreferenceChangeListener(
176                     new Preference.OnPreferenceChangeListener() {
177                         @Override
178                         public boolean onPreferenceChange(Preference pref, Object val) {
179                             boolean switchOn = (Boolean) val;
180                             channel.setBypassDnd(switchOn);
181                             channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
182                             mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, channel);
183                             mAllNotificationsToggle.setChecked(areAllChannelsBypassing());
184                             return true;
185                         }
186                     });
187 
188             Bundle channelArgs = new Bundle();
189             channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid);
190             channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg);
191             channelArgs.putString(Settings.EXTRA_CHANNEL_ID, channel.getId());
192             channelArgs.putBoolean(ARG_FROM_SETTINGS, true);
193             channelPreference.setOnPreferenceClickListener(preference -> {
194                 new SubSettingLauncher(mContext)
195                         .setDestination(ChannelNotificationSettings.class.getName())
196                         .setArguments(channelArgs)
197                         .setUserHandle(UserHandle.of(mAppRow.userId))
198                         .setTitleRes(com.android.settings.R.string.notification_channel_title)
199                         .setSourceMetricsCategory(SettingsEnums.DND_APPS_BYPASSING)
200                         .launch();
201                 return true;
202             });
203             mPreferenceCategory.addPreference(channelPreference);
204         }
205         mAllNotificationsToggle.setChecked(areAllChannelsBypassing());
206     }
207 
areAllChannelsBypassing()208     private boolean areAllChannelsBypassing() {
209         boolean allChannelsBypassing = true;
210         for (NotificationChannel channel : mChannels) {
211             if (showNotification(channel)) {
212                 allChannelsBypassing &= showNotificationInDnd(channel);
213             }
214         }
215         return allChannelsBypassing;
216     }
217 
218     /**
219      * Whether notifications from this channel would show if DND were on.
220      */
showNotificationInDnd(NotificationChannel channel)221     private boolean showNotificationInDnd(NotificationChannel channel) {
222         return channel.canBypassDnd() && showNotification(channel);
223     }
224 
225     /**
226      * Whether notifications from this channel would show if DND weren't on.
227      */
showNotification(NotificationChannel channel)228     private boolean showNotification(NotificationChannel channel) {
229         return channel.getImportance() != IMPORTANCE_NONE;
230     }
231 
232     /**
233      * Whether this notification channel is representing a conversation.
234      */
isConversation(NotificationChannel channel)235     private boolean isConversation(NotificationChannel channel) {
236         return channel.getConversationId() != null && !channel.isDemoted();
237     }
238 }
239