diff --git a/res/values/strings.xml b/res/values/strings.xml index 27f04a7d5d0..0a9f00da96d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8296,6 +8296,21 @@ Add to the conversation section + + Manage conversations + + + Important conversations + + + All conversations + + + Bubble important conversations + + + Important conversations show at the top of the pull-down shade. You can also set them to bubble and interrupt Do Not Disturb. + diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml index 419270a9529..f0977e85dd8 100644 --- a/res/xml/configure_notification_settings.xml +++ b/res/xml/configure_notification_settings.xml @@ -49,6 +49,16 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index d6f89863b88..138930beb0d 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -261,6 +261,15 @@ public class NotificationBackend { } } + public ParceledListSlice getConversations(boolean onlyImportant) { + try { + return sINM.getConversations(onlyImportant); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return ParceledListSlice.emptyList(); + } + } + /** * Returns all notification channels associated with the package and uid that will bypass DND */ @@ -514,6 +523,9 @@ public class NotificationBackend { public Drawable getConversationDrawable(Context context, ShortcutInfo info, String pkg, int uid) { + if (info == null) { + return null; + } ConversationIconFactory iconFactory = new ConversationIconFactory(context, context.getSystemService(LauncherApps.class), context.getPackageManager(), IconDrawableFactory.newInstance(context), diff --git a/src/com/android/settings/notification/app/AllConversationsPreferenceController.java b/src/com/android/settings/notification/app/AllConversationsPreferenceController.java new file mode 100644 index 00000000000..1a139a289a4 --- /dev/null +++ b/src/com/android/settings/notification/app/AllConversationsPreferenceController.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 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.app; + +import android.content.Context; +import android.os.AsyncTask; +import android.service.notification.ConversationChannelWrapper; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + +import com.android.settings.notification.NotificationBackend; + +import java.util.Collections; +import java.util.List; + +public class AllConversationsPreferenceController extends ConversationListPreferenceController { + + private static final String KEY = "all_conversations"; + + private List mConversations; + + public AllConversationsPreferenceController(Context context, + NotificationBackend backend) { + super(context, backend); + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void updateState(Preference preference) { + PreferenceCategory pref = (PreferenceCategory) preference; + // Load conversations + new AsyncTask() { + @Override + protected Void doInBackground(Void... unused) { + mConversations = mBackend.getConversations(false).getList(); + Collections.sort(mConversations, mConversationComparator); + return null; + } + + @Override + protected void onPostExecute(Void unused) { + if (mContext == null) { + return; + } + populateList(mConversations, pref, pref); + } + }.execute(); + } +} diff --git a/src/com/android/settings/notification/app/AllowSoundPreferenceController.java b/src/com/android/settings/notification/app/AllowSoundPreferenceController.java index 0439aabc6f5..cf6db877d23 100644 --- a/src/com/android/settings/notification/app/AllowSoundPreferenceController.java +++ b/src/com/android/settings/notification/app/AllowSoundPreferenceController.java @@ -35,13 +35,13 @@ public class AllowSoundPreferenceController extends NotificationPreferenceContro private static final String TAG = "AllowSoundPrefContr"; private static final String KEY_IMPORTANCE = "allow_sound"; - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; public AllowSoundPreferenceController(Context context, - NotificationSettings.ImportanceListener importanceListener, + NotificationSettings.DependentFieldListener dependentFieldListener, NotificationBackend backend) { super(context, backend); - mImportanceListener = importanceListener; + mDependentFieldListener = dependentFieldListener; } @Override @@ -77,7 +77,7 @@ public class AllowSoundPreferenceController extends NotificationPreferenceContro mChannel.setImportance(importance); mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); saveChannel(); - mImportanceListener.onImportanceChanged(); + mDependentFieldListener.onFieldValueChanged(); } return true; } diff --git a/src/com/android/settings/notification/app/AppConversationListPreferenceController.java b/src/com/android/settings/notification/app/AppConversationListPreferenceController.java new file mode 100644 index 00000000000..b36c2940240 --- /dev/null +++ b/src/com/android/settings/notification/app/AppConversationListPreferenceController.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2020 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.app; + +import android.app.NotificationChannel; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.pm.ShortcutInfo; +import android.os.AsyncTask; +import android.os.Bundle; +import android.provider.Settings; +import android.service.notification.ConversationChannelWrapper; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + +import com.android.settings.R; +import com.android.settings.applications.AppInfoBase; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.notification.NotificationBackend; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class AppConversationListPreferenceController extends NotificationPreferenceController { + + private static final String KEY = "conversations"; + public static final String ARG_FROM_SETTINGS = "fromSettings"; + + private List mConversations; + private PreferenceCategory mPreference; + + public AppConversationListPreferenceController(Context context, NotificationBackend backend) { + super(context, backend); + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public boolean isAvailable() { + if (mAppRow == null) { + return false; + } + if (mAppRow.banned) { + return false; + } + if (mChannel != null) { + if (mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid) + || NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) { + return false; + } + } + return true; + } + + @Override + public void updateState(Preference preference) { + mPreference = (PreferenceCategory) preference; + // Load channel settings + new AsyncTask() { + @Override + protected Void doInBackground(Void... unused) { + mConversations = mBackend.getConversations(mAppRow.pkg, mAppRow.uid).getList(); + Collections.sort(mConversations, mConversationComparator); + return null; + } + + @Override + protected void onPostExecute(Void unused) { + if (mContext == null) { + return; + } + populateList(); + } + }.execute(); + } + + private void populateList() { + // TODO: if preference has children, compare with newly loaded list + mPreference.removeAll(); + mPreference.setTitle(R.string.conversations_category_title); + + if (mConversations.isEmpty()) { + mPreference.setVisible(false); + } else { + mPreference.setVisible(true); + populateConversations(); + } + } + + private void populateConversations() { + for (ConversationChannelWrapper conversation : mConversations) { + if (conversation.getNotificationChannel().isDemoted()) { + continue; + } + mPreference.addPreference(createConversationPref(conversation)); + } + } + + protected Preference createConversationPref(final ConversationChannelWrapper conversation) { + Preference pref = new Preference(mContext); + ShortcutInfo si = conversation.getShortcutInfo(); + + pref.setTitle(si != null + ? si.getShortLabel() + : conversation.getNotificationChannel().getName()); + pref.setSummary(conversation.getNotificationChannel().getGroup() != null + ? mContext.getString(R.string.notification_conversation_summary, + conversation.getParentChannelLabel(), conversation.getGroupLabel()) + : conversation.getParentChannelLabel()); + if (si != null) { + pref.setIcon(mBackend.getConversationDrawable(mContext, si, mAppRow.pkg, mAppRow.uid)); + } + pref.setKey(conversation.getNotificationChannel().getId()); + + Bundle channelArgs = new Bundle(); + channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid); + channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg); + channelArgs.putString(Settings.EXTRA_CHANNEL_ID, + conversation.getNotificationChannel().getParentChannelId()); + channelArgs.putString(Settings.EXTRA_CONVERSATION_ID, + conversation.getNotificationChannel().getConversationId()); + channelArgs.putBoolean(ARG_FROM_SETTINGS, true); + pref.setIntent(new SubSettingLauncher(mContext) + .setDestination(ChannelNotificationSettings.class.getName()) + .setArguments(channelArgs) + .setExtras(channelArgs) + .setTitleText(pref.getTitle()) + .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_APP_NOTIFICATION) + .toIntent()); + return pref; + } + + protected Comparator mConversationComparator = + (left, right) -> { + if (left.getNotificationChannel().isImportantConversation() + != right.getNotificationChannel().isImportantConversation()) { + // important first + return Boolean.compare(right.getNotificationChannel().isImportantConversation(), + left.getNotificationChannel().isImportantConversation()); + } + return left.getNotificationChannel().getId().compareTo( + right.getNotificationChannel().getId()); + }; +} diff --git a/src/com/android/settings/notification/app/AppNotificationSettings.java b/src/com/android/settings/notification/app/AppNotificationSettings.java index e6ccd8915d8..d9f42393597 100644 --- a/src/com/android/settings/notification/app/AppNotificationSettings.java +++ b/src/com/android/settings/notification/app/AppNotificationSettings.java @@ -100,18 +100,18 @@ public class AppNotificationSettings extends NotificationSettings { protected List createPreferenceControllers(Context context) { mControllers = new ArrayList<>(); mControllers.add(new HeaderPreferenceController(context, this)); - mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend)); + mControllers.add(new BlockPreferenceController(context, mDependentFieldListener, mBackend)); mControllers.add(new BadgePreferenceController(context, mBackend)); mControllers.add(new AllowSoundPreferenceController( - context, mImportanceListener, mBackend)); + context, mDependentFieldListener, mBackend)); mControllers.add(new ImportancePreferenceController( - context, mImportanceListener, mBackend)); + context, mDependentFieldListener, mBackend)); mControllers.add(new MinImportancePreferenceController( - context, mImportanceListener, mBackend)); + context, mDependentFieldListener, mBackend)); mControllers.add(new HighImportancePreferenceController( - context, mImportanceListener, mBackend)); + context, mDependentFieldListener, mBackend)); mControllers.add(new SoundPreferenceController(context, this, - mImportanceListener, mBackend)); + mDependentFieldListener, mBackend)); mControllers.add(new LightsPreferenceController(context, mBackend)); mControllers.add(new VibrationPreferenceController(context, mBackend)); mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context), @@ -123,7 +123,7 @@ public class AppNotificationSettings extends NotificationSettings { mControllers.add(new DeletedChannelsPreferenceController(context, mBackend)); mControllers.add(new BubbleSummaryPreferenceController(context, mBackend)); mControllers.add(new ChannelListPreferenceController(context, mBackend)); - mControllers.add(new ConversationListPreferenceController(context, mBackend)); + mControllers.add(new AppConversationListPreferenceController(context, mBackend)); return new ArrayList<>(mControllers); } } diff --git a/src/com/android/settings/notification/app/BlockPreferenceController.java b/src/com/android/settings/notification/app/BlockPreferenceController.java index 7f90979b15d..37563c0905a 100644 --- a/src/com/android/settings/notification/app/BlockPreferenceController.java +++ b/src/com/android/settings/notification/app/BlockPreferenceController.java @@ -36,13 +36,13 @@ public class BlockPreferenceController extends NotificationPreferenceController implements PreferenceControllerMixin, SwitchBar.OnSwitchChangeListener { private static final String KEY_BLOCK = "block"; - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; public BlockPreferenceController(Context context, - NotificationSettings.ImportanceListener importanceListener, + NotificationSettings.DependentFieldListener dependentFieldListener, NotificationBackend backend) { super(context, backend); - mImportanceListener = importanceListener; + mDependentFieldListener = dependentFieldListener; } @Override @@ -124,7 +124,7 @@ public class BlockPreferenceController extends NotificationPreferenceController mAppRow.banned = blocked; mBackend.setNotificationsEnabledForPackage(mAppRow.pkg, mAppRow.uid, !blocked); } - mImportanceListener.onImportanceChanged(); + mDependentFieldListener.onFieldValueChanged(); } String getSwitchBarText() { diff --git a/src/com/android/settings/notification/app/ChannelNotificationSettings.java b/src/com/android/settings/notification/app/ChannelNotificationSettings.java index b5212c94c8c..9d80304ba29 100644 --- a/src/com/android/settings/notification/app/ChannelNotificationSettings.java +++ b/src/com/android/settings/notification/app/ChannelNotificationSettings.java @@ -109,17 +109,17 @@ public class ChannelNotificationSettings extends NotificationSettings { protected List createPreferenceControllers(Context context) { mControllers = new ArrayList<>(); mControllers.add(new HeaderPreferenceController(context, this)); - mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend)); + mControllers.add(new BlockPreferenceController(context, mDependentFieldListener, mBackend)); mControllers.add(new ImportancePreferenceController( - context, mImportanceListener, mBackend)); + context, mDependentFieldListener, mBackend)); mControllers.add(new MinImportancePreferenceController( - context, mImportanceListener, mBackend)); + context, mDependentFieldListener, mBackend)); mControllers.add(new HighImportancePreferenceController( - context, mImportanceListener, mBackend)); + context, mDependentFieldListener, mBackend)); mControllers.add(new AllowSoundPreferenceController( - context, mImportanceListener, mBackend)); + context, mDependentFieldListener, mBackend)); mControllers.add(new SoundPreferenceController(context, this, - mImportanceListener, mBackend)); + mDependentFieldListener, mBackend)); mControllers.add(new VibrationPreferenceController(context, mBackend)); mControllers.add(new AppLinkPreferenceController(context)); mControllers.add(new DescriptionPreferenceController(context)); diff --git a/src/com/android/settings/notification/app/ConversationImportantPreferenceController.java b/src/com/android/settings/notification/app/ConversationImportantPreferenceController.java index 71fa23158fe..35e1f7d3a3f 100644 --- a/src/com/android/settings/notification/app/ConversationImportantPreferenceController.java +++ b/src/com/android/settings/notification/app/ConversationImportantPreferenceController.java @@ -16,7 +16,10 @@ package com.android.settings.notification.app; +import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS; + import android.content.Context; +import android.provider.Settings; import androidx.preference.Preference; @@ -29,10 +32,12 @@ public class ConversationImportantPreferenceController extends NotificationPrefe private static final String TAG = "ConvoImpPC"; private static final String KEY = "important"; + private final NotificationSettings.DependentFieldListener mDependentFieldListener; public ConversationImportantPreferenceController(Context context, - NotificationBackend backend) { + NotificationBackend backend, NotificationSettings.DependentFieldListener listener) { super(context, backend); + mDependentFieldListener = listener; } @Override @@ -67,8 +72,17 @@ public class ConversationImportantPreferenceController extends NotificationPrefe } final boolean value = (Boolean) newValue; mChannel.setImportantConversation(value); + if (value && bubbleImportantConversations()) { + mChannel.setAllowBubbles(true); + mDependentFieldListener.onFieldValueChanged(); + } saveChannel(); return true; } + + private boolean bubbleImportantConversations() { + return Settings.Secure.getInt(mContext.getContentResolver(), + BUBBLE_IMPORTANT_CONVERSATIONS, 1) == 1; + } } diff --git a/src/com/android/settings/notification/app/ConversationListPreferenceController.java b/src/com/android/settings/notification/app/ConversationListPreferenceController.java index dba70869017..79ed28cbdb8 100644 --- a/src/com/android/settings/notification/app/ConversationListPreferenceController.java +++ b/src/com/android/settings/notification/app/ConversationListPreferenceController.java @@ -16,37 +16,38 @@ package com.android.settings.notification.app; -import android.app.NotificationChannel; import android.app.settings.SettingsEnums; import android.content.Context; +import android.content.Intent; import android.content.pm.ShortcutInfo; -import android.os.AsyncTask; import android.os.Bundle; import android.provider.Settings; import android.service.notification.ConversationChannelWrapper; +import android.text.TextUtils; import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; import com.android.settings.R; import com.android.settings.applications.AppInfoBase; import com.android.settings.core.SubSettingLauncher; import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.core.AbstractPreferenceController; -import java.util.Collections; +import java.text.Collator; import java.util.Comparator; import java.util.List; -public class ConversationListPreferenceController extends NotificationPreferenceController { +public abstract class ConversationListPreferenceController extends AbstractPreferenceController { - private static final String KEY = "conversations"; - public static final String ARG_FROM_SETTINGS = "fromSettings"; + private static final String KEY = "all_conversations"; - private List mConversations; - private PreferenceCategory mPreference; + protected final NotificationBackend mBackend; - public ConversationListPreferenceController(Context context, NotificationBackend backend) { - super(context, backend); + public ConversationListPreferenceController(Context context, + NotificationBackend backend) { + super(context); + mBackend = backend; } @Override @@ -56,108 +57,93 @@ public class ConversationListPreferenceController extends NotificationPreference @Override public boolean isAvailable() { - if (mAppRow == null) { - return false; - } - if (mAppRow.banned) { - return false; - } - if (mChannel != null) { - if (mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid) - || NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) { - return false; - } - } return true; } - @Override - public void updateState(Preference preference) { - mPreference = (PreferenceCategory) preference; - // Load channel settings - new AsyncTask() { - @Override - protected Void doInBackground(Void... unused) { - mConversations = mBackend.getConversations(mAppRow.pkg, mAppRow.uid).getList(); - Collections.sort(mConversations, mConversationComparator); - return null; - } - - @Override - protected void onPostExecute(Void unused) { - if (mContext == null) { - return; - } - populateList(); - } - }.execute(); - } - - private void populateList() { + protected void populateList(List conversations, + PreferenceGroup outerContainer, PreferenceGroup innerContainer) { // TODO: if preference has children, compare with newly loaded list - mPreference.removeAll(); - mPreference.setTitle(R.string.conversations_category_title); - - if (mConversations.isEmpty()) { - mPreference.setVisible(false); + if (conversations.isEmpty()) { + outerContainer.setVisible(false); } else { - mPreference.setVisible(true); - populateConversations(); + outerContainer.setVisible(true); + populateConversations(conversations, innerContainer); } } - private void populateConversations() { - for (ConversationChannelWrapper conversation : mConversations) { + protected void populateConversations(List conversations, + PreferenceGroup containerGroup) { + containerGroup.removeAll(); + for (ConversationChannelWrapper conversation : conversations) { if (conversation.getNotificationChannel().isDemoted()) { continue; } - mPreference.addPreference(createConversationPref(conversation)); + containerGroup.addPreference(createConversationPref(conversation)); } } protected Preference createConversationPref(final ConversationChannelWrapper conversation) { Preference pref = new Preference(mContext); - ShortcutInfo si = conversation.getShortcutInfo(); - pref.setTitle(si != null - ? si.getShortLabel() - : conversation.getNotificationChannel().getName()); - pref.setSummary(conversation.getNotificationChannel().getGroup() != null - ? mContext.getString(R.string.notification_conversation_summary, - conversation.getParentChannelLabel(), conversation.getGroupLabel()) - : conversation.getParentChannelLabel()); - if (si != null) { - pref.setIcon(mBackend.getConversationDrawable(mContext, si, mAppRow.pkg, mAppRow.uid)); - } + pref.setTitle(getTitle(conversation)); + pref.setSummary(getSummary(conversation)); + pref.setIcon(mBackend.getConversationDrawable(mContext, conversation.getShortcutInfo(), + conversation.getPkg(), conversation.getUid())); pref.setKey(conversation.getNotificationChannel().getId()); + pref.setIntent(getIntent(conversation, pref.getTitle())); - Bundle channelArgs = new Bundle(); - channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid); - channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg); - channelArgs.putString(Settings.EXTRA_CHANNEL_ID, - conversation.getNotificationChannel().getParentChannelId()); - channelArgs.putString(Settings.EXTRA_CONVERSATION_ID, - conversation.getNotificationChannel().getConversationId()); - channelArgs.putBoolean(ARG_FROM_SETTINGS, true); - pref.setIntent(new SubSettingLauncher(mContext) - .setDestination(ChannelNotificationSettings.class.getName()) - .setArguments(channelArgs) - .setExtras(channelArgs) - .setTitleText(pref.getTitle()) - .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_APP_NOTIFICATION) - .toIntent()); return pref; } + CharSequence getSummary(ConversationChannelWrapper conversation) { + return TextUtils.isEmpty(conversation.getGroupLabel()) + ? conversation.getParentChannelLabel() + : mContext.getString(R.string.notification_conversation_summary, + conversation.getParentChannelLabel(), conversation.getGroupLabel()); + } + + CharSequence getTitle(ConversationChannelWrapper conversation) { + ShortcutInfo si = conversation.getShortcutInfo(); + return si != null + ? si.getShortLabel() + : conversation.getNotificationChannel().getName(); + } + + Intent getIntent(ConversationChannelWrapper conversation, CharSequence title) { + Bundle channelArgs = new Bundle(); + channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, conversation.getUid()); + channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, conversation.getPkg()); + channelArgs.putString(Settings.EXTRA_CHANNEL_ID, + conversation.getNotificationChannel().getId()); + channelArgs.putString(Settings.EXTRA_CONVERSATION_ID, + conversation.getNotificationChannel().getConversationId()); + + return new SubSettingLauncher(mContext) + .setDestination(ChannelNotificationSettings.class.getName()) + .setArguments(channelArgs) + .setExtras(channelArgs) + .setTitleText(title) + .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_CONVERSATION_LIST_SETTINGS) + .toIntent(); + } + protected Comparator mConversationComparator = - (left, right) -> { - if (left.getNotificationChannel().isImportantConversation() - != right.getNotificationChannel().isImportantConversation()) { - // important first - return Boolean.compare(right.getNotificationChannel().isImportantConversation(), - left.getNotificationChannel().isImportantConversation()); + new Comparator() { + private final Collator sCollator = Collator.getInstance(); + @Override + public int compare(ConversationChannelWrapper o1, ConversationChannelWrapper o2) { + if (o1.getShortcutInfo() != null && o2.getShortcutInfo() == null) { + return -1; + } + if (o1.getShortcutInfo() == null && o2.getShortcutInfo() != null) { + return 1; + } + if (o1.getShortcutInfo() == null && o2.getShortcutInfo() == null) { + return o1.getNotificationChannel().getId().compareTo( + o2.getNotificationChannel().getId()); + } + return sCollator.compare(o1.getShortcutInfo().getShortLabel(), + o2.getShortcutInfo().getShortLabel()); } - return left.getNotificationChannel().getId().compareTo( - right.getNotificationChannel().getId()); }; } diff --git a/src/com/android/settings/notification/app/ConversationListSettings.java b/src/com/android/settings/notification/app/ConversationListSettings.java new file mode 100644 index 00000000000..aaeaa950c8b --- /dev/null +++ b/src/com/android/settings/notification/app/ConversationListSettings.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 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.app; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; + +import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.core.AbstractPreferenceController; + +import java.util.ArrayList; +import java.util.List; + +public class ConversationListSettings extends DashboardFragment { + private static final String TAG = "ConvoListSettings"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + NotificationBackend mBackend = new NotificationBackend(); + protected List mControllers = new ArrayList<>(); + + @Override + public int getMetricsCategory() { + return SettingsEnums.NOTIFICATION_CONVERSATION_LIST_SETTINGS; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.conversation_list_settings; + } + + @Override + protected List createPreferenceControllers(Context context) { + mControllers = new ArrayList<>(); + mControllers.add(new ImportantConversationsPreferenceController(context, mBackend)); + mControllers.add(new AllConversationsPreferenceController(context, mBackend)); + return new ArrayList<>(mControllers); + } +} diff --git a/src/com/android/settings/notification/app/ConversationNotificationSettings.java b/src/com/android/settings/notification/app/ConversationNotificationSettings.java index 3f250378a1a..c5a8e6ee100 100644 --- a/src/com/android/settings/notification/app/ConversationNotificationSettings.java +++ b/src/com/android/settings/notification/app/ConversationNotificationSettings.java @@ -79,14 +79,15 @@ public class ConversationNotificationSettings extends NotificationSettings { protected List createPreferenceControllers(Context context) { mControllers = new ArrayList<>(); mControllers.add(new ConversationHeaderPreferenceController(context, this)); - mControllers.add(new ConversationImportantPreferenceController(context, mBackend)); + mControllers.add(new ConversationImportantPreferenceController( + context, mBackend, mDependentFieldListener)); mControllers.add(new DefaultImportancePreferenceController( - context, mImportanceListener, mBackend)); + context, mDependentFieldListener, mBackend)); mControllers.add(new AddToHomeScreenPreferenceController(context, mBackend)); mControllers.add(new HighImportancePreferenceController( - context, mImportanceListener, mBackend)); + context, mDependentFieldListener, mBackend)); mControllers.add(new SoundPreferenceController(context, this, - mImportanceListener, mBackend)); + mDependentFieldListener, mBackend)); mControllers.add(new VibrationPreferenceController(context, mBackend)); mControllers.add(new AppLinkPreferenceController(context)); mControllers.add(new DescriptionPreferenceController(context)); diff --git a/src/com/android/settings/notification/app/DefaultImportancePreferenceController.java b/src/com/android/settings/notification/app/DefaultImportancePreferenceController.java index e6641019835..616d64a3b46 100644 --- a/src/com/android/settings/notification/app/DefaultImportancePreferenceController.java +++ b/src/com/android/settings/notification/app/DefaultImportancePreferenceController.java @@ -32,13 +32,13 @@ public class DefaultImportancePreferenceController extends NotificationPreferenc implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { private static final String KEY = "alerting"; - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; public DefaultImportancePreferenceController(Context context, - NotificationSettings.ImportanceListener importanceListener, + NotificationSettings.DependentFieldListener dependentFieldListener, NotificationBackend backend) { super(context, backend); - mImportanceListener = importanceListener; + mDependentFieldListener = dependentFieldListener; } @Override @@ -78,7 +78,7 @@ public class DefaultImportancePreferenceController extends NotificationPreferenc mChannel.setImportance(checked ? IMPORTANCE_DEFAULT : IMPORTANCE_LOW); mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); saveChannel(); - mImportanceListener.onImportanceChanged(); + mDependentFieldListener.onFieldValueChanged(); } return true; } diff --git a/src/com/android/settings/notification/app/HighImportancePreferenceController.java b/src/com/android/settings/notification/app/HighImportancePreferenceController.java index 16cbf012f81..3aecfa87365 100644 --- a/src/com/android/settings/notification/app/HighImportancePreferenceController.java +++ b/src/com/android/settings/notification/app/HighImportancePreferenceController.java @@ -32,13 +32,13 @@ public class HighImportancePreferenceController extends NotificationPreferenceCo implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { private static final String KEY_IMPORTANCE = "high_importance"; - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; public HighImportancePreferenceController(Context context, - NotificationSettings.ImportanceListener importanceListener, + NotificationSettings.DependentFieldListener dependentFieldListener, NotificationBackend backend) { super(context, backend); - mImportanceListener = importanceListener; + mDependentFieldListener = dependentFieldListener; } @Override @@ -78,7 +78,7 @@ public class HighImportancePreferenceController extends NotificationPreferenceCo mChannel.setImportance(checked ? IMPORTANCE_HIGH : IMPORTANCE_DEFAULT); mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); saveChannel(); - mImportanceListener.onImportanceChanged(); + mDependentFieldListener.onFieldValueChanged(); } return true; } diff --git a/src/com/android/settings/notification/app/ImportancePreferenceController.java b/src/com/android/settings/notification/app/ImportancePreferenceController.java index cf0dc27d671..dc7f29bd537 100644 --- a/src/com/android/settings/notification/app/ImportancePreferenceController.java +++ b/src/com/android/settings/notification/app/ImportancePreferenceController.java @@ -33,13 +33,13 @@ public class ImportancePreferenceController extends NotificationPreferenceContro implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { private static final String KEY_IMPORTANCE = "importance"; - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; public ImportancePreferenceController(Context context, - NotificationSettings.ImportanceListener importanceListener, + NotificationSettings.DependentFieldListener dependentFieldListener, NotificationBackend backend) { super(context, backend); - mImportanceListener = importanceListener; + mDependentFieldListener = dependentFieldListener; } @Override @@ -91,7 +91,7 @@ public class ImportancePreferenceController extends NotificationPreferenceContro mChannel.setImportance(importance); mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); saveChannel(); - mImportanceListener.onImportanceChanged(); + mDependentFieldListener.onFieldValueChanged(); } return true; } diff --git a/src/com/android/settings/notification/app/ImportantConversationBubblePreferenceController.java b/src/com/android/settings/notification/app/ImportantConversationBubblePreferenceController.java new file mode 100644 index 00000000000..f6f14aa1ed8 --- /dev/null +++ b/src/com/android/settings/notification/app/ImportantConversationBubblePreferenceController.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 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.app; + +import static android.provider.Settings.Global.NOTIFICATION_BUBBLES; +import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS; + +import android.content.Context; +import android.provider.Settings; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.TogglePreferenceController; + +public class ImportantConversationBubblePreferenceController extends TogglePreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final String TAG = "ImpConvBubPrefContr"; + @VisibleForTesting + static final int ON = 1; + @VisibleForTesting + static final int OFF = 0; + + + public ImportantConversationBubblePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + private boolean isGloballyEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, OFF) == ON; + } + + @Override + public int getAvailabilityStatus() { + return isGloballyEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING; + } + + @Override + public boolean isSliceable() { + return false; + } + + @Override + public boolean isChecked() { + return Settings.Secure.getInt(mContext.getContentResolver(), + BUBBLE_IMPORTANT_CONVERSATIONS, ON) == ON; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.Secure.putInt(mContext.getContentResolver(), + BUBBLE_IMPORTANT_CONVERSATIONS, isChecked ? ON : OFF); + } +} diff --git a/src/com/android/settings/notification/app/ImportantConversationsPreferenceController.java b/src/com/android/settings/notification/app/ImportantConversationsPreferenceController.java new file mode 100644 index 00000000000..2089d9d3bbe --- /dev/null +++ b/src/com/android/settings/notification/app/ImportantConversationsPreferenceController.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 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.app; + +import android.content.Context; +import android.os.AsyncTask; +import android.service.notification.ConversationChannelWrapper; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + +import com.android.settings.notification.NotificationBackend; + +import java.util.Collections; +import java.util.List; + +public class ImportantConversationsPreferenceController extends + ConversationListPreferenceController { + + private static final String KEY = "important_conversations"; + private static final String LIST_KEY = "important_conversations_list"; + private List mConversations; + + public ImportantConversationsPreferenceController(Context context, + NotificationBackend backend) { + super(context, backend); + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void updateState(Preference preference) { + PreferenceCategory pref = (PreferenceCategory) preference; + // Load conversations + new AsyncTask() { + @Override + protected Void doInBackground(Void... unused) { + mConversations = mBackend.getConversations(true).getList(); + Collections.sort(mConversations, mConversationComparator); + return null; + } + + @Override + protected void onPostExecute(Void unused) { + if (mContext == null) { + return; + } + populateList(mConversations, pref, pref.findPreference(LIST_KEY)); + } + }.execute(); + + } +} diff --git a/src/com/android/settings/notification/app/MinImportancePreferenceController.java b/src/com/android/settings/notification/app/MinImportancePreferenceController.java index 4adb81733bb..69b7dc59e86 100644 --- a/src/com/android/settings/notification/app/MinImportancePreferenceController.java +++ b/src/com/android/settings/notification/app/MinImportancePreferenceController.java @@ -32,13 +32,13 @@ public class MinImportancePreferenceController extends NotificationPreferenceCon implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { private static final String KEY_IMPORTANCE = "min_importance"; - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; public MinImportancePreferenceController(Context context, - NotificationSettings.ImportanceListener importanceListener, + NotificationSettings.DependentFieldListener dependentFieldListener, NotificationBackend backend) { super(context, backend); - mImportanceListener = importanceListener; + mDependentFieldListener = dependentFieldListener; } @Override @@ -78,7 +78,7 @@ public class MinImportancePreferenceController extends NotificationPreferenceCon mChannel.setImportance(checked ? IMPORTANCE_MIN : IMPORTANCE_LOW); mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); saveChannel(); - mImportanceListener.onImportanceChanged(); + mDependentFieldListener.onFieldValueChanged(); } return true; } diff --git a/src/com/android/settings/notification/app/NotificationSettings.java b/src/com/android/settings/notification/app/NotificationSettings.java index e30b35ca6e2..9d5217829ef 100644 --- a/src/com/android/settings/notification/app/NotificationSettings.java +++ b/src/com/android/settings/notification/app/NotificationSettings.java @@ -33,7 +33,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; @@ -79,7 +78,7 @@ abstract public class NotificationSettings extends DashboardFragment { protected boolean mListeningToPackageRemove; protected List mControllers = new ArrayList<>(); - protected ImportanceListener mImportanceListener = new ImportanceListener(); + protected DependentFieldListener mDependentFieldListener = new DependentFieldListener(); protected Intent mIntent; protected Bundle mArgs; @@ -328,8 +327,8 @@ abstract public class NotificationSettings extends DashboardFragment { } }; - protected class ImportanceListener { - protected void onImportanceChanged() { + protected class DependentFieldListener { + protected void onFieldValueChanged() { final PreferenceScreen screen = getPreferenceScreen(); for (NotificationPreferenceController controller : mControllers) { controller.displayPreference(screen); diff --git a/src/com/android/settings/notification/app/SoundPreferenceController.java b/src/com/android/settings/notification/app/SoundPreferenceController.java index 60dcf7f35f7..b3ac9270e66 100644 --- a/src/com/android/settings/notification/app/SoundPreferenceController.java +++ b/src/com/android/settings/notification/app/SoundPreferenceController.java @@ -40,16 +40,16 @@ public class SoundPreferenceController extends NotificationPreferenceController private static final String KEY_SOUND = "ringtone"; private final SettingsPreferenceFragment mFragment; - private final NotificationSettings.ImportanceListener mListener; + private final NotificationSettings.DependentFieldListener mListener; private NotificationSoundPreference mPreference; protected static final int CODE = 200; public SoundPreferenceController(Context context, SettingsPreferenceFragment hostFragment, - NotificationSettings.ImportanceListener importanceListener, + NotificationSettings.DependentFieldListener dependentFieldListener, NotificationBackend backend) { super(context, backend); mFragment = hostFragment; - mListener = importanceListener; + mListener = dependentFieldListener; } @Override @@ -121,7 +121,7 @@ public class SoundPreferenceController extends NotificationPreferenceController } // the importance hasn't changed, but the importance description might as a result of // user's selection. - mListener.onImportanceChanged(); + mListener.onFieldValueChanged(); return true; } return false; diff --git a/tests/robotests/src/com/android/settings/notification/app/AllowSoundPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/AllowSoundPreferenceControllerTest.java index e121cfe4a63..49d0a1d2911 100644 --- a/tests/robotests/src/com/android/settings/notification/app/AllowSoundPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/app/AllowSoundPreferenceControllerTest.java @@ -68,7 +68,7 @@ public class AllowSoundPreferenceControllerTest { private PreferenceScreen mScreen; @Mock - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; private AllowSoundPreferenceController mController; @@ -80,7 +80,7 @@ public class AllowSoundPreferenceControllerTest { shadowApplication.setSystemService(Context.USER_SERVICE, mUm); mContext = RuntimeEnvironment.application; mController = - spy(new AllowSoundPreferenceController(mContext, mImportanceListener, mBackend)); + spy(new AllowSoundPreferenceController(mContext, mDependentFieldListener, mBackend)); } @Test @@ -213,7 +213,7 @@ public class AllowSoundPreferenceControllerTest { mController.onPreferenceChange(pref, true); assertEquals(IMPORTANCE_UNSPECIFIED, mController.mChannel.getImportance()); - verify(mImportanceListener, times(1)).onImportanceChanged(); + verify(mDependentFieldListener, times(1)).onFieldValueChanged(); } @Test @@ -232,6 +232,6 @@ public class AllowSoundPreferenceControllerTest { verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); assertEquals(IMPORTANCE_LOW, mController.mChannel.getImportance()); - verify(mImportanceListener, times(1)).onImportanceChanged(); + verify(mDependentFieldListener, times(1)).onFieldValueChanged(); } } diff --git a/tests/robotests/src/com/android/settings/notification/app/BlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/BlockPreferenceControllerTest.java index e73ee5a3d3a..b83ab77a0c2 100644 --- a/tests/robotests/src/com/android/settings/notification/app/BlockPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/app/BlockPreferenceControllerTest.java @@ -69,7 +69,7 @@ public class BlockPreferenceControllerTest { private UserManager mUm; @Mock - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; private BlockPreferenceController mController; @Mock @@ -83,7 +83,7 @@ public class BlockPreferenceControllerTest { shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); shadowApplication.setSystemService(Context.USER_SERVICE, mUm); mContext = RuntimeEnvironment.application; - mController = spy(new BlockPreferenceController(mContext, mImportanceListener, mBackend)); + mController = spy(new BlockPreferenceController(mContext, mDependentFieldListener, mBackend)); mSwitch = new SwitchBar(mContext); when(mPreference.findViewById(R.id.switch_bar)).thenReturn(mSwitch); } diff --git a/tests/robotests/src/com/android/settings/notification/app/ConversationImportantPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/ConversationImportantPreferenceControllerTest.java index d1dd0a64483..783b40f908e 100644 --- a/tests/robotests/src/com/android/settings/notification/app/ConversationImportantPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/app/ConversationImportantPreferenceControllerTest.java @@ -19,12 +19,14 @@ package com.android.settings.notification.app; import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -34,6 +36,7 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.os.UserManager; +import android.provider.Settings; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -64,6 +67,8 @@ public class ConversationImportantPreferenceControllerTest { private UserManager mUm; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private PreferenceScreen mScreen; + @Mock + private NotificationSettings.DependentFieldListener mDependentFieldListener; private ConversationImportantPreferenceController mController; @@ -74,7 +79,8 @@ public class ConversationImportantPreferenceControllerTest { shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); shadowApplication.setSystemService(Context.USER_SERVICE, mUm); mContext = RuntimeEnvironment.application; - mController = spy(new ConversationImportantPreferenceController(mContext, mBackend)); + mController = spy(new ConversationImportantPreferenceController( + mContext, mBackend, mDependentFieldListener)); } @Test @@ -133,9 +139,12 @@ public class ConversationImportantPreferenceControllerTest { @Test public void testOnPreferenceChange_on() { + Settings.Secure.putInt(mContext.getContentResolver(), + BUBBLE_IMPORTANT_CONVERSATIONS, 0); NotificationChannel channel = new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_DEFAULT); channel.setImportantConversation(false); + channel.setAllowBubbles(false); mController.onResume(new NotificationBackend.AppRow(), channel, null, null, null, null); RestrictedSwitchPreference pref = @@ -145,14 +154,41 @@ public class ConversationImportantPreferenceControllerTest { mController.onPreferenceChange(pref, true); assertTrue(channel.isImportantConversation()); + assertFalse(channel.canBubble()); verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + verify(mDependentFieldListener, never()).onFieldValueChanged(); + } + + @Test + public void testOnPreferenceChange_on_bubble() { + Settings.Secure.putInt(mContext.getContentResolver(), + BUBBLE_IMPORTANT_CONVERSATIONS, 1); + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_DEFAULT); + channel.setImportantConversation(false); + channel.setAllowBubbles(false); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + mController.onPreferenceChange(pref, true); + + assertTrue(channel.isImportantConversation()); + assertTrue(channel.canBubble()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + verify(mDependentFieldListener).onFieldValueChanged(); } @Test public void testOnPreferenceChange_off() { + Settings.Secure.putInt(mContext.getContentResolver(), + BUBBLE_IMPORTANT_CONVERSATIONS, 1); NotificationChannel channel = new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH); channel.setImportantConversation(true); + channel.setAllowBubbles(false); mController.onResume(new NotificationBackend.AppRow(), channel, null, null, null, null); RestrictedSwitchPreference pref = @@ -164,6 +200,8 @@ public class ConversationImportantPreferenceControllerTest { mController.onPreferenceChange(pref, false); assertFalse(channel.isImportantConversation()); + assertFalse(channel.canBubble()); verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + verify(mDependentFieldListener, never()).onFieldValueChanged(); } } diff --git a/tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java new file mode 100644 index 00000000000..0fab4e58c98 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2020 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.app; + +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_MIN; +import static android.app.NotificationManager.IMPORTANCE_NONE; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.os.Bundle; +import android.os.UserManager; +import android.provider.Settings; +import android.service.notification.ConversationChannelWrapper; +import android.view.View; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; + +import com.android.settings.applications.AppInfoBase; +import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.RestrictedLockUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowApplication; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class ConversationListPreferenceControllerTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock + private NotificationBackend mBackend; + + private TestPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + mContext = RuntimeEnvironment.application; + mController = new TestPreferenceController(mContext, mBackend); + } + + @Test + public void isAvailable() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void testPopulateList_hideIfNoConversations() { + PreferenceCategory outerContainer = mock(PreferenceCategory.class); + PreferenceCategory innerContainer = mock(PreferenceCategory.class); + + mController.populateList(new ArrayList<>(), outerContainer, innerContainer); + + verify(outerContainer).setVisible(false); + verify(innerContainer, never()).addPreference(any()); + } + + @Test + public void testPopulateList_validConversations() { + PreferenceCategory outerContainer = mock(PreferenceCategory.class); + PreferenceCategory innerContainer = mock(PreferenceCategory.class); + + ConversationChannelWrapper ccw = new ConversationChannelWrapper(); + ccw.setNotificationChannel(mock(NotificationChannel.class)); + ccw.setPkg("pkg"); + ccw.setUid(1); + ccw.setShortcutInfo(mock(ShortcutInfo.class)); + + ArrayList list = new ArrayList<>(); + list.add(ccw); + + mController.populateList(list, outerContainer, innerContainer); + + verify(outerContainer).setVisible(true); + verify(innerContainer, times(1)).addPreference(any()); + } + + @Test + public void populateConversations() { + PreferenceCategory container = mock(PreferenceCategory.class); + + ConversationChannelWrapper ccw = new ConversationChannelWrapper(); + ccw.setNotificationChannel(mock(NotificationChannel.class)); + ccw.setPkg("pkg"); + ccw.setUid(1); + ccw.setShortcutInfo(mock(ShortcutInfo.class)); + + ConversationChannelWrapper ccwDemoted = new ConversationChannelWrapper(); + NotificationChannel demoted = new NotificationChannel("a", "a", 2); + demoted.setDemoted(true); + ccwDemoted.setNotificationChannel(demoted); + ccwDemoted.setPkg("pkg"); + ccwDemoted.setUid(1); + ccwDemoted.setShortcutInfo(mock(ShortcutInfo.class)); + + ArrayList list = new ArrayList<>(); + list.add(ccw); + list.add(ccwDemoted); + + mController.populateConversations(list, container); + + verify(container, times(1)).addPreference(any()); + } + + @Test + public void getSummary_withGroup() { + ConversationChannelWrapper ccw = new ConversationChannelWrapper(); + NotificationChannel channel = new NotificationChannel("a", "child", 2); + ccw.setNotificationChannel(channel); + ccw.setPkg("pkg"); + ccw.setUid(1); + ccw.setShortcutInfo(mock(ShortcutInfo.class)); + ccw.setGroupLabel("group"); + ccw.setParentChannelLabel("parent"); + + assertThat(mController.getSummary(ccw).toString()).contains(ccw.getGroupLabel()); + assertThat(mController.getSummary(ccw).toString()).contains(ccw.getParentChannelLabel()); + } + + @Test + public void getSummary_noGroup() { + ConversationChannelWrapper ccw = new ConversationChannelWrapper(); + NotificationChannel channel = new NotificationChannel("a", "child", 2); + ccw.setNotificationChannel(channel); + ccw.setPkg("pkg"); + ccw.setUid(1); + ccw.setShortcutInfo(mock(ShortcutInfo.class)); + ccw.setParentChannelLabel("parent"); + + assertThat(mController.getSummary(ccw).toString()).isEqualTo(ccw.getParentChannelLabel()); + } + + @Test + public void getTitle_withShortcut() { + ConversationChannelWrapper ccw = new ConversationChannelWrapper(); + NotificationChannel channel = new NotificationChannel("a", "child", 2); + ccw.setNotificationChannel(channel); + ccw.setPkg("pkg"); + ccw.setUid(1); + ShortcutInfo si = mock(ShortcutInfo.class); + when(si.getShortLabel()).thenReturn("conversation name"); + ccw.setShortcutInfo(si); + ccw.setGroupLabel("group"); + ccw.setParentChannelLabel("parent"); + + assertThat(mController.getTitle(ccw).toString()).isEqualTo(si.getShortLabel()); + } + + @Test + public void getTitle_noShortcut() { + ConversationChannelWrapper ccw = new ConversationChannelWrapper(); + NotificationChannel channel = new NotificationChannel("a", "child", 2); + ccw.setNotificationChannel(channel); + ccw.setPkg("pkg"); + ccw.setUid(1); + ccw.setParentChannelLabel("parent"); + + assertThat(mController.getTitle(ccw).toString()).isEqualTo( + ccw.getNotificationChannel().getName()); + } + + @Test + public void testGetIntent() { + ConversationChannelWrapper ccw = new ConversationChannelWrapper(); + NotificationChannel channel = new NotificationChannel("a", "child", 2); + channel.setConversationId("parent", "convo id"); + ccw.setNotificationChannel(channel); + ccw.setPkg("pkg"); + ccw.setUid(1); + ccw.setParentChannelLabel("parent label"); + Intent intent = mController.getIntent(ccw, "title"); + + Bundle extras = intent.getExtras(); + assertThat(extras.getString(AppInfoBase.ARG_PACKAGE_NAME)).isEqualTo(ccw.getPkg()); + assertThat(extras.getInt(AppInfoBase.ARG_PACKAGE_UID)).isEqualTo(ccw.getUid()); + assertThat(extras.getString(Settings.EXTRA_CHANNEL_ID)).isEqualTo( + ccw.getNotificationChannel().getId()); + assertThat(extras.getString(Settings.EXTRA_CONVERSATION_ID)).isEqualTo( + ccw.getNotificationChannel().getConversationId()); + } + + private final class TestPreferenceController extends ConversationListPreferenceController { + + private TestPreferenceController(Context context, NotificationBackend backend) { + super(context, backend); + } + + @Override + public String getPreferenceKey() { + return "test"; + } + } +} diff --git a/tests/robotests/src/com/android/settings/notification/app/DefaultImportancePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/DefaultImportancePreferenceControllerTest.java index dc2303fd37a..5b853226c69 100644 --- a/tests/robotests/src/com/android/settings/notification/app/DefaultImportancePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/app/DefaultImportancePreferenceControllerTest.java @@ -62,7 +62,7 @@ public class DefaultImportancePreferenceControllerTest { @Mock private NotificationBackend mBackend; @Mock - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; @Mock private UserManager mUm; @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -78,7 +78,7 @@ public class DefaultImportancePreferenceControllerTest { shadowApplication.setSystemService(Context.USER_SERVICE, mUm); mContext = RuntimeEnvironment.application; mController = spy(new DefaultImportancePreferenceController( - mContext, mImportanceListener, mBackend)); + mContext, mDependentFieldListener, mBackend)); } @Test @@ -224,7 +224,7 @@ public class DefaultImportancePreferenceControllerTest { mController.onPreferenceChange(pref, false); assertEquals(IMPORTANCE_LOW, channel.getImportance()); - verify(mImportanceListener, times(1)).onImportanceChanged(); + verify(mDependentFieldListener, times(1)).onFieldValueChanged(); } @Test @@ -241,6 +241,6 @@ public class DefaultImportancePreferenceControllerTest { mController.onPreferenceChange(pref, true); assertEquals(IMPORTANCE_DEFAULT, channel.getImportance()); - verify(mImportanceListener, times(1)).onImportanceChanged(); + verify(mDependentFieldListener, times(1)).onFieldValueChanged(); } } diff --git a/tests/robotests/src/com/android/settings/notification/app/HighImportancePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/HighImportancePreferenceControllerTest.java index 8dc74215945..150fd2ad6a1 100644 --- a/tests/robotests/src/com/android/settings/notification/app/HighImportancePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/app/HighImportancePreferenceControllerTest.java @@ -61,7 +61,7 @@ public class HighImportancePreferenceControllerTest { @Mock private NotificationBackend mBackend; @Mock - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; @Mock private UserManager mUm; @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -77,7 +77,7 @@ public class HighImportancePreferenceControllerTest { shadowApplication.setSystemService(Context.USER_SERVICE, mUm); mContext = RuntimeEnvironment.application; mController = spy(new HighImportancePreferenceController( - mContext, mImportanceListener, mBackend)); + mContext, mDependentFieldListener, mBackend)); } @Test @@ -223,6 +223,6 @@ public class HighImportancePreferenceControllerTest { mController.onPreferenceChange(pref, false); assertEquals(IMPORTANCE_DEFAULT, channel.getImportance()); - verify(mImportanceListener, times(1)).onImportanceChanged(); + verify(mDependentFieldListener, times(1)).onFieldValueChanged(); } } diff --git a/tests/robotests/src/com/android/settings/notification/app/ImportancePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/ImportancePreferenceControllerTest.java index 2c7e17f843e..3de7e7ce312 100644 --- a/tests/robotests/src/com/android/settings/notification/app/ImportancePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/app/ImportancePreferenceControllerTest.java @@ -67,7 +67,7 @@ public class ImportancePreferenceControllerTest { @Mock private NotificationBackend mBackend; @Mock - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; @Mock private UserManager mUm; @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -83,7 +83,7 @@ public class ImportancePreferenceControllerTest { shadowApplication.setSystemService(Context.USER_SERVICE, mUm); mContext = RuntimeEnvironment.application; mController = spy(new ImportancePreferenceController( - mContext, mImportanceListener, mBackend)); + mContext, mDependentFieldListener, mBackend)); } @Test diff --git a/tests/robotests/src/com/android/settings/notification/app/ImportantConversationBubblePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/ImportantConversationBubblePreferenceControllerTest.java new file mode 100644 index 00000000000..c99e0953726 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/app/ImportantConversationBubblePreferenceControllerTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2020 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.app; + +import static android.provider.Settings.Global.NOTIFICATION_BUBBLES; +import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; +import static com.android.settings.notification.app.ImportantConversationBubblePreferenceController.OFF; +import static com.android.settings.notification.app.ImportantConversationBubblePreferenceController.ON; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; + +import androidx.preference.PreferenceScreen; +import androidx.preference.TwoStatePreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class ImportantConversationBubblePreferenceControllerTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + + private ImportantConversationBubblePreferenceController mController; + @Mock + private TwoStatePreference mPreference; + + private static final String KEY = "important_bubble"; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mController = new ImportantConversationBubblePreferenceController(mContext, KEY); + when(mPreference.getKey()).thenReturn(mController.getPreferenceKey()); + when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference); + } + + @Test + public void testGetAvailabilityStatus_globallyOn() { + Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, ON); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void testGetAvailabilityStatus_globallyOff() { + Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, OFF); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + public void updateState_preferenceSetCheckedWhenSettingIsOn() { + Settings.Secure.putInt(mContext.getContentResolver(), BUBBLE_IMPORTANT_CONVERSATIONS, ON); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(true); + } + + @Test + public void updateState_preferenceSetUncheckedWhenSettingIsOff() { + Settings.Secure.putInt(mContext.getContentResolver(), BUBBLE_IMPORTANT_CONVERSATIONS, OFF); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(false); + } + + @Test + public void isChecked_settingIsOff_shouldReturnFalse() { + Settings.Secure.putInt(mContext.getContentResolver(), BUBBLE_IMPORTANT_CONVERSATIONS, OFF); + + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_settingIsOn_shouldReturnTrue() { + Settings.Secure.putInt(mContext.getContentResolver(), BUBBLE_IMPORTANT_CONVERSATIONS, ON); + + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void setChecked_setFalse_disablesSetting() { + Settings.Secure.putInt(mContext.getContentResolver(), BUBBLE_IMPORTANT_CONVERSATIONS, ON); + + mController.setChecked(false); + int updatedValue = Settings.Secure.getInt(mContext.getContentResolver(), + BUBBLE_IMPORTANT_CONVERSATIONS, -1); + + assertThat(updatedValue).isEqualTo(OFF); + } + + @Test + public void setChecked_setTrue_enablesSetting() { + Settings.Secure.putInt(mContext.getContentResolver(), BUBBLE_IMPORTANT_CONVERSATIONS, OFF); + + mController.setChecked(true); + int updatedValue = Settings.Secure.getInt(mContext.getContentResolver(), + BUBBLE_IMPORTANT_CONVERSATIONS, -1); + + assertThat(updatedValue).isEqualTo(ON); + } + + @Test + public void isSliceable_returnsFalse() { + assertThat(mController.isSliceable()).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/app/MinImportancePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/MinImportancePreferenceControllerTest.java index bed12af539e..f0468204202 100644 --- a/tests/robotests/src/com/android/settings/notification/app/MinImportancePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/app/MinImportancePreferenceControllerTest.java @@ -61,7 +61,7 @@ public class MinImportancePreferenceControllerTest { @Mock private NotificationBackend mBackend; @Mock - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; @Mock private UserManager mUm; @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -77,7 +77,7 @@ public class MinImportancePreferenceControllerTest { shadowApplication.setSystemService(Context.USER_SERVICE, mUm); mContext = RuntimeEnvironment.application; mController = spy(new MinImportancePreferenceController( - mContext, mImportanceListener, mBackend)); + mContext, mDependentFieldListener, mBackend)); } @Test @@ -223,6 +223,6 @@ public class MinImportancePreferenceControllerTest { mController.onPreferenceChange(pref, true); assertEquals(IMPORTANCE_MIN, channel.getImportance()); - verify(mImportanceListener, times(1)).onImportanceChanged(); + verify(mDependentFieldListener, times(1)).onFieldValueChanged(); } } diff --git a/tests/robotests/src/com/android/settings/notification/app/SoundPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/SoundPreferenceControllerTest.java index 157e66676d0..0d18c1f9aef 100644 --- a/tests/robotests/src/com/android/settings/notification/app/SoundPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/app/SoundPreferenceControllerTest.java @@ -80,7 +80,7 @@ public class SoundPreferenceControllerTest { @Mock private SettingsPreferenceFragment mFragment; @Mock - private NotificationSettings.ImportanceListener mImportanceListener; + private NotificationSettings.DependentFieldListener mDependentFieldListener; private SoundPreferenceController mController; @@ -92,7 +92,7 @@ public class SoundPreferenceControllerTest { shadowApplication.setSystemService(Context.USER_SERVICE, mUm); mContext = RuntimeEnvironment.application; mController = spy(new SoundPreferenceController( - mContext, mFragment, mImportanceListener, mBackend)); + mContext, mFragment, mDependentFieldListener, mBackend)); } @Test @@ -303,7 +303,7 @@ public class SoundPreferenceControllerTest { mController.onActivityResult(SoundPreferenceController.CODE, 1, new Intent("hi")); verify(pref, times(1)).onActivityResult(anyInt(), anyInt(), any()); - verify(mImportanceListener, times(1)).onImportanceChanged(); + verify(mDependentFieldListener, times(1)).onFieldValueChanged(); } @Test