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; 18 19 import static android.app.NotificationChannel.DEFAULT_ALLOW_BUBBLE; 20 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; 21 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; 22 import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED; 23 24 import android.app.NotificationChannel; 25 import android.app.NotificationChannelGroup; 26 import android.content.Context; 27 import android.content.pm.ShortcutInfo; 28 import android.graphics.drawable.Drawable; 29 import android.service.notification.ConversationChannelWrapper; 30 import android.view.View; 31 import android.widget.ImageView; 32 33 import androidx.annotation.Nullable; 34 import androidx.preference.Preference; 35 import androidx.preference.PreferenceViewHolder; 36 37 import com.android.settings.R; 38 import com.android.settings.notification.app.AppConversationListPreferenceController; 39 import com.android.settingslib.RestrictedLockUtils; 40 41 import com.google.common.annotations.VisibleForTesting; 42 43 import java.util.List; 44 import java.util.stream.Collectors; 45 46 /** 47 * Displays a list of conversations that have either been selected or excluded from bubbling. 48 */ 49 public class AppBubbleListPreferenceController extends AppConversationListPreferenceController { 50 51 private static final String KEY = "bubble_conversations"; 52 AppBubbleListPreferenceController(Context context, NotificationBackend backend)53 public AppBubbleListPreferenceController(Context context, NotificationBackend backend) { 54 super(context, backend); 55 } 56 57 @Override updateState(Preference preference)58 public void updateState(Preference preference) { 59 // loading convos is async; hide header until we know we have conversations to show 60 preference.setVisible(false); 61 super.updateState(preference); 62 } 63 64 @Override onResume(NotificationBackend.AppRow appRow, @Nullable NotificationChannel channel, @Nullable NotificationChannelGroup group, Drawable conversationDrawable, ShortcutInfo conversationInfo, RestrictedLockUtils.EnforcedAdmin admin, List<String> preferenceFilter)65 public void onResume(NotificationBackend.AppRow appRow, 66 @Nullable NotificationChannel channel, @Nullable NotificationChannelGroup group, 67 Drawable conversationDrawable, 68 ShortcutInfo conversationInfo, 69 RestrictedLockUtils.EnforcedAdmin admin, 70 List<String> preferenceFilter) { 71 super.onResume(appRow, channel, group, conversationDrawable, conversationInfo, admin, 72 preferenceFilter); 73 // In case something changed in the foreground (e.g. via bubble button on notification) 74 loadConversationsAndPopulate(); 75 } 76 77 @Override getPreferenceKey()78 public String getPreferenceKey() { 79 return KEY; 80 } 81 82 @Override isAvailable()83 public boolean isAvailable() { 84 // copy rather than inherit super's isAvailable because apps can link to this page 85 // as part of onboarding, before they send a valid conversation notification 86 if (mAppRow == null) { 87 return false; 88 } 89 if (mAppRow.banned) { 90 return false; 91 } 92 if (mChannel != null) { 93 if (mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid) 94 || NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) { 95 return false; 96 } 97 } 98 if (mAppRow.bubblePreference == BUBBLE_PREFERENCE_NONE) { 99 return false; 100 } 101 return true; 102 } 103 104 @VisibleForTesting 105 @Override filterAndSortConversations( List<ConversationChannelWrapper> conversations)106 public List<ConversationChannelWrapper> filterAndSortConversations( 107 List<ConversationChannelWrapper> conversations) { 108 return conversations.stream() 109 .sorted(mConversationComparator) 110 .filter((c) -> { 111 if (mAppRow.bubblePreference == BUBBLE_PREFERENCE_SELECTED) { 112 return c.getNotificationChannel().canBubble(); 113 } else if (mAppRow.bubblePreference == BUBBLE_PREFERENCE_ALL) { 114 return c.getNotificationChannel().getAllowBubbles() == 0; 115 } 116 return false; 117 }) 118 .collect(Collectors.toList()); 119 } 120 121 @Override 122 protected int getTitleResId() { 123 // TODO: possible to left align like mocks? 124 return mAppRow.bubblePreference == BUBBLE_PREFERENCE_SELECTED 125 ? R.string.bubble_app_setting_selected_conversation_title 126 : R.string.bubble_app_setting_excluded_conversation_title; 127 } 128 129 @VisibleForTesting 130 @Override 131 public Preference createConversationPref(final ConversationChannelWrapper conversation) { 132 final ConversationPreference pref = new ConversationPreference(mContext); 133 populateConversationPreference(conversation, pref); 134 pref.setOnClickBubblesConversation(mAppRow.bubblePreference == BUBBLE_PREFERENCE_ALL); 135 pref.setOnClickListener((v) -> { 136 conversation.getNotificationChannel().setAllowBubbles(DEFAULT_ALLOW_BUBBLE); 137 mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, conversation.getNotificationChannel()); 138 mPreference.removePreference(pref); 139 if (mPreference.getPreferenceCount() == 0) { 140 mPreference.setVisible(false); 141 } 142 }); 143 return pref; 144 } 145 146 @Override 147 protected void populateList() { 148 super.populateList(); 149 if (mPreference == null) { 150 return; 151 } 152 mPreference.setVisible(mPreference.getPreferenceCount() > 0); 153 } 154 155 /** Simple preference with a 'x' button at the end. */ 156 @VisibleForTesting 157 public static class ConversationPreference extends Preference implements View.OnClickListener { 158 159 View.OnClickListener mOnClickListener; 160 boolean mOnClickBubbles; 161 162 ConversationPreference(Context context) { 163 super(context); 164 setWidgetLayoutResource(R.layout.bubble_conversation_remove_button); 165 } 166 167 @Override 168 public void onBindViewHolder(final PreferenceViewHolder holder) { 169 super.onBindViewHolder(holder); 170 ImageView view = holder.itemView.findViewById(R.id.button); 171 view.setContentDescription(mOnClickBubbles 172 ? getContext().getString(R.string.bubble_app_setting_bubble_conversation) 173 : getContext().getString(R.string.bubble_app_setting_unbubble_conversation)); 174 view.setOnClickListener(mOnClickListener); 175 } 176 177 public void setOnClickBubblesConversation(boolean enablesBubbles) { 178 mOnClickBubbles = enablesBubbles; 179 } 180 181 public void setOnClickListener(View.OnClickListener listener) { 182 mOnClickListener = listener; 183 } 184 185 @Override 186 public void onClick(View v) { 187 if (mOnClickListener != null) { 188 mOnClickListener.onClick(v); 189 } 190 } 191 } 192 } 193