Merge "DND Bypassing Apps redesign" into rvc-dev am: 7440bf7034

Change-Id: If875b3ad36863daff5e408a142caade2b44a3f18
This commit is contained in:
TreeHugger Robot
2020-03-26 21:06:31 +00:00
committed by Automerger Merge Worker
26 changed files with 1128 additions and 523 deletions

View File

@@ -6908,7 +6908,8 @@
<!-- Summary for add user action, when it's disabled [CHAR LIMIT=100] --> <!-- 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> <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> <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] --> <!-- 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> <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> <string name="zen_mode_bypassing_apps">Allow apps to override</string>
<!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND header --> <!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND header -->
<string name="zen_mode_bypassing_apps_header">Apps that can interrupt</string> <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 --> <!-- [CHAR LIMIT=120] Zen mode settings: No apps are bypassing DND -->
<string name="zen_mode_bypassing_apps_subtext_none">No apps can interrupt</string> <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 --> <!-- [CHAR LIMIT=120] Zen mode settings: Allow apps to bypass DND -->
<plurals name="zen_mode_bypassing_apps_subtext"> <plurals name="zen_mode_bypassing_apps_subtext">
<item quantity="one"><xliff:g id="app_name" example="Nest">%s</xliff:g> can interrupt</item> <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> <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--> <!-- [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> <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 --> <!-- [CHAR LIMIT=120] Zen mode settings: Summary for sound interruption settings -->
<plurals name="zen_mode_other_sounds_summary"> <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) --> <!-- [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> <string name="zen_mode_repeat_callers_list">repeat callers</string>
<!-- [CHAR LIMIT=50] Zen mode settings: calls summary --> <!-- [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 --> <!-- [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 --> <!-- [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> <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>

View File

@@ -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>

View File

@@ -14,10 +14,11 @@
limitations under the License. limitations under the License.
--> -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen
xmlns:settings="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"
android:key="conversation_list" xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="zen_mode_conversations_title"> android:key="conversation_list"
android:title="@string/zen_mode_conversations_title">
<PreferenceCategory <PreferenceCategory
android:key="important_conversations" android:key="important_conversations"

View File

@@ -17,4 +17,21 @@
<PreferenceScreen <PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android" 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -33,6 +33,11 @@ import android.os.Bundle;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; 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.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.AppInfoBase;
@@ -43,14 +48,8 @@ import com.android.settingslib.RestrictedSwitchPreference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; 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 { public class ChannelListPreferenceController extends NotificationPreferenceController {
private static final String KEY = "channels"; private static final String KEY = "channels";
@@ -94,7 +93,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
@Override @Override
protected Void doInBackground(Void... unused) { protected Void doInBackground(Void... unused) {
mChannelGroupList = mBackend.getGroups(mAppRow.pkg, mAppRow.uid).getList(); mChannelGroupList = mBackend.getGroups(mAppRow.pkg, mAppRow.uid).getList();
Collections.sort(mChannelGroupList, mChannelGroupComparator); Collections.sort(mChannelGroupList, CHANNEL_GROUP_COMPARATOR);
return null; return null;
} }
@@ -142,7 +141,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
} }
if (!group.isBlocked()) { if (!group.isBlocked()) {
final List<NotificationChannel> channels = group.getChannels(); final List<NotificationChannel> channels = group.getChannels();
Collections.sort(channels, mChannelComparator); Collections.sort(channels, CHANNEL_COMPARATOR);
int N = channels.size(); int N = channels.size();
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
final NotificationChannel channel = channels.get(i); final NotificationChannel channel = channels.get(i);
@@ -274,7 +273,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
} }
} else { } else {
final List<NotificationChannel> channels = group.getChannels(); final List<NotificationChannel> channels = group.getChannels();
Collections.sort(channels, mChannelComparator); Collections.sort(channels, CHANNEL_COMPARATOR);
int N = channels.size(); int N = channels.size();
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
final NotificationChannel channel = channels.get(i); final NotificationChannel channel = channels.get(i);
@@ -283,33 +282,4 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
} }
} }
} }
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());
};
} }

View File

