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