From 1ee9dce20c0d6452ab8ef4ecd44b7b92b52ed5e2 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Fri, 14 Feb 2020 16:45:15 -0500 Subject: [PATCH] Add screen for all conversations Test: atest Bug: 149583455 Change-Id: I9704082d097ef2bb17c051703e380f573056318c --- res/values/strings.xml | 12 + res/xml/configure_notification_settings.xml | 10 + res/xml/conversation_list_settings.xml | 46 ++++ .../notification/NotificationBackend.java | 12 + .../AllConversationsPreferenceController.java | 73 ++++++ ...pConversationListPreferenceController.java | 163 ++++++++++++ .../app/AppNotificationSettings.java | 2 +- .../ConversationListPreferenceController.java | 166 ++++++------ .../app/ConversationListSettings.java | 67 +++++ ...tantConversationsPreferenceController.java | 75 ++++++ ...versationListPreferenceControllerTest.java | 241 ++++++++++++++++++ 11 files changed, 776 insertions(+), 91 deletions(-) create mode 100644 res/xml/conversation_list_settings.xml create mode 100644 src/com/android/settings/notification/app/AllConversationsPreferenceController.java create mode 100644 src/com/android/settings/notification/app/AppConversationListPreferenceController.java create mode 100644 src/com/android/settings/notification/app/ConversationListSettings.java create mode 100644 src/com/android/settings/notification/app/ImportantConversationsPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 83e730af664..5fb536762a6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8268,6 +8268,18 @@ Add to the conversation section + + Manage conversations + + + Important conversations + + + All 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/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..4b6e694e612 100644 --- a/src/com/android/settings/notification/app/AppNotificationSettings.java +++ b/src/com/android/settings/notification/app/AppNotificationSettings.java @@ -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/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..ad7548656ba --- /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/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/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"; + } + } +}