@@ -35,6 +35,7 @@ import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
import java.util.Comparator;
import java.util.Objects; import java.util.Objects;
/** /**
@@ -172,4 +173,31 @@ public abstract class NotificationPreferenceController extends AbstractPreferenc
} }
return Objects.equals(NotificationChannel.DEFAULT_CHANNEL_ID, mChannel.getId()); 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());
};
} }

View File

@@ -44,7 +44,7 @@ public class ZenCustomRuleCallsSettings extends ZenCustomRuleSettingsBase {
@Override @Override
protected int getPreferenceScreenResId() { 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 @Override

View File

@@ -37,7 +37,7 @@ public class ZenCustomRuleMessagesSettings extends ZenCustomRuleSettingsBase {
@Override @Override
protected int getPreferenceScreenResId() { 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 @Override

View File

@@ -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();
}
};
}

View File

@@ -17,57 +17,59 @@
package com.android.settings.notification.zen; package com.android.settings.notification.zen;
import android.app.Application; import android.app.Application;
import android.app.NotificationChannel;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter; import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.app.ChannelNotificationSettings;
import com.android.settings.notification.NotificationBackend; import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.app.AppChannelsBypassingDndSettings;
import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.apppreference.AppPreference; import com.android.settingslib.widget.apppreference.AppPreference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* Adds a preference to the PreferenceScreen for each notification channel that can bypass DND. * Adds a preference to the PreferenceScreen for each notification channel that can bypass DND.
*/ */
public class ZenModeAllBypassingAppsPreferenceController extends AbstractPreferenceController public class ZenModeAllBypassingAppsPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin { 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 ApplicationsState mApplicationsState;
@VisibleForTesting PreferenceScreen mPreferenceScreen; @VisibleForTesting PreferenceCategory mPreferenceCategory;
@VisibleForTesting Context mPrefContext; @VisibleForTesting Context mPrefContext;
private ApplicationsState.Session mAppSession; private ApplicationsState.Session mAppSession;
private NotificationBackend mNotificationBackend = new NotificationBackend();
private Fragment mHostFragment; private Fragment mHostFragment;
public ZenModeAllBypassingAppsPreferenceController(Context context, Application app, public ZenModeAllBypassingAppsPreferenceController(Context context, Application app,
Fragment host) { Fragment host, NotificationBackend notificationBackend) {
this(context, app == null ? null : ApplicationsState.getInstance(app), host,
this(context, app == null ? null : ApplicationsState.getInstance(app), host); notificationBackend);
} }
private ZenModeAllBypassingAppsPreferenceController(Context context, ApplicationsState appState, private ZenModeAllBypassingAppsPreferenceController(Context context, ApplicationsState appState,
Fragment host) { Fragment host, NotificationBackend notificationBackend) {
super(context); super(context);
mNotificationBackend = notificationBackend;
mApplicationsState = appState; mApplicationsState = appState;
mHostFragment = host; mHostFragment = host;
@@ -78,9 +80,9 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere
@Override @Override
public void displayPreference(PreferenceScreen screen) { public void displayPreference(PreferenceScreen screen) {
mPreferenceScreen = screen; mPreferenceCategory = screen.findPreference(KEY);
mPrefContext = mPreferenceScreen.getContext(); mPrefContext = screen.getContext();
updateNotificationChannelList(); updateAppList();
super.displayPreference(screen); super.displayPreference(screen);
} }
@@ -95,9 +97,9 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere
} }
/** /**
* 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) { if (mAppSession == null) {
return; return;
} }
@@ -105,64 +107,95 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere
ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED; ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED;
List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter, List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter,
ApplicationsState.ALPHA_COMPARATOR); ApplicationsState.ALPHA_COMPARATOR);
updateNotificationChannelList(apps); updateAppList(apps);
} }
@VisibleForTesting @VisibleForTesting
void updateNotificationChannelList(List<ApplicationsState.AppEntry> apps) { void updateAppList(List<ApplicationsState.AppEntry> apps) {
if (mPreferenceScreen == null || apps == null) { if (mPreferenceCategory == null || apps == null) {
return; return;
} }
boolean showEmptyState = true; List<Preference> appsBypassingDnd = new ArrayList<>();
for (ApplicationsState.AppEntry app : apps) {
List<Preference> channelsBypassingDnd = new ArrayList<>(); String pkg = app.info.packageName;
for (ApplicationsState.AppEntry entry : apps) { mApplicationsState.ensureIcon(app);
String pkg = entry.info.packageName; final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid);
mApplicationsState.ensureIcon(entry); final int appChannelsBypassingDnd = mNotificationBackend
for (NotificationChannel channel : mNotificationBackend .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size();
.getNotificationChannelsBypassingDnd(pkg, entry.info.uid).getList()) { if (appChannelsBypassingDnd > 0) {
if (!TextUtils.isEmpty(channel.getConversationId())) { final String key = getKey(pkg);
// conversation channels that bypass dnd will be shown on the People page // re-use previously created preference when possible
continue; 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.setTitle(BidiFormatter.getInstance().unicodeWrap(app.label));
pref.setKey(pkg + "|" + channel.getId()); pref.setIcon(app.icon);
pref.setTitle(BidiFormatter.getInstance().unicodeWrap(entry.label)); if (appChannels > appChannelsBypassingDnd) {
pref.setIcon(entry.icon); pref.setSummary(R.string.zen_mode_bypassing_apps_summary_some);
pref.setSummary(BidiFormatter.getInstance().unicodeWrap(channel.getName())); } else {
pref.setSummary(R.string.zen_mode_bypassing_apps_summary_all);
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);
} }
}
if (showEmptyState) { appsBypassingDnd.add(pref);
Preference pref = new Preference(mPrefContext);
pref.setTitle(R.string.zen_mode_bypassing_apps_subtext_none);
mPreferenceScreen.addPreference(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 = private final ApplicationsState.Callbacks mAppSessionCallbacks =
@@ -170,27 +203,27 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere
@Override @Override
public void onRunningStateChanged(boolean running) { public void onRunningStateChanged(boolean running) {
updateNotificationChannelList(); updateAppList();
} }
@Override @Override
public void onPackageListChanged() { public void onPackageListChanged() {
updateNotificationChannelList(); updateAppList();
} }
@Override @Override
public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
updateNotificationChannelList(apps); updateAppList(apps);
} }
@Override @Override
public void onPackageIconChanged() { public void onPackageIconChanged() {
updateNotificationChannelList(); updateAppList();
} }
@Override @Override
public void onPackageSizeChanged(String packageName) { public void onPackageSizeChanged(String packageName) {
updateNotificationChannelList(); updateAppList();
} }
@Override @Override
@@ -198,12 +231,12 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere
@Override @Override
public void onLauncherInfoChanged() { public void onLauncherInfoChanged() {
updateNotificationChannelList(); updateAppList();
} }
@Override @Override
public void onLoadEntriesCompleted() { public void onLoadEntriesCompleted() {
updateNotificationChannelList(); updateAppList();
} }
}; };
} }

View File

@@ -287,44 +287,15 @@ public class ZenModeBackend {
protected int getAlarmsTotalSilencePeopleSummary(int category) { protected int getAlarmsTotalSilencePeopleSummary(int category) {
if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) { 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){ } 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) { } else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS) {
return R.string.zen_mode_from_no_conversations; return R.string.zen_mode_from_no_conversations;
} }
return R.string.zen_mode_from_none; 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() { protected int getConversationSummary() {
int conversationType = getPriorityConversationSenders(); int conversationType = getPriorityConversationSenders();
@@ -366,7 +337,7 @@ public class ZenModeBackend {
return R.string.zen_mode_from_starred; return R.string.zen_mode_from_starred;
case ZenPolicy.PEOPLE_TYPE_NONE: case ZenPolicy.PEOPLE_TYPE_NONE:
default: default:
return R.string.zen_mode_from_none_messages; return R.string.zen_mode_from_none;
} }
} }
@@ -384,20 +355,6 @@ public class ZenModeBackend {
} }
} }
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) { public boolean removeZenRule(String ruleId) {
return NotificationManager.from(mContext).removeAutomaticZenRule(ruleId); return NotificationManager.from(mContext).removeAutomaticZenRule(ruleId);
} }

View File

@@ -6,6 +6,7 @@ import android.content.Context;
import android.icu.text.ListFormatter; import android.icu.text.ListFormatter;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.ArraySet;
import androidx.core.text.BidiFormatter; import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -21,6 +22,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* Controls the summary for preference found at: * Controls the summary for preference found at:
@@ -102,7 +104,7 @@ public class ZenModeBypassingAppsPreferenceController extends AbstractZenModePre
return; return;
} }
List<String> appsBypassingDnd = new ArrayList<>(); Set<String> appsBypassingDnd = new ArraySet<>();
for (ApplicationsState.AppEntry entry : apps) { for (ApplicationsState.AppEntry entry : apps) {
String pkg = entry.info.packageName; String pkg = entry.info.packageName;
for (NotificationChannel channel : mNotificationBackend for (NotificationChannel channel : mNotificationBackend
@@ -116,7 +118,8 @@ public class ZenModeBypassingAppsPreferenceController extends AbstractZenModePre
} }
} }
if (appsBypassingDnd.size() == 0) { final int numAppsBypassingDnd = appsBypassingDnd.size();
if (numAppsBypassingDnd == 0) {
mSummary = mContext.getResources().getString( mSummary = mContext.getResources().getString(
R.string.zen_mode_bypassing_apps_subtext_none); R.string.zen_mode_bypassing_apps_subtext_none);
refreshSummary(mPreference); refreshSummary(mPreference);
@@ -124,18 +127,20 @@ public class ZenModeBypassingAppsPreferenceController extends AbstractZenModePre
} }
List<String> displayAppsBypassing = new ArrayList<>(); List<String> displayAppsBypassing = new ArrayList<>();
if (appsBypassingDnd.size() <= 2) { if (numAppsBypassingDnd <= 2) {
displayAppsBypassing = appsBypassingDnd; displayAppsBypassing.addAll(appsBypassingDnd);
} else { } else {
displayAppsBypassing.add(appsBypassingDnd.get(0)); String[] appsBypassingDndArr =
displayAppsBypassing.add(appsBypassingDnd.get(1)); appsBypassingDnd.toArray(new String[numAppsBypassingDnd]);
displayAppsBypassing.add(appsBypassingDndArr[0]);
displayAppsBypassing.add(appsBypassingDndArr[1]);
displayAppsBypassing.add(mContext.getResources().getString( displayAppsBypassing.add(mContext.getResources().getString(
R.string.zen_mode_apps_bypassing_list_count, R.string.zen_mode_apps_bypassing_list_count,
appsBypassingDnd.size() - 2)); numAppsBypassingDnd - 2));
} }
mSummary = mContext.getResources().getQuantityString( mSummary = mContext.getResources().getQuantityString(
R.plurals.zen_mode_bypassing_apps_subtext, R.plurals.zen_mode_bypassing_apps_subtext,
appsBypassingDnd.size(), numAppsBypassingDnd,
ListFormatter.getInstance().format(displayAppsBypassing)); ListFormatter.getInstance().format(displayAppsBypassing));
refreshSummary(mPreference); refreshSummary(mPreference);
} }

View File

@@ -24,6 +24,7 @@ import android.content.Context;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.Indexable; import com.android.settingslib.search.Indexable;
@@ -46,13 +47,16 @@ public class ZenModeBypassingAppsSettings extends ZenModeSettingsBase implements
} else { } else {
app = null; app = null;
} }
return buildPreferenceControllers(context, app, this); return buildPreferenceControllers(context, app, this, new NotificationBackend());
} }
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context, private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Application app, Fragment host) { Application app, Fragment host, NotificationBackend notificationBackend) {
final List<AbstractPreferenceController> controllers = new ArrayList<>(); 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; return controllers;
} }
@@ -80,7 +84,7 @@ public class ZenModeBypassingAppsSettings extends ZenModeSettingsBase implements
@Override @Override
public List<AbstractPreferenceController> createPreferenceControllers( public List<AbstractPreferenceController> createPreferenceControllers(
Context context) { Context context) {
return buildPreferenceControllers(context, null, null); return buildPreferenceControllers(context, null, null, null);
} }
}; };
} }

View File

@@ -30,7 +30,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Settings > Sound > Do Not Disturb > Conversationss * Settings > Sound > Do Not Disturb > Conversations
*/ */
@SearchIndexable @SearchIndexable
public class ZenModeConversationsSettings extends ZenModeSettingsBase { public class ZenModeConversationsSettings extends ZenModeSettingsBase {

View File

@@ -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;
}
}

View File

@@ -16,11 +16,16 @@
package com.android.settings.notification.zen; 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.NotificationManager;
import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.service.notification.ConversationChannelWrapper; import android.service.notification.ConversationChannelWrapper;
import android.view.View;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference; import androidx.preference.Preference;
@@ -28,7 +33,10 @@ import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend; 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.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.RadioButtonPreference; import com.android.settingslib.widget.RadioButtonPreference;
@@ -51,6 +59,7 @@ public class ZenModePriorityConversationsPreferenceController
private int mNumConversations = UNSET; private int mNumConversations = UNSET;
private PreferenceCategory mPreferenceCategory; private PreferenceCategory mPreferenceCategory;
private List<RadioButtonPreference> mRadioButtonPreferences = new ArrayList<>(); private List<RadioButtonPreference> mRadioButtonPreferences = new ArrayList<>();
private Context mPreferenceScreenContext;
public ZenModePriorityConversationsPreferenceController(Context context, String key, public ZenModePriorityConversationsPreferenceController(Context context, String key,
Lifecycle lifecycle, NotificationBackend notificationBackend) { Lifecycle lifecycle, NotificationBackend notificationBackend) {
@@ -60,6 +69,7 @@ public class ZenModePriorityConversationsPreferenceController
@Override @Override
public void displayPreference(PreferenceScreen screen) { public void displayPreference(PreferenceScreen screen) {
mPreferenceScreenContext = screen.getContext();
mPreferenceCategory = screen.findPreference(getPreferenceKey()); mPreferenceCategory = screen.findPreference(getPreferenceKey());
if (mPreferenceCategory.findPreference(KEY_ALL) == null) { if (mPreferenceCategory.findPreference(KEY_ALL) == null) {
makeRadioPreference(KEY_ALL, R.string.zen_mode_from_all_conversations); makeRadioPreference(KEY_ALL, R.string.zen_mode_from_all_conversations);
@@ -125,7 +135,7 @@ public class ZenModePriorityConversationsPreferenceController
R.string.zen_mode_conversations_count_none); R.string.zen_mode_conversations_count_none);
} else { } else {
return mContext.getResources().getQuantityString( return mContext.getResources().getQuantityString(
R.plurals.zen_mode_conversations_count, numConversations); R.plurals.zen_mode_conversations_count, numConversations, numConversations);
} }
} }
@@ -136,14 +146,27 @@ public class ZenModePriorityConversationsPreferenceController
protected Void doInBackground(Void... unused) { protected Void doInBackground(Void... unused) {
ParceledListSlice<ConversationChannelWrapper> allConversations = ParceledListSlice<ConversationChannelWrapper> allConversations =
mNotificationBackend.getConversations(false); mNotificationBackend.getConversations(false);
int numConversations = 0;
if (allConversations != null) { 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); mNotificationBackend.getConversations(true);
if (importantConversations != null) { int numImportantConversations = 0;
mNumImportantConversations = importantConversations.getList().size(); if (impConversations != null) {
for (ConversationChannelWrapper conversation : impConversations.getList()) {
if (!conversation.getNotificationChannel().isDemoted()) {
numImportantConversations++;
}
}
} }
mNumImportantConversations = numImportantConversations;
return null; return null;
} }
@@ -158,7 +181,14 @@ public class ZenModePriorityConversationsPreferenceController
} }
private RadioButtonPreference makeRadioPreference(String key, int titleId) { 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.setKey(key);
pref.setTitle(titleId); pref.setTitle(titleId);
pref.setOnClickListener(mRadioButtonClickListener); pref.setOnClickListener(mRadioButtonClickListener);
@@ -167,6 +197,17 @@ public class ZenModePriorityConversationsPreferenceController
return pref; 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 = private RadioButtonPreference.OnClickListener mRadioButtonClickListener =
new RadioButtonPreference.OnClickListener() { new RadioButtonPreference.OnClickListener() {
@Override @Override

View File

@@ -153,7 +153,7 @@ public class ZenModeSettings extends ZenModeSettingsBase {
String getCallsSettingSummary(Policy policy) { String getCallsSettingSummary(Policy policy) {
List<String> enabledCategories = getEnabledCategories(policy, List<String> enabledCategories = getEnabledCategories(policy,
category -> PRIORITY_CATEGORY_CALLS == category category -> PRIORITY_CATEGORY_CALLS == category
|| PRIORITY_CATEGORY_REPEAT_CALLERS == category, false); || PRIORITY_CATEGORY_REPEAT_CALLERS == category, true);
int numCategories = enabledCategories.size(); int numCategories = enabledCategories.size();
if (numCategories == 0) { if (numCategories == 0) {
return mContext.getString(R.string.zen_mode_from_none_calls); return mContext.getString(R.string.zen_mode_from_none_calls);
@@ -303,10 +303,19 @@ public class ZenModeSettings extends ZenModeSettingsBase {
} }
} else if (category == Policy.PRIORITY_CATEGORY_CALLS) { } else if (category == Policy.PRIORITY_CATEGORY_CALLS) {
if (policy.priorityCallSenders == Policy.PRIORITY_SENDERS_ANY) { 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); return mContext.getString(R.string.zen_mode_all_callers);
} else if (policy.priorityCallSenders == Policy.PRIORITY_SENDERS_CONTACTS){ } 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); return mContext.getString(R.string.zen_mode_contacts_callers);
} else { } else {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_starred);
}
return mContext.getString(R.string.zen_mode_starred_callers); return mContext.getString(R.string.zen_mode_starred_callers);
} }
} else if (category == Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) { } else if (category == Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) {

View File

@@ -16,19 +16,8 @@
package com.android.settings.notification.app; 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 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.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
@@ -37,24 +26,17 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserManager;
import android.provider.Settings; import android.provider.Settings;
import android.service.notification.ConversationChannelWrapper; import android.service.notification.ConversationChannelWrapper;
import android.view.View;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.AppInfoBase;
import com.android.settings.notification.NotificationBackend; import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedLockUtils;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -67,7 +49,6 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowApplication; import org.robolectric.shadows.ShadowApplication;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class ConversationListPreferenceControllerTest { public class ConversationListPreferenceControllerTest {
@@ -116,8 +97,6 @@ public class ConversationListPreferenceControllerTest {
list.add(ccw); list.add(ccw);
mController.populateList(list, outerContainer); mController.populateList(list, outerContainer);
verify(outerContainer).setVisible(true);
verify(outerContainer, times(1)).addPreference(any()); verify(outerContainer, times(1)).addPreference(any());
} }

View File

@@ -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);
}
}

