Merge "DND Bypassing Apps redesign" into rvc-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 46d75e8..fd869d4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6908,7 +6908,8 @@
<!-- Summary for add user action, when it's disabled [CHAR LIMIT=100] -->
<string name="user_add_max_count">You can add up to <xliff:g id="user_count">%1$d</xliff:g> users</string>
- <!-- Message to secondary users that only owner can manage users [CHAR LIMIT=none] -->
+
+ <!-- Message to secondary users that only owner can manage users [CHAR LIMIT=none] -->
<string name="user_cannot_manage_message" product="tablet">Only the tablet\u2019s owner can manage users.</string>
<!-- Message to secondary users that only owner can manage users [CHAR LIMIT=none] -->
<string name="user_cannot_manage_message" product="default">Only the phone\u2019s owner can manage users.</string>
@@ -8973,8 +8974,22 @@
<string name="zen_mode_bypassing_apps">Allow apps to override</string>
<!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND header -->
<string name="zen_mode_bypassing_apps_header">Apps that can interrupt</string>
+ <!-- [CHAR LIMIT=100] Zen mode settings: Add apps to bypass DND header -->
+ <string name="zen_mode_bypassing_apps_add_header">Select more apps</string>
+ <!-- [CHAR LIMIT=120] Zen mode settings: No apps are bypassing DND -->
+ <string name="zen_mode_bypassing_apps_none">No apps selected</string>
<!-- [CHAR LIMIT=120] Zen mode settings: No apps are bypassing DND -->
<string name="zen_mode_bypassing_apps_subtext_none">No apps can interrupt</string>
+ <!-- [CHAR LIMIT=120] Zen mode settings: Preference to add apps that are allowed to bypass DND -->
+ <string name="zen_mode_bypassing_apps_add">Add apps</string>
+ <!-- [CHAR LIMIT=120] Zen mode settings: Summary indicating all notification channels can
+ bypass DND for this app -->
+ <string name="zen_mode_bypassing_apps_summary_all">All notifications</string>
+ <!-- [CHAR LIMIT=120] Zen mode settings: Summary indicating all notification channels can
+ bypass DND for this app -->
+ <string name="zen_mode_bypassing_apps_summary_some">Some notifications</string>
+ <!-- [CHAR LIMIT=NONE] Zen mode settings: Footer for DND bypassing apps settings -->
+ <string name="zen_mode_bypassing_apps_footer">Selected people can still reach you, even if you don\u2019t allow apps to interrupt</string>
<!-- [CHAR LIMIT=120] Zen mode settings: Allow apps to bypass DND -->
<plurals name="zen_mode_bypassing_apps_subtext">
<item quantity="one"><xliff:g id="app_name" example="Nest">%s</xliff:g> can interrupt</item>
@@ -8989,6 +9004,11 @@
<string name="zen_mode_bypassing_apps_all_summary">All notifications</string>
<!-- [CHAR LIMIT=100] Zen mode settings: App that can bypass DND's secondary text describing which notification channels from the app can bypass DND-->
<string name="zen_mode_bypassing_apps_some_summary">Some notifications</string>
+ <!-- [CHAR LIMIT=100] Zen mode settings: Allow notifications from an app to bypass DND header -->
+ <string name="zen_mode_bypassing_app_channels_header">Notifications that can interrupt</string>
+ <!-- [CHAR LIMIT=100] Zen mode settings: Allow all notifications from an app to bypass DND
+ toggle title -->
+ <string name="zen_mode_bypassing_app_channels_toggle_all">Allow all notifications</string>
<!-- [CHAR LIMIT=120] Zen mode settings: Summary for sound interruption settings -->
<plurals name="zen_mode_other_sounds_summary">
@@ -9022,9 +9042,9 @@
<!-- [CHAR LIMIT=50] Zen mode settings: Repeat callers (ie: repeat callers are allowed to bypass dnd) -->
<string name="zen_mode_repeat_callers_list">repeat callers</string>
<!-- [CHAR LIMIT=50] Zen mode settings: calls summary -->
- <string name="zen_mode_calls_summary_one">Allow from <xliff:g id="caller type" example="contacts">%1$s</xliff:g></string>
+ <string name="zen_mode_calls_summary_one"><xliff:g id="caller type" example="contacts">%1$s</xliff:g></string>
<!-- [CHAR LIMIT=50] Zen mode settings: calls summary -->
- <string name="zen_mode_calls_summary_two">Allow from <xliff:g id="caller type" example="starred contacts">%1$s</xliff:g> and <xliff:g id="callert tpye" example="repeat callers">%2$s</xliff:g></string>
+ <string name="zen_mode_calls_summary_two"><xliff:g id="caller type" example="starred contacts">%1$s</xliff:g> and <xliff:g id="caller type" example="repeat callers">%2$s</xliff:g></string>
<!-- [CHAR LIMIT=200] Zen mode settings: Repeat callers option summary -->
<string name="zen_mode_repeat_callers_summary">If the same person calls a second time within a <xliff:g id="minutes">%d</xliff:g> minute period</string>
diff --git a/res/xml/app_channels_bypassing_dnd_settings.xml b/res/xml/app_channels_bypassing_dnd_settings.xml
new file mode 100644
index 0000000..4f6291d
--- /dev/null
+++ b/res/xml/app_channels_bypassing_dnd_settings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:title="@string/zen_mode_settings_title">
+ <com.android.settingslib.widget.LayoutPreference
+ android:key="pref_app_header"
+ android:layout="@layout/settings_entity_header" />
+
+ <PreferenceCategory
+ android:key="zen_mode_bypassing_app_channels_list"
+ android:title="@string/zen_mode_bypassing_app_channels_header">
+ <!-- add app channel toggles here -->
+ </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/conversation_list_settings.xml b/res/xml/conversation_list_settings.xml
index 90a7279..e18b4e5 100644
--- a/res/xml/conversation_list_settings.xml
+++ b/res/xml/conversation_list_settings.xml
@@ -14,10 +14,11 @@
limitations under the License.
-->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res-auto"
- android:key="conversation_list"
- android:title="zen_mode_conversations_title">
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:key="conversation_list"
+ android:title="@string/zen_mode_conversations_title">
<PreferenceCategory
android:key="important_conversations"
diff --git a/res/xml/zen_mode_bypassing_apps.xml b/res/xml/zen_mode_bypassing_apps.xml
index bd46c67..9039470 100644
--- a/res/xml/zen_mode_bypassing_apps.xml
+++ b/res/xml/zen_mode_bypassing_apps.xml
@@ -17,4 +17,21 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
- android:title="@string/zen_mode_bypassing_apps_title" />
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/zen_mode_bypassing_apps_title">
+
+ <PreferenceCategory
+ android:key="zen_mode_bypassing_apps_list"
+ android:title="@string/zen_mode_bypassing_apps_header">
+ <!-- apps that have notifications that can bypass DND are added here -->
+ </PreferenceCategory>
+
+ <Preference
+ android:key="zen_mode_bypassing_apps_add"
+ android:title="@string/zen_mode_bypassing_apps_add"
+ android:icon="@drawable/ic_add_24dp"
+ settings:allowDividerAbove="true" />
+
+ <com.android.settingslib.widget.FooterPreference
+ android:title="@string/zen_mode_bypassing_apps_footer" />
+</PreferenceScreen>
diff --git a/res/xml/zen_mode_custom_rule_calls_settings.xml b/res/xml/zen_mode_custom_rule_calls_settings.xml
new file mode 100644
index 0000000..4dca2ad
--- /dev/null
+++ b/res/xml/zen_mode_custom_rule_calls_settings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="zen_mode_custom_rule_calls_settings_page"
+ android:title="@string/zen_mode_calls_title" >
+
+ <PreferenceCategory
+ android:key="zen_mode_settings_category_calls"
+ android:title="@string/zen_mode_settings_category">
+ <!-- Calls -->
+ <ListPreference
+ android:key="zen_mode_calls"
+ android:title="@string/zen_mode_calls"
+ android:entries="@array/zen_mode_contacts_calls_entries"
+ android:entryValues="@array/zen_mode_contacts_values"/>
+
+ <Preference
+ android:key="zen_mode_starred_contacts_callers"
+ android:title="@string/zen_mode_starred_contacts_title"/>
+
+ <!-- Repeat callers -->
+ <SwitchPreference
+ android:key="zen_mode_repeat_callers"
+ android:title="@string/zen_mode_repeat_callers_title" />
+ </PreferenceCategory>
+
+ <com.android.settingslib.widget.FooterPreference/>
+
+</PreferenceScreen>
diff --git a/res/xml/zen_mode_custom_rule_messages_settings.xml b/res/xml/zen_mode_custom_rule_messages_settings.xml
new file mode 100644
index 0000000..66091ec
--- /dev/null
+++ b/res/xml/zen_mode_custom_rule_messages_settings.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="zen_mode_custom_rule_messages_settings_page"
+ android:title="@string/zen_mode_messages_title" >
+
+ <PreferenceCategory
+ android:title="@string/zen_mode_settings_category"
+ android:key="zen_mode_settings_category_messages">
+ <!-- Messages -->
+ <ListPreference
+ android:key="zen_mode_messages"
+ android:title="@string/zen_mode_messages"
+ android:entries="@array/zen_mode_contacts_messages_entries"
+ android:entryValues="@array/zen_mode_contacts_values"/>
+
+ <Preference
+ android:key="zen_mode_starred_contacts_messages"
+ android:title="@string/zen_mode_starred_contacts_title"/>
+ </PreferenceCategory>
+
+ <com.android.settingslib.widget.FooterPreference/>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java
new file mode 100644
index 0000000..4403873
--- /dev/null
+++ b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java
@@ -0,0 +1,227 @@
+/*
+ * 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_NONE;
+
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.Settings;
+
+import androidx.core.text.BidiFormatter;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoBase;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.notification.NotificationBackend;
+import com.android.settings.widget.MasterSwitchPreference;
+import com.android.settingslib.RestrictedSwitchPreference;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Populates the PreferenceCategory with notification channels associated with the given app.
+ * Users can allow/disallow notification channels from bypassing DND on a single settings
+ * page.
+ */
+public class AppChannelsBypassingDndPreferenceController extends NotificationPreferenceController
+ implements PreferenceControllerMixin, LifecycleObserver {
+
+ private static final String KEY = "zen_mode_bypassing_app_channels_list";
+ private static final String ARG_FROM_SETTINGS = "fromSettings";
+
+ private RestrictedSwitchPreference mAllNotificationsToggle;
+ private PreferenceCategory mPreferenceCategory;
+ private final List<NotificationChannel> mChannels = new ArrayList<>();
+
+ public AppChannelsBypassingDndPreferenceController(
+ Context context,
+ NotificationBackend backend) {
+ super(context, backend);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ mPreferenceCategory = screen.findPreference(KEY);
+
+ mAllNotificationsToggle = new RestrictedSwitchPreference(mPreferenceCategory.getContext());
+ mAllNotificationsToggle.setTitle(R.string.zen_mode_bypassing_app_channels_toggle_all);
+ mAllNotificationsToggle.setDisabledByAdmin(mAdmin);
+ mAllNotificationsToggle.setEnabled(
+ (mAdmin == null || !mAllNotificationsToggle.isDisabledByAdmin()));
+ mAllNotificationsToggle.setOnPreferenceClickListener(
+ new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference pref) {
+ SwitchPreference preference = (SwitchPreference) pref;
+ final boolean bypassDnd = preference.isChecked();
+ for (NotificationChannel channel : mChannels) {
+ if (showNotification(channel) && isChannelConfigurable(channel)) {
+ channel.setBypassDnd(bypassDnd);
+ channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+ mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, channel);
+ }
+ }
+ // the 0th index is the mAllNotificationsToggle which allows users to
+ // toggle all notifications from this app to bypass DND
+ for (int i = 1; i < mPreferenceCategory.getPreferenceCount(); i++) {
+ MasterSwitchPreference childPreference =
+ (MasterSwitchPreference) mPreferenceCategory.getPreference(i);
+ childPreference.setChecked(showNotificationInDnd(mChannels.get(i - 1)));
+ }
+ return true;
+ }
+ });
+
+ loadAppChannels();
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mAppRow != null;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ if (mAppRow != null) {
+ loadAppChannels();
+ }
+ }
+
+ private void loadAppChannels() {
+ // Load channel settings
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ List<NotificationChannelGroup> mChannelGroupList = mBackend.getGroups(mAppRow.pkg,
+ mAppRow.uid).getList();
+ mChannels.clear();
+ for (NotificationChannelGroup channelGroup : mChannelGroupList) {
+ for (NotificationChannel channel : channelGroup.getChannels()) {
+ if (!isConversation(channel)) {
+ mChannels.add(channel);
+ }
+ }
+ }
+ Collections.sort(mChannels, CHANNEL_COMPARATOR);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void unused) {
+ if (mContext == null) {
+ return;
+ }
+ populateList();
+ }
+ }.execute();
+ }
+
+ private void populateList() {
+ if (mPreferenceCategory == null) {
+ return;
+ }
+
+ mPreferenceCategory.removeAll();
+ mPreferenceCategory.addPreference(mAllNotificationsToggle);
+ for (NotificationChannel channel : mChannels) {
+ MasterSwitchPreference channelPreference = new MasterSwitchPreference(mContext);
+ channelPreference.setDisabledByAdmin(mAdmin);
+ channelPreference.setSwitchEnabled(
+ (mAdmin == null || !channelPreference.isDisabledByAdmin())
+ && isChannelConfigurable(channel)
+ && showNotification(channel));
+ channelPreference.setTitle(BidiFormatter.getInstance().unicodeWrap(channel.getName()));
+ channelPreference.setChecked(showNotificationInDnd(channel));
+ channelPreference.setOnPreferenceChangeListener(
+ new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference pref, Object val) {
+ boolean switchOn = (Boolean) val;
+ channel.setBypassDnd(switchOn);
+ channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+ mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, channel);
+ mAllNotificationsToggle.setChecked(areAllChannelsBypassing());
+ return true;
+ }
+ });
+
+ 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, channel.getId());
+ channelArgs.putBoolean(ARG_FROM_SETTINGS, true);
+ channelPreference.setIntent(new SubSettingLauncher(mContext)
+ .setDestination(ChannelNotificationSettings.class.getName())
+ .setArguments(channelArgs)
+ .setTitleRes(com.android.settings.R.string.notification_channel_title)
+ .setSourceMetricsCategory(SettingsEnums.DND_APPS_BYPASSING)
+ .toIntent());
+ mPreferenceCategory.addPreference(channelPreference);
+ }
+ mAllNotificationsToggle.setChecked(areAllChannelsBypassing());
+ }
+
+ private boolean areAllChannelsBypassing() {
+ boolean allChannelsBypassing = true;
+ for (NotificationChannel channel : mChannels) {
+ if (showNotification(channel)) {
+ allChannelsBypassing &= showNotificationInDnd(channel);
+ }
+ }
+ return allChannelsBypassing;
+ }
+
+ /**
+ * Whether notifications from this channel would show if DND were on.
+ */
+ private boolean showNotificationInDnd(NotificationChannel channel) {
+ return channel.canBypassDnd() && showNotification(channel);
+ }
+
+ /**
+ * Whether notifications from this channel would show if DND weren't on.
+ */
+ private boolean showNotification(NotificationChannel channel) {
+ return channel.getImportance() != IMPORTANCE_NONE;
+ }
+
+ /**
+ * Whether this notification channel is representing a conversation.
+ */
+ private boolean isConversation(NotificationChannel channel) {
+ return channel.getConversationId() != null && !channel.isDemoted();
+ }
+}
diff --git a/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java b/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java
new file mode 100644
index 0000000..4dcbcfc
--- /dev/null
+++ b/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java
@@ -0,0 +1,80 @@
+/*
+ * 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.text.TextUtils;
+import android.util.Log;
+
+import com.android.settings.R;
+import com.android.settings.notification.NotificationBackend;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Per-app Settings page that shows a list of notification channels that a user can toggle for
+ * the channel to bypass DND.
+ *
+ * This can be found at:
+ * Settings > Sound > Do Not Disturb > Apps > (Choose app)
+ */
+public class AppChannelsBypassingDndSettings extends NotificationSettings {
+ private static final String TAG = "AppChannelsBypassingDndSettings";
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.DND_APPS_BYPASSING;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
+ Log.w(TAG, "Missing package or uid or packageinfo");
+ finish();
+ return;
+ }
+
+ for (NotificationPreferenceController controller : mControllers) {
+ controller.onResume(mAppRow, null, null, null, null, mSuspendedAppsAdmin);
+ controller.displayPreference(getPreferenceScreen());
+ }
+ updatePreferenceStates();
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.app_channels_bypassing_dnd_settings;
+ }
+
+ @Override
+ protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+ mControllers = new ArrayList<>();
+ mControllers.add(new HeaderPreferenceController(context, this));
+ mControllers.add(new AppChannelsBypassingDndPreferenceController(context,
+ new NotificationBackend()));
+ return new ArrayList<>(mControllers);
+ }
+}
diff --git a/src/com/android/settings/notification/app/ChannelListPreferenceController.java b/src/com/android/settings/notification/app/ChannelListPreferenceController.java
index 187e7e1..b19fc71 100644
--- a/src/com/android/settings/notification/app/ChannelListPreferenceController.java
+++ b/src/com/android/settings/notification/app/ChannelListPreferenceController.java
@@ -33,6 +33,11 @@
import android.provider.Settings;
import android.text.TextUtils;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
@@ -43,14 +48,8 @@
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceGroup;
-import androidx.preference.SwitchPreference;
-
public class ChannelListPreferenceController extends NotificationPreferenceController {
private static final String KEY = "channels";
@@ -94,7 +93,7 @@
@Override
protected Void doInBackground(Void... unused) {
mChannelGroupList = mBackend.getGroups(mAppRow.pkg, mAppRow.uid).getList();
- Collections.sort(mChannelGroupList, mChannelGroupComparator);
+ Collections.sort(mChannelGroupList, CHANNEL_GROUP_COMPARATOR);
return null;
}
@@ -142,7 +141,7 @@
}
if (!group.isBlocked()) {
final List<NotificationChannel> channels = group.getChannels();
- Collections.sort(channels, mChannelComparator);
+ Collections.sort(channels, CHANNEL_COMPARATOR);
int N = channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel channel = channels.get(i);
@@ -274,7 +273,7 @@
}
} else {
final List<NotificationChannel> channels = group.getChannels();
- Collections.sort(channels, mChannelComparator);
+ Collections.sort(channels, CHANNEL_COMPARATOR);
int N = channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel channel = channels.get(i);
@@ -283,33 +282,4 @@
}
}
}
-
- private Comparator<NotificationChannelGroup> mChannelGroupComparator =
- new Comparator<NotificationChannelGroup>() {
-
- @Override
- public int compare(NotificationChannelGroup left, NotificationChannelGroup right) {
- // Non-grouped channels (in placeholder group with a null id) come last
- if (left.getId() == null && right.getId() != null) {
- return 1;
- } else if (right.getId() == null && left.getId() != null) {
- return -1;
- }
- return left.getId().compareTo(right.getId());
- }
- };
-
- protected Comparator<NotificationChannel> mChannelComparator =
- (left, right) -> {
- if (left.isDeleted() != right.isDeleted()) {
- return Boolean.compare(left.isDeleted(), right.isDeleted());
- } else if (left.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- // Uncategorized/miscellaneous legacy channel goes last
- return 1;
- } else if (right.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- return -1;
- }
-
- return left.getId().compareTo(right.getId());
- };
}
diff --git a/src/com/android/settings/notification/app/NotificationPreferenceController.java b/src/com/android/settings/notification/app/NotificationPreferenceController.java
index ecba95b..14c98cf 100644
--- a/src/com/android/settings/notification/app/NotificationPreferenceController.java
+++ b/src/com/android/settings/notification/app/NotificationPreferenceController.java
@@ -35,6 +35,7 @@
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.core.AbstractPreferenceController;
+import java.util.Comparator;
import java.util.Objects;
/**
@@ -172,4 +173,31 @@
}
return Objects.equals(NotificationChannel.DEFAULT_CHANNEL_ID, mChannel.getId());
}
+
+ public static final Comparator<NotificationChannelGroup> CHANNEL_GROUP_COMPARATOR =
+ new Comparator<NotificationChannelGroup>() {
+ @Override
+ public int compare(NotificationChannelGroup left, NotificationChannelGroup right) {
+ // Non-grouped channels (in placeholder group with a null id) come last
+ if (left.getId() == null && right.getId() != null) {
+ return 1;
+ } else if (right.getId() == null && left.getId() != null) {
+ return -1;
+ }
+ return left.getId().compareTo(right.getId());
+ }
+ };
+
+ public static final Comparator<NotificationChannel> CHANNEL_COMPARATOR = (left, right) -> {
+ if (left.isDeleted() != right.isDeleted()) {
+ return Boolean.compare(left.isDeleted(), right.isDeleted());
+ } else if (left.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ // Uncategorized/miscellaneous legacy channel goes last
+ return 1;
+ } else if (right.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ return -1;
+ }
+
+ return left.getId().compareTo(right.getId());
+ };
}
diff --git a/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java b/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java
index 37cd558..459e418 100644
--- a/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java
+++ b/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java
@@ -44,7 +44,7 @@
@Override
protected int getPreferenceScreenResId() {
- return com.android.settings.R.xml.zen_mode_calls_settings;
+ return com.android.settings.R.xml.zen_mode_custom_rule_calls_settings;
}
@Override
diff --git a/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java b/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java
index e592e75..d4d3730 100644
--- a/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java
+++ b/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java
@@ -37,7 +37,7 @@
@Override
protected int getPreferenceScreenResId() {
- return com.android.settings.R.xml.zen_mode_messages_settings;
+ return com.android.settings.R.xml.zen_mode_custom_rule_messages_settings;
}
@Override
diff --git a/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceController.java
new file mode 100644
index 0000000..15a46fe
--- /dev/null
+++ b/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceController.java
@@ -0,0 +1,228 @@
+/*
+ * 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.zen;
+
+import android.app.Application;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.core.text.BidiFormatter;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoBase;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.notification.NotificationBackend;
+import com.android.settings.notification.app.AppChannelsBypassingDndSettings;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.widget.apppreference.AppPreference;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * When clicked, populates the PreferenceScreen with apps that aren't already bypassing DND. The
+ * user can click on these Preferences to allow notification channels from the app to bypass DND.
+ */
+public class ZenModeAddBypassingAppsPreferenceController extends AbstractPreferenceController
+ implements PreferenceControllerMixin {
+
+ private static final String KEY = "zen_mode_non_bypassing_apps_list";
+ private static final String KEY_ADD = "zen_mode_bypassing_apps_add";
+ private final NotificationBackend mNotificationBackend;
+
+ @VisibleForTesting ApplicationsState mApplicationsState;
+ @VisibleForTesting PreferenceScreen mPreferenceScreen;
+ @VisibleForTesting PreferenceCategory mPreferenceCategory;
+ @VisibleForTesting Context mPrefContext;
+
+ private Preference mAddPreference;
+ private ApplicationsState.Session mAppSession;
+ private Fragment mHostFragment;
+
+ public ZenModeAddBypassingAppsPreferenceController(Context context, Application app,
+ Fragment host, NotificationBackend notificationBackend) {
+ this(context, app == null ? null : ApplicationsState.getInstance(app), host,
+ notificationBackend);
+ }
+
+ private ZenModeAddBypassingAppsPreferenceController(Context context, ApplicationsState appState,
+ Fragment host, NotificationBackend notificationBackend) {
+ super(context);
+ mNotificationBackend = notificationBackend;
+ mApplicationsState = appState;
+ mHostFragment = host;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ mPreferenceScreen = screen;
+ mAddPreference = screen.findPreference(KEY_ADD);
+ mAddPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ mAddPreference.setVisible(false);
+ if (mApplicationsState != null && mHostFragment != null) {
+ mAppSession = mApplicationsState.newSession(mAppSessionCallbacks,
+ mHostFragment.getLifecycle());
+ }
+ return true;
+ }
+ });
+ mPrefContext = screen.getContext();
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ /**
+ * Call this method to trigger the app list to refresh.
+ */
+ public void updateAppList() {
+ if (mAppSession == null) {
+ return;
+ }
+
+ ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED;
+ List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter,
+ ApplicationsState.ALPHA_COMPARATOR);
+ updateAppList(apps);
+ }
+
+ @VisibleForTesting
+ void updateAppList(List<ApplicationsState.AppEntry> apps) {
+ if (apps == null) {
+ return;
+ }
+
+ if (mPreferenceCategory == null) {
+ mPreferenceCategory = new PreferenceCategory(mPrefContext);
+ mPreferenceCategory.setTitle(R.string.zen_mode_bypassing_apps_add_header);
+ mPreferenceScreen.addPreference(mPreferenceCategory);
+ }
+
+ List<Preference> appsWithNoBypassingDndNotificationChannels = new ArrayList<>();
+ for (ApplicationsState.AppEntry entry : apps) {
+ String pkg = entry.info.packageName;
+ mApplicationsState.ensureIcon(entry);
+ final int appChannels = mNotificationBackend.getChannelCount(pkg, entry.info.uid);
+ final int appChannelsBypassingDnd = mNotificationBackend
+ .getNotificationChannelsBypassingDnd(pkg, entry.info.uid).getList().size();
+ if (appChannelsBypassingDnd == 0 && appChannels > 0) {
+ final String key = ZenModeAllBypassingAppsPreferenceController.getKey(pkg);
+ Preference pref = mPreferenceCategory.findPreference("");
+ if (pref == null) {
+ pref = new AppPreference(mPrefContext);
+ pref.setKey(key);
+ pref.setOnPreferenceClickListener(preference -> {
+ Bundle args = new Bundle();
+ args.putString(AppInfoBase.ARG_PACKAGE_NAME, entry.info.packageName);
+ args.putInt(AppInfoBase.ARG_PACKAGE_UID, entry.info.uid);
+ new SubSettingLauncher(mContext)
+ .setDestination(AppChannelsBypassingDndSettings.class.getName())
+ .setArguments(args)
+ .setResultListener(mHostFragment, 0)
+ .setSourceMetricsCategory(
+ SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
+ .launch();
+ return true;
+ });
+ }
+ pref.setTitle(BidiFormatter.getInstance().unicodeWrap(entry.label));
+ pref.setIcon(entry.icon);
+ appsWithNoBypassingDndNotificationChannels.add(pref);
+ }
+ }
+
+ if (appsWithNoBypassingDndNotificationChannels.size() == 0) {
+ Preference pref = mPreferenceCategory.findPreference(
+ ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS);
+ if (pref == null) {
+ pref = new Preference(mPrefContext);
+ pref.setKey(ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS);
+ pref.setTitle(R.string.zen_mode_bypassing_apps_subtext_none);
+ }
+ mPreferenceCategory.addPreference(pref);
+ }
+
+ if (ZenModeAllBypassingAppsPreferenceController.hasAppListChanged(
+ appsWithNoBypassingDndNotificationChannels, mPreferenceCategory)) {
+ mPreferenceCategory.removeAll();
+ for (Preference prefToAdd : appsWithNoBypassingDndNotificationChannels) {
+ mPreferenceCategory.addPreference(prefToAdd);
+ }
+ }
+ }
+
+ private final ApplicationsState.Callbacks mAppSessionCallbacks =
+ new ApplicationsState.Callbacks() {
+
+ @Override
+ public void onRunningStateChanged(boolean running) {
+ updateAppList();
+ }
+
+ @Override
+ public void onPackageListChanged() {
+ updateAppList();
+ }
+
+ @Override
+ public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
+ updateAppList(apps);
+ }
+
+ @Override
+ public void onPackageIconChanged() {
+ updateAppList();
+ }
+
+ @Override
+ public void onPackageSizeChanged(String packageName) {
+ updateAppList();
+ }
+
+ @Override
+ public void onAllSizesComputed() { }
+
+ @Override
+ public void onLauncherInfoChanged() {
+ updateAppList();
+ }
+
+ @Override
+ public void onLoadEntriesCompleted() {
+ updateAppList();
+ }
+ };
+}
diff --git a/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceController.java
index 9484a09..52c872c 100644
--- a/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceController.java
+++ b/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceController.java
@@ -17,57 +17,59 @@
package com.android.settings.notification.zen;
import android.app.Application;
-import android.app.NotificationChannel;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
-import android.provider.Settings;
-import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
-import com.android.settings.notification.app.ChannelNotificationSettings;
import com.android.settings.notification.NotificationBackend;
+import com.android.settings.notification.app.AppChannelsBypassingDndSettings;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.apppreference.AppPreference;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
+
/**
* Adds a preference to the PreferenceScreen for each notification channel that can bypass DND.
*/
public class ZenModeAllBypassingAppsPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin {
+ public static final String KEY_NO_APPS = getKey("none");
+ private static final String KEY = "zen_mode_bypassing_apps_list";
- private final String KEY = "zen_mode_bypassing_apps_category";
+ private final NotificationBackend mNotificationBackend;
@VisibleForTesting ApplicationsState mApplicationsState;
- @VisibleForTesting PreferenceScreen mPreferenceScreen;
+ @VisibleForTesting PreferenceCategory mPreferenceCategory;
@VisibleForTesting Context mPrefContext;
private ApplicationsState.Session mAppSession;
- private NotificationBackend mNotificationBackend = new NotificationBackend();
private Fragment mHostFragment;
public ZenModeAllBypassingAppsPreferenceController(Context context, Application app,
- Fragment host) {
-
- this(context, app == null ? null : ApplicationsState.getInstance(app), host);
+ Fragment host, NotificationBackend notificationBackend) {
+ this(context, app == null ? null : ApplicationsState.getInstance(app), host,
+ notificationBackend);
}
private ZenModeAllBypassingAppsPreferenceController(Context context, ApplicationsState appState,
- Fragment host) {
+ Fragment host, NotificationBackend notificationBackend) {
super(context);
+ mNotificationBackend = notificationBackend;
mApplicationsState = appState;
mHostFragment = host;
@@ -78,9 +80,9 @@
@Override
public void displayPreference(PreferenceScreen screen) {
- mPreferenceScreen = screen;
- mPrefContext = mPreferenceScreen.getContext();
- updateNotificationChannelList();
+ mPreferenceCategory = screen.findPreference(KEY);
+ mPrefContext = screen.getContext();
+ updateAppList();
super.displayPreference(screen);
}
@@ -95,9 +97,9 @@
}
/**
- * Call this method to trigger the notification channels list to refresh.
+ * Call this method to trigger the app list to refresh.
*/
- public void updateNotificationChannelList() {
+ public void updateAppList() {
if (mAppSession == null) {
return;
}
@@ -105,64 +107,95 @@
ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED;
List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter,
ApplicationsState.ALPHA_COMPARATOR);
- updateNotificationChannelList(apps);
+ updateAppList(apps);
}
@VisibleForTesting
- void updateNotificationChannelList(List<ApplicationsState.AppEntry> apps) {
- if (mPreferenceScreen == null || apps == null) {
+ void updateAppList(List<ApplicationsState.AppEntry> apps) {
+ if (mPreferenceCategory == null || apps == null) {
return;
}
- boolean showEmptyState = true;
-
- List<Preference> channelsBypassingDnd = new ArrayList<>();
- for (ApplicationsState.AppEntry entry : apps) {
- String pkg = entry.info.packageName;
- mApplicationsState.ensureIcon(entry);
- for (NotificationChannel channel : mNotificationBackend
- .getNotificationChannelsBypassingDnd(pkg, entry.info.uid).getList()) {
- if (!TextUtils.isEmpty(channel.getConversationId())) {
- // conversation channels that bypass dnd will be shown on the People page
- continue;
+ List<Preference> appsBypassingDnd = new ArrayList<>();
+ for (ApplicationsState.AppEntry app : apps) {
+ String pkg = app.info.packageName;
+ mApplicationsState.ensureIcon(app);
+ final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid);
+ final int appChannelsBypassingDnd = mNotificationBackend
+ .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size();
+ if (appChannelsBypassingDnd > 0) {
+ final String key = getKey(pkg);
+ // re-use previously created preference when possible
+ Preference pref = mPreferenceCategory.findPreference(key);
+ if (pref == null) {
+ pref = new AppPreference(mPrefContext);
+ pref.setKey(key);
+ pref.setOnPreferenceClickListener(preference -> {
+ Bundle args = new Bundle();
+ args.putString(AppInfoBase.ARG_PACKAGE_NAME, app.info.packageName);
+ args.putInt(AppInfoBase.ARG_PACKAGE_UID, app.info.uid);
+ new SubSettingLauncher(mContext)
+ .setDestination(AppChannelsBypassingDndSettings.class.getName())
+ .setArguments(args)
+ .setResultListener(mHostFragment, 0)
+ .setSourceMetricsCategory(
+ SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
+ .launch();
+ return true;
+ });
}
- Preference pref = new AppPreference(mPrefContext);
- pref.setKey(pkg + "|" + channel.getId());
- pref.setTitle(BidiFormatter.getInstance().unicodeWrap(entry.label));
- pref.setIcon(entry.icon);
- pref.setSummary(BidiFormatter.getInstance().unicodeWrap(channel.getName()));
-
- pref.setOnPreferenceClickListener(preference -> {
- Bundle args = new Bundle();
- args.putString(AppInfoBase.ARG_PACKAGE_NAME, entry.info.packageName);
- args.putInt(AppInfoBase.ARG_PACKAGE_UID, entry.info.uid);
- args.putString(Settings.EXTRA_CHANNEL_ID, channel.getId());
- new SubSettingLauncher(mContext)
- .setDestination(ChannelNotificationSettings.class.getName())
- .setArguments(args)
- .setTitleRes(R.string.notification_channel_title)
- .setResultListener(mHostFragment, 0)
- .setSourceMetricsCategory(
- SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
- .launch();
- return true;
- });
- channelsBypassingDnd.add(pref);
- showEmptyState = false;
- }
-
- mPreferenceScreen.removeAll();
- if (channelsBypassingDnd.size() > 0) {
- for (Preference prefToAdd : channelsBypassingDnd) {
- mPreferenceScreen.addPreference(prefToAdd);
+ pref.setTitle(BidiFormatter.getInstance().unicodeWrap(app.label));
+ pref.setIcon(app.icon);
+ if (appChannels > appChannelsBypassingDnd) {
+ pref.setSummary(R.string.zen_mode_bypassing_apps_summary_some);
+ } else {
+ pref.setSummary(R.string.zen_mode_bypassing_apps_summary_all);
}
- }
- if (showEmptyState) {
- Preference pref = new Preference(mPrefContext);
- pref.setTitle(R.string.zen_mode_bypassing_apps_subtext_none);
- mPreferenceScreen.addPreference(pref);
+
+ appsBypassingDnd.add(pref);
}
}
+
+ if (appsBypassingDnd.size() == 0) {
+ Preference pref = mPreferenceCategory.findPreference(KEY_NO_APPS);
+ if (pref == null) {
+ pref = new Preference(mPrefContext);
+ pref.setKey(KEY_NO_APPS);
+ pref.setTitle(R.string.zen_mode_bypassing_apps_none);
+ }
+ appsBypassingDnd.add(pref);
+ }
+
+ if (hasAppListChanged(appsBypassingDnd, mPreferenceCategory)) {
+ mPreferenceCategory.removeAll();
+ for (Preference prefToAdd : appsBypassingDnd) {
+ mPreferenceCategory.addPreference(prefToAdd);
+ }
+ }
+ }
+
+ static boolean hasAppListChanged(List<Preference> newAppPreferences,
+ PreferenceCategory preferenceCategory) {
+ if (newAppPreferences.size() != preferenceCategory.getPreferenceCount()) {
+ return true;
+ }
+
+ for (int i = 0; i < newAppPreferences.size(); i++) {
+ Preference newAppPref = newAppPreferences.get(i);
+ Preference pref = preferenceCategory.getPreference(i);
+ if (!Objects.equals(newAppPref.getKey(), pref.getKey())) {
+ return true;
+ }
+ }
+ return false;
+
+ }
+
+ /**
+ * Create a unique key to idenfity an AppPreference
+ */
+ static String getKey(String pkg) {
+ return pkg;
}
private final ApplicationsState.Callbacks mAppSessionCallbacks =
@@ -170,27 +203,27 @@
@Override
public void onRunningStateChanged(boolean running) {
- updateNotificationChannelList();
+ updateAppList();
}
@Override
public void onPackageListChanged() {
- updateNotificationChannelList();
+ updateAppList();
}
@Override
public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
- updateNotificationChannelList(apps);
+ updateAppList(apps);
}
@Override
public void onPackageIconChanged() {
- updateNotificationChannelList();
+ updateAppList();
}
@Override
public void onPackageSizeChanged(String packageName) {
- updateNotificationChannelList();
+ updateAppList();
}
@Override
@@ -198,12 +231,12 @@
@Override
public void onLauncherInfoChanged() {
- updateNotificationChannelList();
+ updateAppList();
}
@Override
public void onLoadEntriesCompleted() {
- updateNotificationChannelList();
+ updateAppList();
}
};
}
diff --git a/src/com/android/settings/notification/zen/ZenModeBackend.java b/src/com/android/settings/notification/zen/ZenModeBackend.java
index bb406c1..9291d55 100644
--- a/src/com/android/settings/notification/zen/ZenModeBackend.java
+++ b/src/com/android/settings/notification/zen/ZenModeBackend.java
@@ -287,44 +287,15 @@
protected int getAlarmsTotalSilencePeopleSummary(int category) {
if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) {
- return R.string.zen_mode_from_none_messages;
+ return R.string.zen_mode_from_none;
} else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS){
- return R.string.zen_mode_from_none_calls;
+ return R.string.zen_mode_from_none;
} else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS) {
return R.string.zen_mode_from_no_conversations;
}
return R.string.zen_mode_from_none;
}
- protected int getContactsSummary(int category) {
- int contactType = -1;
- if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) {
- if (isPriorityCategoryEnabled(category)) {
- contactType = getPriorityMessageSenders();
- }
- } else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) {
- if (isPriorityCategoryEnabled(category)) {
- contactType = getPriorityCallSenders();
- }
- }
-
- switch (contactType) {
- case NotificationManager.Policy.PRIORITY_SENDERS_ANY:
- return R.string.zen_mode_from_anyone;
- case NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS:
- return R.string.zen_mode_from_contacts;
- case NotificationManager.Policy.PRIORITY_SENDERS_STARRED:
- return R.string.zen_mode_from_starred;
- case SOURCE_NONE:
- default:
- if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) {
- return R.string.zen_mode_from_none_messages;
- } else {
- return R.string.zen_mode_from_none_calls;
- }
- }
- }
-
protected int getConversationSummary() {
int conversationType = getPriorityConversationSenders();
@@ -366,7 +337,7 @@
return R.string.zen_mode_from_starred;
case ZenPolicy.PEOPLE_TYPE_NONE:
default:
- return R.string.zen_mode_from_none_messages;
+ return R.string.zen_mode_from_none;
}
}
@@ -384,20 +355,6 @@
}
}
- protected static int getSettingFromPrefKey(String key) {
- switch (key) {
- case ZEN_MODE_FROM_ANYONE:
- return NotificationManager.Policy.PRIORITY_SENDERS_ANY;
- case ZEN_MODE_FROM_CONTACTS:
- return NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS;
- case ZEN_MODE_FROM_STARRED:
- return NotificationManager.Policy.PRIORITY_SENDERS_STARRED;
- case ZEN_MODE_FROM_NONE:
- default:
- return SOURCE_NONE;
- }
- }
-
public boolean removeZenRule(String ruleId) {
return NotificationManager.from(mContext).removeAutomaticZenRule(ruleId);
}
diff --git a/src/com/android/settings/notification/zen/ZenModeBypassingAppsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeBypassingAppsPreferenceController.java
index 7459394..6b1f46c 100644
--- a/src/com/android/settings/notification/zen/ZenModeBypassingAppsPreferenceController.java
+++ b/src/com/android/settings/notification/zen/ZenModeBypassingAppsPreferenceController.java
@@ -6,6 +6,7 @@
import android.icu.text.ListFormatter;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArraySet;
import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment;
@@ -21,6 +22,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
/**
* Controls the summary for preference found at:
@@ -102,7 +104,7 @@
return;
}
- List<String> appsBypassingDnd = new ArrayList<>();
+ Set<String> appsBypassingDnd = new ArraySet<>();
for (ApplicationsState.AppEntry entry : apps) {
String pkg = entry.info.packageName;
for (NotificationChannel channel : mNotificationBackend
@@ -116,7 +118,8 @@
}
}
- if (appsBypassingDnd.size() == 0) {
+ final int numAppsBypassingDnd = appsBypassingDnd.size();
+ if (numAppsBypassingDnd == 0) {
mSummary = mContext.getResources().getString(
R.string.zen_mode_bypassing_apps_subtext_none);
refreshSummary(mPreference);
@@ -124,18 +127,20 @@
}
List<String> displayAppsBypassing = new ArrayList<>();
- if (appsBypassingDnd.size() <= 2) {
- displayAppsBypassing = appsBypassingDnd;
+ if (numAppsBypassingDnd <= 2) {
+ displayAppsBypassing.addAll(appsBypassingDnd);
} else {
- displayAppsBypassing.add(appsBypassingDnd.get(0));
- displayAppsBypassing.add(appsBypassingDnd.get(1));
+ String[] appsBypassingDndArr =
+ appsBypassingDnd.toArray(new String[numAppsBypassingDnd]);
+ displayAppsBypassing.add(appsBypassingDndArr[0]);
+ displayAppsBypassing.add(appsBypassingDndArr[1]);
displayAppsBypassing.add(mContext.getResources().getString(
R.string.zen_mode_apps_bypassing_list_count,
- appsBypassingDnd.size() - 2));
+ numAppsBypassingDnd - 2));
}
mSummary = mContext.getResources().getQuantityString(
R.plurals.zen_mode_bypassing_apps_subtext,
- appsBypassingDnd.size(),
+ numAppsBypassingDnd,
ListFormatter.getInstance().format(displayAppsBypassing));
refreshSummary(mPreference);
}
diff --git a/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java b/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java
index bd1a041..89a80f0 100644
--- a/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java
+++ b/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java
@@ -24,6 +24,7 @@
import androidx.fragment.app.Fragment;
import com.android.settings.R;
+import com.android.settings.notification.NotificationBackend;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.Indexable;
@@ -46,13 +47,16 @@
} else {
app = null;
}
- return buildPreferenceControllers(context, app, this);
+ return buildPreferenceControllers(context, app, this, new NotificationBackend());
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
- Application app, Fragment host) {
+ Application app, Fragment host, NotificationBackend notificationBackend) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
- controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host));
+ controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host,
+ notificationBackend));
+ controllers.add(new ZenModeAddBypassingAppsPreferenceController(context, app, host,
+ notificationBackend));
return controllers;
}
@@ -80,7 +84,7 @@
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
- return buildPreferenceControllers(context, null, null);
+ return buildPreferenceControllers(context, null, null, null);
}
};
}
diff --git a/src/com/android/settings/notification/zen/ZenModeConversationsSettings.java b/src/com/android/settings/notification/zen/ZenModeConversationsSettings.java
index 4307538..3daefd5 100644
--- a/src/com/android/settings/notification/zen/ZenModeConversationsSettings.java
+++ b/src/com/android/settings/notification/zen/ZenModeConversationsSettings.java
@@ -30,7 +30,7 @@
import java.util.List;
/**
- * Settings > Sound > Do Not Disturb > Conversationss
+ * Settings > Sound > Do Not Disturb > Conversations
*/
@SearchIndexable
public class ZenModeConversationsSettings extends ZenModeSettingsBase {
diff --git a/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceController.java
deleted file mode 100644
index 6b538dc..0000000
--- a/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceController.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2018 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.zen;
-
-import android.app.NotificationManager;
-import android.content.Context;
-import android.provider.Settings;
-import android.text.TextUtils;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.ListPreference;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-
-public class ZenModePriorityCallsPreferenceController extends AbstractZenModePreferenceController
- implements Preference.OnPreferenceChangeListener {
-
- protected static final String KEY = "zen_mode_calls";
- private final ZenModeBackend mBackend;
- private ListPreference mPreference;
- private final String[] mListValues;
-
- public ZenModePriorityCallsPreferenceController(Context context, Lifecycle lifecycle) {
- super(context, KEY, lifecycle);
- mBackend = ZenModeBackend.getInstance(context);
- mListValues = context.getResources().getStringArray(R.array.zen_mode_contacts_values);
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY;
- }
-
- @Override
- public boolean isAvailable() {
- return true;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(KEY);
- }
-
- @Override
- public void updateState(Preference preference) {
- super.updateState(preference);
- updateFromContactsValue(preference);
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object selectedContactsFrom) {
- mBackend.saveSenders(NotificationManager.Policy.PRIORITY_CATEGORY_CALLS,
- ZenModeBackend.getSettingFromPrefKey(selectedContactsFrom.toString()));
- updateFromContactsValue(preference);
- return true;
- }
-
- private void updateFromContactsValue(Preference preference) {
- mPreference = (ListPreference) preference;
- switch (getZenMode()) {
- case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
- case Settings.Global.ZEN_MODE_ALARMS:
- mPreference.setEnabled(false);
- mPreference.setValue(ZenModeBackend.ZEN_MODE_FROM_NONE);
- mPreference.setSummary(mBackend.getAlarmsTotalSilencePeopleSummary(
- NotificationManager.Policy.PRIORITY_CATEGORY_CALLS));
- break;
- default:
- preference.setEnabled(true);
- preference.setSummary(mBackend.getContactsSummary(
- NotificationManager.Policy.PRIORITY_CATEGORY_CALLS));
-
- final String currentVal = ZenModeBackend.getKeyFromSetting(
- mBackend.getPriorityCallSenders());
- mPreference.setValue(mListValues[getIndexOfSendersValue(currentVal)]);
- }
- }
-
- @VisibleForTesting
- protected int getIndexOfSendersValue(String currentVal) {
- int index = 3; // defaults to "none" based on R.array.zen_mode_contacts_values
- for (int i = 0; i < mListValues.length; i++) {
- if (TextUtils.equals(currentVal, mListValues[i])) {
- return i;
- }
- }
-
- return index;
- }
-}
diff --git a/src/com/android/settings/notification/zen/ZenModePriorityConversationsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModePriorityConversationsPreferenceController.java
index b6824a9..4ae16bc 100644
--- a/src/com/android/settings/notification/zen/ZenModePriorityConversationsPreferenceController.java
+++ b/src/com/android/settings/notification/zen/ZenModePriorityConversationsPreferenceController.java
@@ -16,11 +16,16 @@
package com.android.settings.notification.zen;
+import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_GONE;
+import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_SETTING;
+
import android.app.NotificationManager;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.AsyncTask;
import android.service.notification.ConversationChannelWrapper;
+import android.view.View;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -28,7 +33,10 @@
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
+import com.android.settings.notification.app.ConversationListSettings;
+import com.android.settings.widget.RadioButtonPreferenceWithExtraWidget;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.RadioButtonPreference;
@@ -51,6 +59,7 @@
private int mNumConversations = UNSET;
private PreferenceCategory mPreferenceCategory;
private List<RadioButtonPreference> mRadioButtonPreferences = new ArrayList<>();
+ private Context mPreferenceScreenContext;
public ZenModePriorityConversationsPreferenceController(Context context, String key,
Lifecycle lifecycle, NotificationBackend notificationBackend) {
@@ -60,6 +69,7 @@
@Override
public void displayPreference(PreferenceScreen screen) {
+ mPreferenceScreenContext = screen.getContext();
mPreferenceCategory = screen.findPreference(getPreferenceKey());
if (mPreferenceCategory.findPreference(KEY_ALL) == null) {
makeRadioPreference(KEY_ALL, R.string.zen_mode_from_all_conversations);
@@ -125,7 +135,7 @@
R.string.zen_mode_conversations_count_none);
} else {
return mContext.getResources().getQuantityString(
- R.plurals.zen_mode_conversations_count, numConversations);
+ R.plurals.zen_mode_conversations_count, numConversations, numConversations);
}
}
@@ -136,14 +146,27 @@
protected Void doInBackground(Void... unused) {
ParceledListSlice<ConversationChannelWrapper> allConversations =
mNotificationBackend.getConversations(false);
+ int numConversations = 0;
if (allConversations != null) {
- mNumConversations = allConversations.getList().size();
+ for (ConversationChannelWrapper conversation : allConversations.getList()) {
+ if (!conversation.getNotificationChannel().isDemoted()) {
+ numConversations++;
+ }
+ }
}
- ParceledListSlice<ConversationChannelWrapper> importantConversations =
+ mNumConversations = numConversations;
+
+ ParceledListSlice<ConversationChannelWrapper> impConversations =
mNotificationBackend.getConversations(true);
- if (importantConversations != null) {
- mNumImportantConversations = importantConversations.getList().size();
+ int numImportantConversations = 0;
+ if (impConversations != null) {
+ for (ConversationChannelWrapper conversation : impConversations.getList()) {
+ if (!conversation.getNotificationChannel().isDemoted()) {
+ numImportantConversations++;
+ }
+ }
}
+ mNumImportantConversations = numImportantConversations;
return null;
}
@@ -158,7 +181,14 @@
}
private RadioButtonPreference makeRadioPreference(String key, int titleId) {
- RadioButtonPreference pref = new RadioButtonPreference(mPreferenceCategory.getContext());
+ RadioButtonPreferenceWithExtraWidget pref =
+ new RadioButtonPreferenceWithExtraWidget(mPreferenceCategory.getContext());
+ if (KEY_ALL.equals(key) || KEY_IMPORTANT.equals(key)) {
+ pref.setExtraWidgetOnClickListener(mConversationSettingsWidgetClickListener);
+ pref.setExtraWidgetVisibility(EXTRA_WIDGET_VISIBILITY_SETTING);
+ } else {
+ pref.setExtraWidgetVisibility(EXTRA_WIDGET_VISIBILITY_GONE);
+ }
pref.setKey(key);
pref.setTitle(titleId);
pref.setOnClickListener(mRadioButtonClickListener);
@@ -167,6 +197,17 @@
return pref;
}
+ private View.OnClickListener mConversationSettingsWidgetClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ new SubSettingLauncher(mPreferenceScreenContext)
+ .setDestination(ConversationListSettings.class.getName())
+ .setSourceMetricsCategory(SettingsEnums.DND_CONVERSATIONS)
+ .launch();
+ }
+ };
+
private RadioButtonPreference.OnClickListener mRadioButtonClickListener =
new RadioButtonPreference.OnClickListener() {
@Override
diff --git a/src/com/android/settings/notification/zen/ZenModeSettings.java b/src/com/android/settings/notification/zen/ZenModeSettings.java
index c92099a..d67c353 100644
--- a/src/com/android/settings/notification/zen/ZenModeSettings.java
+++ b/src/com/android/settings/notification/zen/ZenModeSettings.java
@@ -153,7 +153,7 @@
String getCallsSettingSummary(Policy policy) {
List<String> enabledCategories = getEnabledCategories(policy,
category -> PRIORITY_CATEGORY_CALLS == category
- || PRIORITY_CATEGORY_REPEAT_CALLERS == category, false);
+ || PRIORITY_CATEGORY_REPEAT_CALLERS == category, true);
int numCategories = enabledCategories.size();
if (numCategories == 0) {
return mContext.getString(R.string.zen_mode_from_none_calls);
@@ -303,10 +303,19 @@
}
} else if (category == Policy.PRIORITY_CATEGORY_CALLS) {
if (policy.priorityCallSenders == Policy.PRIORITY_SENDERS_ANY) {
+ if (isFirst) {
+ return mContext.getString(R.string.zen_mode_from_anyone);
+ }
return mContext.getString(R.string.zen_mode_all_callers);
} else if (policy.priorityCallSenders == Policy.PRIORITY_SENDERS_CONTACTS){
+ if (isFirst) {
+ return mContext.getString(R.string.zen_mode_from_contacts);
+ }
return mContext.getString(R.string.zen_mode_contacts_callers);
} else {
+ if (isFirst) {
+ return mContext.getString(R.string.zen_mode_from_starred);
+ }
return mContext.getString(R.string.zen_mode_starred_callers);
}
} else if (category == Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) {
diff --git a/tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java
index ad2e74a..6cc20d7 100644
--- a/tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java
@@ -16,19 +16,8 @@
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;
@@ -37,24 +26,17 @@
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;
@@ -67,7 +49,6 @@
import org.robolectric.shadows.ShadowApplication;
import java.util.ArrayList;
-import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class ConversationListPreferenceControllerTest {
@@ -116,8 +97,6 @@
list.add(ccw);
mController.populateList(list, outerContainer);
-
- verify(outerContainer).setVisible(true);
verify(outerContainer, times(1)).addPreference(any());
}
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java
new file mode 100644
index 0000000..52f2c51
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.zen;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ParceledListSlice;
+
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.settings.notification.NotificationBackend;
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeAddBypassingAppsPreferenceControllerTest {
+
+ @Mock
+ private NotificationBackend mBackend;
+ @Mock
+ private PreferenceCategory mPreferenceCategory;
+ @Mock
+ private ApplicationsState mApplicationState;
+ private ZenModeAddBypassingAppsPreferenceController mController;
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ mController = new ZenModeAddBypassingAppsPreferenceController(
+ mContext, null, mock(Fragment.class), mBackend);
+ mController.mPreferenceCategory = mPreferenceCategory;
+ mController.mApplicationsState = mApplicationState;
+ mController.mPrefContext = mContext;
+ }
+
+ @Test
+ public void testIsAvailable() {
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void testUpdateAppList() {
+ // GIVEN there's an app with bypassing channels, app without any channels, and then an app
+ // with notification channels but none that can bypass DND
+ ApplicationsState.AppEntry appWithBypassingChannels =
+ mock(ApplicationsState.AppEntry.class);
+ appWithBypassingChannels.info = new ApplicationInfo();
+ appWithBypassingChannels.info.packageName = "appWithBypassingChannels";
+ appWithBypassingChannels.info.uid = 0;
+ when(mBackend.getNotificationChannelsBypassingDnd(
+ appWithBypassingChannels.info.packageName,
+ appWithBypassingChannels.info.uid))
+ .thenReturn(new ParceledListSlice<>(
+ Arrays.asList(mock(NotificationChannel.class))));
+ when(mBackend.getChannelCount(
+ appWithBypassingChannels.info.packageName,
+ appWithBypassingChannels.info.uid))
+ .thenReturn(5);
+
+ ApplicationsState.AppEntry appWithoutChannels = mock(ApplicationsState.AppEntry.class);
+ appWithoutChannels.info = new ApplicationInfo();
+ appWithoutChannels.info.packageName = "appWithoutChannels";
+ appWithoutChannels.info.uid = 0;
+ when(mBackend.getChannelCount(
+ appWithoutChannels.info.packageName,
+ appWithoutChannels.info.uid))
+ .thenReturn(0);
+ when(mBackend.getNotificationChannelsBypassingDnd(
+ appWithoutChannels.info.packageName,
+ appWithoutChannels.info.uid))
+ .thenReturn(new ParceledListSlice<>(new ArrayList<>()));
+
+ ApplicationsState.AppEntry appWithChannelsNoneBypassing =
+ mock(ApplicationsState.AppEntry.class);
+ appWithChannelsNoneBypassing.info = new ApplicationInfo();
+ appWithChannelsNoneBypassing.info.packageName = "appWithChannelsNoneBypassing";
+ appWithChannelsNoneBypassing.info.uid = 0;
+ when(mBackend.getChannelCount(
+ appWithChannelsNoneBypassing.info.packageName,
+ appWithChannelsNoneBypassing.info.uid))
+ .thenReturn(5);
+ when(mBackend.getNotificationChannelsBypassingDnd(
+ appWithChannelsNoneBypassing.info.packageName,
+ appWithChannelsNoneBypassing.info.uid))
+ .thenReturn(new ParceledListSlice<>(new ArrayList<>()));
+
+ List<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
+ appEntries.add(appWithBypassingChannels);
+ appEntries.add(appWithoutChannels);
+ appEntries.add(appWithChannelsNoneBypassing);
+
+ // WHEN the controller updates the app list with the app entries
+ mController.updateAppList(appEntries);
+
+ // THEN only the appWithChannelsNoneBypassing makes it to the app list
+ ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
+ verify(mPreferenceCategory).addPreference(prefCaptor.capture());
+
+ Preference pref = prefCaptor.getValue();
+ assertThat(pref.getKey()).isEqualTo(
+ ZenModeAllBypassingAppsPreferenceController.getKey(
+ appWithChannelsNoneBypassing.info.packageName));
+ }
+
+ @Test
+ public void testUpdateAppList_nullApps() {
+ mController.updateAppList(null);
+ verify(mPreferenceCategory, never()).addPreference(any());
+ }
+
+ @Test
+ public void testUpdateAppList_emptyAppList() {
+ // WHEN there are no apps
+ mController.updateAppList(new ArrayList<>());
+
+ // THEN only the appWithChannelsNoneBypassing makes it to the app list
+ ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
+ verify(mPreferenceCategory).addPreference(prefCaptor.capture());
+
+ Preference pref = prefCaptor.getValue();
+ assertThat(pref.getKey()).isEqualTo(
+ ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceControllerTest.java
index c2d54f4..1fff49c 100644
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceControllerTest.java
@@ -19,6 +19,8 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -30,25 +32,25 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ParceledListSlice;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
import com.android.settings.notification.NotificationBackend;
-import com.android.settings.notification.zen.ZenModeAllBypassingAppsPreferenceController;
import com.android.settingslib.applications.ApplicationsState;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
-import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
-import androidx.fragment.app.Fragment;
-import androidx.preference.PreferenceScreen;
-
@RunWith(RobolectricTestRunner.class)
public class ZenModeAllBypassingAppsPreferenceControllerTest {
private ZenModeAllBypassingAppsPreferenceController mController;
@@ -57,7 +59,7 @@
@Mock
private NotificationBackend mBackend;
@Mock
- private PreferenceScreen mPreferenceScreen;
+ private PreferenceCategory mPreferenceCategory;
@Mock
private ApplicationsState mApplicationState;
@@ -67,11 +69,10 @@
mContext = RuntimeEnvironment.application;
mController = new ZenModeAllBypassingAppsPreferenceController(
- mContext, null, mock(Fragment.class));
- mController.mPreferenceScreen = mPreferenceScreen;
+ mContext, null, mock(Fragment.class), mBackend);
+ mController.mPreferenceCategory = mPreferenceCategory;
mController.mApplicationsState = mApplicationState;
mController.mPrefContext = mContext;
- ReflectionHelpers.setField(mController, "mNotificationBackend", mBackend);
}
@Test
@@ -80,36 +81,49 @@
}
@Test
- public void testUpdateNotificationChannelList() {
- ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class);
- entry.info = new ApplicationInfo();
- entry.info.packageName = "test";
- entry.info.uid = 0;
+ public void testUpdateAppList() {
+ // WHEN there's two apps with notification channels that bypass DND
+ ApplicationsState.AppEntry entry1 = mock(ApplicationsState.AppEntry.class);
+ entry1.info = new ApplicationInfo();
+ entry1.info.packageName = "test";
+ entry1.info.uid = 0;
+
+ ApplicationsState.AppEntry entry2 = mock(ApplicationsState.AppEntry.class);
+ entry2.info = new ApplicationInfo();
+ entry2.info.packageName = "test2";
+ entry2.info.uid = 0;
List<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
- appEntries.add(entry);
-
+ appEntries.add(entry1);
+ appEntries.add(entry2);
List<NotificationChannel> channelsBypassing = new ArrayList<>();
channelsBypassing.add(mock(NotificationChannel.class));
channelsBypassing.add(mock(NotificationChannel.class));
- channelsBypassing.add(mock(NotificationChannel.class));
+ when(mBackend.getNotificationChannelsBypassingDnd(anyString(),
+ anyInt())).thenReturn(new ParceledListSlice<>(channelsBypassing));
- when(mBackend.getNotificationChannelsBypassingDnd(entry.info.packageName,
- entry.info.uid)).thenReturn(new ParceledListSlice<>(channelsBypassing));
-
- mController.updateNotificationChannelList(appEntries);
- verify(mPreferenceScreen, times(3)).addPreference(any());
+ // THEN there's are two preferences
+ mController.updateAppList(appEntries);
+ verify(mPreferenceCategory, times(2)).addPreference(any());
}
@Test
- public void testUpdateNotificationChannelList_nullChannels() {
- mController.updateNotificationChannelList(null);
- verify(mPreferenceScreen, never()).addPreference(any());
+ public void testUpdateAppList_nullApps() {
+ mController.updateAppList(null);
+ verify(mPreferenceCategory, never()).addPreference(any());
}
@Test
- public void testUpdateNotificationChannelList_emptyChannelsList() {
- mController.updateNotificationChannelList(new ArrayList<>());
- verify(mPreferenceScreen, never()).addPreference(any());
+ public void testUpdateAppList_emptyAppList() {
+ // WHEN there are no apps
+ mController.updateAppList(new ArrayList<>());
+
+ // THEN only the appWithChannelsNoneBypassing makes it to the app list
+ ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
+ verify(mPreferenceCategory).addPreference(prefCaptor.capture());
+
+ Preference pref = prefCaptor.getValue();
+ assertThat(pref.getKey()).isEqualTo(
+ ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS);
}
}
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceControllerTest.java
deleted file mode 100644
index d1f80b8..0000000
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceControllerTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2018 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.zen;
-
-import static android.provider.Settings.Global.ZEN_MODE;
-import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
-import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.NotificationManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.provider.Settings;
-
-import androidx.preference.ListPreference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowApplication;
-import org.robolectric.util.ReflectionHelpers;
-
-@RunWith(RobolectricTestRunner.class)
-public class ZenModePriorityCallsPreferenceControllerTest {
-
- private ZenModePriorityCallsPreferenceController mController;
-
- @Mock
- private ZenModeBackend mBackend;
- @Mock
- private NotificationManager mNotificationManager;
- @Mock
- private ListPreference mockPref;
- @Mock
- private NotificationManager.Policy mPolicy;
- @Mock
- private PreferenceScreen mPreferenceScreen;
- private ContentResolver mContentResolver;
- private Context mContext;
-
- /**
- * Array Values Key
- * 0: anyone
- * 1: contacts
- * 2: starred
- * 3: none
- */
- private String[] mValues;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- ShadowApplication shadowApplication = ShadowApplication.getInstance();
- shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager);
-
- mContext = RuntimeEnvironment.application;
- mValues = mContext.getResources().getStringArray(R.array.zen_mode_contacts_values);
- mContentResolver = RuntimeEnvironment.application.getContentResolver();
- when(mNotificationManager.getNotificationPolicy()).thenReturn(mPolicy);
-
- when(mBackend.getPriorityCallSenders())
- .thenReturn(NotificationManager.Policy.PRIORITY_SENDERS_STARRED);
- when(mBackend.getAlarmsTotalSilencePeopleSummary(
- NotificationManager.Policy.PRIORITY_CATEGORY_CALLS)).thenCallRealMethod();
- when(mBackend.getContactsSummary(NotificationManager.Policy.PRIORITY_CATEGORY_CALLS))
- .thenCallRealMethod();
-
- mController = new ZenModePriorityCallsPreferenceController(mContext, mock(Lifecycle.class));
- ReflectionHelpers.setField(mController, "mBackend", mBackend);
-
- when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn(mockPref);
- mController.displayPreference(mPreferenceScreen);
- }
-
- @Test
- public void updateState_TotalSilence() {
- Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_NO_INTERRUPTIONS);
-
- when(mBackend.isPriorityCategoryEnabled(
- NotificationManager.Policy.PRIORITY_CATEGORY_CALLS))
- .thenReturn(false);
- final ListPreference mockPref = mock(ListPreference.class);
- mController.updateState(mockPref);
-
- verify(mockPref).setEnabled(false);
- verify(mockPref).setSummary(R.string.zen_mode_from_none_calls);
- }
-
- @Test
- public void updateState_AlarmsOnly() {
- Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_ALARMS);
-
- final ListPreference mockPref = mock(ListPreference.class);
- mController.updateState(mockPref);
-
- verify(mockPref).setEnabled(false);
- verify(mockPref).setSummary(R.string.zen_mode_from_none_calls);
- }
-
- @Test
- public void updateState_Priority() {
- Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- when(mBackend.isPriorityCategoryEnabled(
- NotificationManager.Policy.PRIORITY_CATEGORY_CALLS))
- .thenReturn(true);
-
- mController.updateState(mockPref);
-
- verify(mockPref).setEnabled(true);
- verify(mockPref).setSummary(R.string.zen_mode_from_starred);
- }
-
- @Test
- public void onPreferenceChange_setSelectedContacts_any() {
- Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- when(mBackend.getPriorityCallSenders()).thenReturn(
- NotificationManager.Policy.PRIORITY_SENDERS_ANY);
- mController.updateState(mockPref);
- verify(mockPref).setValue(mValues[mController.getIndexOfSendersValue(
- ZenModeBackend.ZEN_MODE_FROM_ANYONE)]);
- }
-
- @Test
- public void onPreferenceChange_setSelectedContacts_none() {
- Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- when(mBackend.getPriorityCallSenders()).thenReturn(ZenModeBackend.SOURCE_NONE);
- mController.updateState(mockPref);
- verify(mockPref).setValue(mValues[mController.getIndexOfSendersValue(
- ZenModeBackend.ZEN_MODE_FROM_NONE)]);
- }
-
- @Test
- public void onPreferenceChange_setSelectedContacts_starred() {
- Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- when(mBackend.getPriorityCallSenders()).thenReturn(
- NotificationManager.Policy.PRIORITY_SENDERS_STARRED);
- mController.updateState(mockPref);
- verify(mockPref).setValue(mValues[mController.getIndexOfSendersValue(
- ZenModeBackend.ZEN_MODE_FROM_STARRED)]);
- }
-
- @Test
- public void onPreferenceChange_setSelectedContacts_contacts() {
- Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- when(mBackend.getPriorityCallSenders()).thenReturn(
- NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS);
- mController.updateState(mockPref);
- verify(mockPref).setValue(mValues[mController.getIndexOfSendersValue(
- ZenModeBackend.ZEN_MODE_FROM_CONTACTS)]);
- }
-}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsTest.java
index d3c3a85..ea75056 100644
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsTest.java
@@ -79,13 +79,13 @@
public void testGetCallsSettingSummary_contacts() {
Policy policy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | Policy.PRIORITY_CATEGORY_CALLS,
Policy.PRIORITY_SENDERS_CONTACTS, 0, 0);
- assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("Allow from contacts");
+ assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("Contacts");
}
@Test
public void testGetCallsSettingSummary_repeatCallers() {
Policy policy = new Policy(Policy.PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0);
- assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("Allow from repeat callers");
+ assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("Repeat callers");
}
@Test
@@ -94,7 +94,7 @@
Policy.PRIORITY_CATEGORY_REPEAT_CALLERS | Policy.PRIORITY_CATEGORY_CALLS,
Policy.PRIORITY_SENDERS_STARRED, 0, 0);
assertThat(mBuilder.getCallsSettingSummary(policy))
- .isEqualTo("Allow from starred contacts and repeat callers");
+ .isEqualTo("Starred contacts and repeat callers");
}
@Test