View File

@@ -19,6 +19,8 @@ package com.android.settings.notification.zen;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; 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.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@@ -30,25 +32,25 @@ import android.content.Context;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.ParceledListSlice; 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.NotificationBackend;
import com.android.settings.notification.zen.ZenModeAllBypassingAppsPreferenceController;
import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceScreen;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class ZenModeAllBypassingAppsPreferenceControllerTest { public class ZenModeAllBypassingAppsPreferenceControllerTest {
private ZenModeAllBypassingAppsPreferenceController mController; private ZenModeAllBypassingAppsPreferenceController mController;
@@ -57,7 +59,7 @@ public class ZenModeAllBypassingAppsPreferenceControllerTest {
@Mock @Mock
private NotificationBackend mBackend; private NotificationBackend mBackend;
@Mock @Mock
private PreferenceScreen mPreferenceScreen; private PreferenceCategory mPreferenceCategory;
@Mock @Mock
private ApplicationsState mApplicationState; private ApplicationsState mApplicationState;
@@ -67,11 +69,10 @@ public class ZenModeAllBypassingAppsPreferenceControllerTest {
mContext = RuntimeEnvironment.application; mContext = RuntimeEnvironment.application;
mController = new ZenModeAllBypassingAppsPreferenceController( mController = new ZenModeAllBypassingAppsPreferenceController(
mContext, null, mock(Fragment.class)); mContext, null, mock(Fragment.class), mBackend);
mController.mPreferenceScreen = mPreferenceScreen; mController.mPreferenceCategory = mPreferenceCategory;
mController.mApplicationsState = mApplicationState; mController.mApplicationsState = mApplicationState;
mController.mPrefContext = mContext; mController.mPrefContext = mContext;
ReflectionHelpers.setField(mController, "mNotificationBackend", mBackend);
} }
@Test @Test
@@ -80,36 +81,49 @@ public class ZenModeAllBypassingAppsPreferenceControllerTest {
} }
@Test @Test
public void testUpdateNotificationChannelList() { public void testUpdateAppList() {
ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class); // WHEN there's two apps with notification channels that bypass DND
entry.info = new ApplicationInfo(); ApplicationsState.AppEntry entry1 = mock(ApplicationsState.AppEntry.class);
entry.info.packageName = "test"; entry1.info = new ApplicationInfo();
entry.info.uid = 0; 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<>(); List<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
appEntries.add(entry); appEntries.add(entry1);
appEntries.add(entry2);
List<NotificationChannel> channelsBypassing = new ArrayList<>(); List<NotificationChannel> channelsBypassing = new ArrayList<>();
channelsBypassing.add(mock(NotificationChannel.class)); channelsBypassing.add(mock(NotificationChannel.class));
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, // THEN there's are two preferences
entry.info.uid)).thenReturn(new ParceledListSlice<>(channelsBypassing)); mController.updateAppList(appEntries);
verify(mPreferenceCategory, times(2)).addPreference(any());
mController.updateNotificationChannelList(appEntries);
verify(mPreferenceScreen, times(3)).addPreference(any());
} }
@Test @Test
public void testUpdateNotificationChannelList_nullChannels() { public void testUpdateAppList_nullApps() {
mController.updateNotificationChannelList(null); mController.updateAppList(null);
verify(mPreferenceScreen, never()).addPreference(any()); verify(mPreferenceCategory, never()).addPreference(any());
} }
@Test @Test
public void testUpdateNotificationChannelList_emptyChannelsList() { public void testUpdateAppList_emptyAppList() {
mController.updateNotificationChannelList(new ArrayList<>()); // WHEN there are no apps
verify(mPreferenceScreen, never()).addPreference(any()); 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);
} }
} }

View File

@@ -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)]);
}
}

View File

@@ -79,13 +79,13 @@ public class ZenModeSettingsTest {
public void testGetCallsSettingSummary_contacts() { public void testGetCallsSettingSummary_contacts() {
Policy policy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | Policy.PRIORITY_CATEGORY_CALLS, Policy policy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | Policy.PRIORITY_CATEGORY_CALLS,
Policy.PRIORITY_SENDERS_CONTACTS, 0, 0); Policy.PRIORITY_SENDERS_CONTACTS, 0, 0);
assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("Allow from contacts"); assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("Contacts");
} }
@Test @Test
public void testGetCallsSettingSummary_repeatCallers() { public void testGetCallsSettingSummary_repeatCallers() {
Policy policy = new Policy(Policy.PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0); 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 @Test
@@ -94,7 +94,7 @@ public class ZenModeSettingsTest {
Policy.PRIORITY_CATEGORY_REPEAT_CALLERS | Policy.PRIORITY_CATEGORY_CALLS, Policy.PRIORITY_CATEGORY_REPEAT_CALLERS | Policy.PRIORITY_CATEGORY_CALLS,
Policy.PRIORITY_SENDERS_STARRED, 0, 0); Policy.PRIORITY_SENDERS_STARRED, 0, 0);
assertThat(mBuilder.getCallsSettingSummary(policy)) assertThat(mBuilder.getCallsSettingSummary(policy))
.isEqualTo("Allow from starred contacts and repeat callers"); .isEqualTo("Starred contacts and repeat callers");
} }
@Test @Test