diff --git a/AndroidManifest.xml b/AndroidManifest.xml index e3decb0f1e3..7702257c060 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2700,6 +2700,22 @@ android:value="com.android.settings.notification.AppNotificationSettings" /> + + + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index ec830536a63..0edb017fd1c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6835,6 +6835,9 @@ Notification category + + Notification category group + Importance @@ -7002,12 +7005,21 @@ Android is blocking this category of notifications from appearing on this device + + Android is blocking this group of notifications from appearing on this device + Categories Other + + + %d category + %d categories + + This app has not posted any notifications diff --git a/res/xml/upgraded_channel_notification_settings.xml b/res/xml/upgraded_channel_notification_settings.xml index 2cece9e62ff..ee23435576b 100644 --- a/res/xml/upgraded_channel_notification_settings.xml +++ b/res/xml/upgraded_channel_notification_settings.xml @@ -38,6 +38,7 @@ settings:useAdditionalSummary="true" /> diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index a6961726550..ecdf4ba2287 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -124,6 +124,7 @@ public class Settings extends SettingsActivity { public static class NotificationAppListActivity extends SettingsActivity { /* empty */ } public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ } public static class ChannelNotificationSettingsActivity extends SettingsActivity { /* empty */ } + public static class ChannelGroupNotificationSettingsActivity extends SettingsActivity { /* empty */ } public static class ManageDomainUrlsActivity extends SettingsActivity { /* empty */ } public static class AutomaticStorageManagerSettingsActivity extends SettingsActivity { /* empty */ } public static class GamesStorageActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 380c0704a7a..a03314ca2e2 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -100,6 +100,7 @@ import com.android.settings.nfc.AndroidBeam; import com.android.settings.nfc.PaymentSettings; import com.android.settings.notification.AppNotificationSettings; import com.android.settings.notification.ChannelNotificationSettings; +import com.android.settings.notification.ChannelGroupNotificationSettings; import com.android.settings.notification.ConfigureNotificationSettings; import com.android.settings.notification.NotificationAccessSettings; import com.android.settings.notification.NotificationStation; @@ -209,6 +210,7 @@ public class SettingsGateway { BatterySaverSettings.class.getName(), AppNotificationSettings.class.getName(), ChannelNotificationSettings.class.getName(), + ChannelGroupNotificationSettings.class.getName(), ApnSettings.class.getName(), ApnEditor.class.getName(), WifiCallingSettings.class.getName(), diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java index 78a0a74999f..29eb4a3098e 100644 --- a/src/com/android/settings/notification/AppNotificationSettings.java +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -26,6 +26,7 @@ import android.os.Bundle; import android.provider.Settings; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceGroup; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -50,7 +51,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; @@ -105,7 +105,7 @@ public class AppNotificationSettings extends NotificationSettingsBase { new AsyncTask() { @Override protected Void doInBackground(Void... unused) { - mChannelGroupList = mBackend.getChannelGroups(mPkg, mUid).getList(); + mChannelGroupList = mBackend.getGroups(mPkg, mUid).getList(); Collections.sort(mChannelGroupList, mChannelGroupComparator); return null; } @@ -115,7 +115,7 @@ public class AppNotificationSettings extends NotificationSettingsBase { if (getHost() == null) { return; } - populateChannelList(); + populateList(); addAppLinkPref(); } }.execute(); @@ -144,7 +144,7 @@ public class AppNotificationSettings extends NotificationSettingsBase { getPreferenceScreen().addPreference(pref); } - private void populateChannelList() { + private void populateList() { if (!mChannelGroups.isEmpty()) { // If there's anything in mChannelGroups, we've called populateChannelList twice. // Clear out existing channels and log. @@ -166,30 +166,7 @@ public class AppNotificationSettings extends NotificationSettingsBase { empty.setEnabled(false); groupCategory.addPreference(empty); } else { - for (NotificationChannelGroup group : mChannelGroupList) { - PreferenceCategory groupCategory = new PreferenceCategory(getPrefContext()); - if (group.getId() == null) { - groupCategory.setTitle(mChannelGroupList.size() > 1 - ? R.string.notification_channels_other - : R.string.notification_channels); - groupCategory.setKey(KEY_GENERAL_CATEGORY); - } else { - groupCategory.setTitle(group.getName()); - groupCategory.setKey(group.getId()); - } - groupCategory.setOrderingAsAdded(true); - getPreferenceScreen().addPreference(groupCategory); - mChannelGroups.add(groupCategory); - - final List channels = group.getChannels(); - Collections.sort(channels, mChannelComparator); - int N = channels.size(); - for (int i = 0; i < N; i++) { - final NotificationChannel channel = channels.get(i); - populateSingleChannelPrefs(groupCategory, channel); - } - } - + populateGroupList(); int deletedChannelCount = mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid); if (deletedChannelCount > 0) { mDeletedChannels = new FooterPreference(getPrefContext()); @@ -202,48 +179,63 @@ public class AppNotificationSettings extends NotificationSettingsBase { getPreferenceScreen().addPreference(mDeletedChannels); } } - updateDependents(mAppRow.banned); } - private void populateSingleChannelPrefs(PreferenceCategory groupCategory, - final NotificationChannel channel) { - MasterSwitchPreference channelPref = new MasterSwitchPreference( + private void populateGroupList() { + PreferenceCategory groupCategory = new PreferenceCategory(getPrefContext()); + groupCategory.setTitle(R.string.notification_channels); + groupCategory.setKey(KEY_GENERAL_CATEGORY); + groupCategory.setOrderingAsAdded(true); + getPreferenceScreen().addPreference(groupCategory); + mChannelGroups.add(groupCategory); + for (NotificationChannelGroup group : mChannelGroupList) { + final List channels = group.getChannels(); + int N = channels.size(); + // app defined groups with one channel and channels with no group display the channel + // name and no summary and link directly to the channel page unless the group is blocked + if ((group.getId() == null || N < 2) && !group.isBlocked()) { + Collections.sort(channels, mChannelComparator); + for (int i = 0; i < N; i++) { + final NotificationChannel channel = channels.get(i); + populateSingleChannelPrefs(groupCategory, channel, ""); + } + } else { + populateGroupPreference(groupCategory, group, N); + } + } + } + + void populateGroupPreference(PreferenceGroup parent, + final NotificationChannelGroup group, int channelCount) { + MasterSwitchPreference groupPref = new MasterSwitchPreference( getPrefContext()); - channelPref.setSwitchEnabled(mSuspendedAppsAdmin == null - && isChannelBlockable(mAppRow.systemApp, channel) - && isChannelConfigurable(channel)); - channelPref.setKey(channel.getId()); - channelPref.setTitle(channel.getName()); - channelPref.setChecked(channel.getImportance() != IMPORTANCE_NONE); - channelPref.setSummary(getImportanceSummary(channel)); - Bundle channelArgs = new Bundle(); - channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid); - channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg); - channelArgs.putString(Settings.EXTRA_CHANNEL_ID, channel.getId()); - Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(), - ChannelNotificationSettings.class.getName(), - channelArgs, null, R.string.notification_channel_title, null, false, + groupPref.setSwitchEnabled(mSuspendedAppsAdmin == null + && isChannelGroupBlockable(group)); + groupPref.setKey(group.getId()); + groupPref.setTitle(group.getName()); + groupPref.setChecked(!group.isBlocked()); + groupPref.setSummary(getResources().getQuantityString( + R.plurals.notification_group_summary, channelCount, channelCount)); + Bundle groupArgs = new Bundle(); + groupArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid); + groupArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg); + groupArgs.putString(Settings.EXTRA_CHANNEL_GROUP_ID, group.getId()); + Intent groupIntent = Utils.onBuildStartFragmentIntent(getActivity(), + ChannelGroupNotificationSettings.class.getName(), + groupArgs, null, R.string.notification_group_title, null, false, getMetricsCategory()); - channelPref.setIntent(channelIntent); + groupPref.setIntent(groupIntent); - channelPref.setOnPreferenceChangeListener( - new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, - Object o) { - boolean value = (Boolean) o; - int importance = value ? IMPORTANCE_LOW : IMPORTANCE_NONE; - channel.setImportance(importance); - channel.lockFields( - NotificationChannel.USER_LOCKED_IMPORTANCE); - channelPref.setSummary(getImportanceSummary(channel)); - mBackend.updateChannel(mPkg, mUid, channel); + groupPref.setOnPreferenceChangeListener( + (preference, o) -> { + boolean value = (Boolean) o; + group.setBlocked(!value); + mBackend.updateChannelGroup(mPkg, mUid, group); - return true; - } + return true; }); - groupCategory.addPreference(channelPref); + parent.addPreference(groupPref); } void setupBadge() { @@ -330,38 +322,6 @@ public class AppNotificationSettings extends NotificationSettingsBase { } } - private String getImportanceSummary(NotificationChannel channel) { - switch (channel.getImportance()) { - case NotificationManager.IMPORTANCE_UNSPECIFIED: - return getContext().getString(R.string.notification_importance_unspecified); - case NotificationManager.IMPORTANCE_NONE: - return getContext().getString(R.string.notification_toggle_off); - case NotificationManager.IMPORTANCE_MIN: - return getContext().getString(R.string.notification_importance_min); - case NotificationManager.IMPORTANCE_LOW: - return getContext().getString(R.string.notification_importance_low); - case NotificationManager.IMPORTANCE_DEFAULT: - return getContext().getString(R.string.notification_importance_default); - case NotificationManager.IMPORTANCE_HIGH: - case NotificationManager.IMPORTANCE_MAX: - default: - return getContext().getString(R.string.notification_importance_high); - } - - } - - private Comparator mChannelComparator = - new Comparator() { - - @Override - public int compare(NotificationChannel left, NotificationChannel right) { - if (left.isDeleted() != right.isDeleted()) { - return Boolean.compare(left.isDeleted(), right.isDeleted()); - } - return left.getId().compareTo(right.getId()); - } - }; - private Comparator mChannelGroupComparator = new Comparator() { diff --git a/src/com/android/settings/notification/ChannelGroupNotificationSettings.java b/src/com/android/settings/notification/ChannelGroupNotificationSettings.java new file mode 100644 index 00000000000..7837ec866df --- /dev/null +++ b/src/com/android/settings/notification/ChannelGroupNotificationSettings.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2017 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; + +import android.app.Activity; +import android.app.NotificationChannel; +import android.support.v7.preference.Preference; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.applications.LayoutPreference; +import com.android.settings.widget.EntityHeaderController; +import com.android.settingslib.widget.FooterPreference; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ChannelGroupNotificationSettings extends NotificationSettingsBase { + private static final String TAG = "ChannelGroupSettings"; + + private static String KEY_DELETED = "deleted"; + + private EntityHeaderController mHeaderPref; + private List mChannels = new ArrayList(); + private FooterPreference mDeletedChannels; + + @Override + public int getMetricsCategory() { + return MetricsEvent.NOTIFICATION_CHANNEL_GROUP; + } + + @Override + public void onResume() { + super.onResume(); + if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mChannelGroup == null) { + Log.w(TAG, "Missing package or uid or packageinfo or group"); + finish(); + return; + } + + if (getPreferenceScreen() != null) { + getPreferenceScreen().removeAll(); + } + addPreferencesFromResource(R.xml.notification_settings); + setupBlock(); + addHeaderPref(); + addAppLinkPref(); + addFooterPref(); + populateChannelList(); + + updateDependents(mChannelGroup.isBlocked()); + } + + @Override + void setupBadge() { + + } + + private void populateChannelList() { + if (!mChannels.isEmpty()) { + // If there's anything in mChannels, we've called populateChannelList twice. + // Clear out existing channels and log. + Log.w(TAG, "Notification channel group posted twice to settings - old size " + + mChannels.size() + ", new size " + mChannels.size()); + for (Preference p : mChannels) { + getPreferenceScreen().removePreference(p); + } + } + if (mChannelGroup.getChannels().isEmpty()) { + Preference empty = new Preference(getPrefContext()); + empty.setTitle(R.string.no_channels); + empty.setEnabled(false); + getPreferenceScreen().addPreference(empty); + mChannels.add(empty); + + } else { + final List channels = mChannelGroup.getChannels(); + Collections.sort(channels, mChannelComparator); + for (NotificationChannel channel : channels) { + mChannels.add(populateSingleChannelPrefs( + getPreferenceScreen(), channel, getImportanceSummary(channel))); + } + + int deletedChannelCount = mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid); + if (deletedChannelCount > 0) { + mDeletedChannels = new FooterPreference(getPrefContext()); + mDeletedChannels.setSelectable(false); + mDeletedChannels.setTitle(getResources().getQuantityString( + R.plurals.deleted_channels, deletedChannelCount, deletedChannelCount)); + mDeletedChannels.setEnabled(false); + mDeletedChannels.setKey(KEY_DELETED); + mDeletedChannels.setOrder(ORDER_LAST); + getPreferenceScreen().addPreference(mDeletedChannels); + mChannels.add(mDeletedChannels); + } + } + + updateDependents(mAppRow.banned); + } + + private void addHeaderPref() { + ArrayMap rows = new ArrayMap<>(); + rows.put(mAppRow.pkg, mAppRow); + collectConfigActivities(rows); + final Activity activity = getActivity(); + mHeaderPref = EntityHeaderController + .newInstance(activity, this /* fragment */, null /* header */) + .setRecyclerView(getListView(), getLifecycle()); + final Preference pref = mHeaderPref + .setIcon(mAppRow.icon) + .setLabel(mChannelGroup.getName()) + .setSummary(mAppRow.label) + .setPackageName(mAppRow.pkg) + .setUid(mAppRow.uid) + .setButtonActions(EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE, + EntityHeaderController.ActionType.ACTION_NONE) + .setHasAppInfoLink(true) + .done(activity, getPrefContext()); + getPreferenceScreen().addPreference(pref); + } + + private void addFooterPref() { + if (!TextUtils.isEmpty(mChannelGroup.getDescription())) { + FooterPreference descPref = new FooterPreference(getPrefContext()); + descPref.setOrder(ORDER_LAST); + descPref.setSelectable(false); + descPref.setTitle(mChannelGroup.getDescription()); + getPreferenceScreen().addPreference(descPref); + mChannels.add(descPref); + } + } + + private void setupBlock() { + View switchBarContainer = LayoutInflater.from( + getPrefContext()).inflate(R.layout.styled_switch_bar, null); + mSwitchBar = switchBarContainer.findViewById(R.id.switch_bar); + mSwitchBar.show(); + mSwitchBar.setDisabledByAdmin(mSuspendedAppsAdmin); + mSwitchBar.setChecked(!mChannelGroup.isBlocked()); + mSwitchBar.addOnSwitchChangeListener((switchView, isChecked) -> { + mChannelGroup.setBlocked(!isChecked); + mBackend.updateChannelGroup(mPkg, mUid, mChannelGroup); + updateDependents(!isChecked); + }); + + mBlockBar = new LayoutPreference(getPrefContext(), switchBarContainer); + mBlockBar.setOrder(ORDER_FIRST); + mBlockBar.setKey(KEY_BLOCK); + getPreferenceScreen().addPreference(mBlockBar); + + if (!isChannelGroupBlockable(mChannelGroup)) { + setVisible(mBlockBar, false); + } + + setupBlockDesc(R.string.channel_group_notifications_off_desc); + } + + protected void updateDependents(boolean banned) { + for (Preference channel : mChannels) { + setVisible(channel, !banned); + } + if (mAppLink != null) { + setVisible(mAppLink, !banned); + } + setVisible(mBlockBar, isChannelGroupBlockable(mChannelGroup)); + setVisible(mBlockedDesc, mAppRow.banned || mChannelGroup.isBlocked()); + } +} diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java index 2f95dd29f38..41f98014535 100644 --- a/src/com/android/settings/notification/ChannelNotificationSettings.java +++ b/src/com/android/settings/notification/ChannelNotificationSettings.java @@ -26,6 +26,7 @@ import android.os.Bundle; import android.os.AsyncTask; import android.provider.Settings; import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceGroup; import android.text.TextUtils; import android.text.BidiFormatter; import android.text.SpannableStringBuilder; @@ -57,6 +58,7 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { private static final String KEY_VIBRATE = "vibrate"; private static final String KEY_RINGTONE = "ringtone"; private static final String KEY_IMPORTANCE = "importance"; + private static final String KEY_ADVANCED = "advanced"; private Preference mImportance; private RestrictedSwitchPreference mLights; @@ -65,6 +67,7 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { private FooterPreference mFooter; private NotificationChannelGroup mChannelGroup; private EntityHeaderController mHeaderPref; + private PreferenceGroup mAdvanced; @Override public int getMetricsCategory() { @@ -96,24 +99,10 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { populateUpgradedChannelPrefs(); if (mChannel.getGroup() != null) { - // Go look up group name - new AsyncTask() { - @Override - protected Void doInBackground(Void... unused) { - if (mChannel.getGroup() != null) { - mChannelGroup = mBackend.getGroup(mChannel.getGroup(), mPkg, mUid); - } - return null; - } - - @Override - protected void onPostExecute(Void unused) { - if (getHost() == null || mChannelGroup == null) { - return; - } - setChannelGroupLabel(mChannelGroup.getName()); - } - }.execute(); + mChannelGroup = mBackend.getGroup(mPkg, mUid, mChannel.getGroup()); + if (mChannelGroup != null) { + setChannelGroupLabel(mChannelGroup.getName()); + } } } @@ -129,6 +118,7 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { setupVibrate(); setupRingtone(); setupImportance(); + mAdvanced = (PreferenceGroup) findPreference(KEY_ADVANCED); } private void addHeaderPref() { @@ -272,7 +262,7 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { mBlockBar.setKey(KEY_BLOCK); getPreferenceScreen().addPreference(mBlockBar); - if (!isChannelBlockable(mAppRow.systemApp, mChannel)) { + if (!isChannelBlockable(mChannel)) { setVisible(mBlockBar, false); } @@ -373,6 +363,7 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { if (mShowLegacyChannelConfig) { setVisible(mImportanceToggle, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); } else { + setVisible(mAdvanced, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); setVisible(mImportance, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); setVisible(mLights, checkCanBeVisible( NotificationManager.IMPORTANCE_DEFAULT) && canPulseLight()); diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index 82e3a9e13ef..4de528e0a03 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -136,8 +136,7 @@ public class NotificationBackend { } } - - public NotificationChannelGroup getGroup(String groupId, String pkg, int uid) { + public NotificationChannelGroup getGroup(String pkg, int uid, String groupId) { if (groupId == null) { return null; } @@ -149,7 +148,19 @@ public class NotificationBackend { } } - public ParceledListSlice getChannelGroups(String pkg, int uid) { + public NotificationChannelGroup getGroupWithChannels(String pkg, int uid, String groupId) { + if (groupId == null) { + return null; + } + try { + return sINM.getPopulatedNotificationChannelGroupForPackage(pkg, uid, groupId, true); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return null; + } + } + + public ParceledListSlice getGroups(String pkg, int uid) { try { return sINM.getNotificationChannelGroupsForPackage(pkg, uid, false); } catch (Exception e) { @@ -166,6 +177,15 @@ public class NotificationBackend { } } + public void updateChannelGroup(String pkg, int uid, NotificationChannelGroup group) { + try { + sINM.updateNotificationChannelGroupForPackage(pkg, uid, group); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + } + } + + public int getDeletedChannelCount(String pkg, int uid) { try { return sINM.getDeletedChannelCount(pkg, uid); diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java index 38498829764..8c70a20e966 100644 --- a/src/com/android/settings/notification/NotificationSettingsBase.java +++ b/src/com/android/settings/notification/NotificationSettingsBase.java @@ -24,8 +24,10 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.LayoutPreference; +import com.android.settings.widget.MasterSwitchPreference; import com.android.settings.widget.SwitchBar; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedSwitchPreference; @@ -33,6 +35,7 @@ import com.android.settingslib.widget.FooterPreference; import android.app.Notification; import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -51,8 +54,8 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.service.notification.NotificationListenerService; -import android.support.v7.preference.DropDownPreference; import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceGroup; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -61,6 +64,7 @@ import android.widget.Toast; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; abstract public class NotificationSettingsBase extends SettingsPreferenceFragment { @@ -106,6 +110,7 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen protected EnforcedAdmin mSuspendedAppsAdmin; protected boolean mDndVisualEffectsSuppressed; + protected NotificationChannelGroup mChannelGroup; protected NotificationChannel mChannel; protected NotificationBackend.AppRow mAppRow; protected boolean mShowLegacyChannelConfig = false; @@ -185,6 +190,11 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen mChannel = (args != null && args.containsKey(Settings.EXTRA_CHANNEL_ID)) ? mBackend.getChannel(mPkg, mUid, args.getString(Settings.EXTRA_CHANNEL_ID)) : null; + mChannelGroup = (args != null && args.containsKey(Settings.EXTRA_CHANNEL_GROUP_ID)) ? + mBackend.getGroupWithChannels(mPkg, mUid, + args.getString(Settings.EXTRA_CHANNEL_GROUP_ID)) + : null; + mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended( mContext, mPkg, mUserId); NotificationManager.Policy policy = mNm.getNotificationPolicy(); @@ -249,6 +259,10 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen if (mChannel != null) { row.settingsIntent.putExtra(Notification.EXTRA_CHANNEL_ID, mChannel.getId()); } + if (mChannelGroup != null) { + row.settingsIntent.putExtra( + Notification.EXTRA_CHANNEL_GROUP_ID, mChannelGroup.getId()); + } } } @@ -276,7 +290,7 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen protected void addAppLinkPref() { if (mAppRow.settingsIntent != null && mAppLink == null) { addPreferencesFromResource(R.xml.inapp_notification_settings); - mAppLink = (Preference) findPreference(KEY_APP_LINK); + mAppLink = findPreference(KEY_APP_LINK); mAppLink.setIntent(mAppRow.settingsIntent); } } @@ -392,16 +406,56 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen } protected void setupBlockDesc(int summaryResId) { - mBlockedDesc = (FooterPreference) getPreferenceScreen().findPreference( - KEY_BLOCKED_DESC); mBlockedDesc = new FooterPreference(getPrefContext()); mBlockedDesc.setSelectable(false); mBlockedDesc.setTitle(summaryResId); mBlockedDesc.setEnabled(false); mBlockedDesc.setOrder(50); + mBlockedDesc.setKey(KEY_BLOCKED_DESC); getPreferenceScreen().addPreference(mBlockedDesc); } + protected Preference populateSingleChannelPrefs(PreferenceGroup parent, + final NotificationChannel channel, String summary) { + MasterSwitchPreference channelPref = new MasterSwitchPreference( + getPrefContext()); + channelPref.setSwitchEnabled(mSuspendedAppsAdmin == null + && isChannelBlockable(channel) + && isChannelConfigurable(channel)); + channelPref.setKey(channel.getId()); + channelPref.setTitle(channel.getName()); + channelPref.setChecked(channel.getImportance() != IMPORTANCE_NONE); + channelPref.setSummary(summary); + Bundle channelArgs = new Bundle(); + channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid); + channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg); + channelArgs.putString(Settings.EXTRA_CHANNEL_ID, channel.getId()); + Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(), + ChannelNotificationSettings.class.getName(), + channelArgs, null, R.string.notification_channel_title, null, false, + getMetricsCategory()); + channelPref.setIntent(channelIntent); + + channelPref.setOnPreferenceChangeListener( + new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, + Object o) { + boolean value = (Boolean) o; + int importance = value ? IMPORTANCE_LOW : IMPORTANCE_NONE; + channel.setImportance(importance); + channel.lockFields( + NotificationChannel.USER_LOCKED_IMPORTANCE); + channelPref.setSummary(summary); + mBackend.updateChannel(mPkg, mUid, channel); + + return true; + } + }); + parent.addPreference(channelPref); + return channelPref; + } + protected boolean checkCanBeVisible(int minImportanceVisible) { int importance = mChannel.getImportance(); if (importance == NotificationManager.IMPORTANCE_UNSPECIFIED) { @@ -410,6 +464,26 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen return importance >= minImportanceVisible; } + protected String getImportanceSummary(NotificationChannel channel) { + switch (channel.getImportance()) { + case NotificationManager.IMPORTANCE_UNSPECIFIED: + return getContext().getString(R.string.notification_importance_unspecified); + case NotificationManager.IMPORTANCE_NONE: + return getContext().getString(R.string.notification_toggle_off); + case NotificationManager.IMPORTANCE_MIN: + return getContext().getString(R.string.notification_importance_min); + case NotificationManager.IMPORTANCE_LOW: + return getContext().getString(R.string.notification_importance_low); + case NotificationManager.IMPORTANCE_DEFAULT: + return getContext().getString(R.string.notification_importance_default); + case NotificationManager.IMPORTANCE_HIGH: + case NotificationManager.IMPORTANCE_MAX: + default: + return getContext().getString(R.string.notification_importance_high); + } + + } + private void setRestrictedIfNotificationFeaturesDisabled(CharSequence entry, CharSequence entryValue, int keyguardNotificationFeatures) { RestrictedLockUtils.EnforcedAdmin admin = @@ -459,7 +533,7 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen return !channel.getId().equals(mAppRow.lockedChannelId); } - protected boolean isChannelBlockable(boolean systemApp, NotificationChannel channel) { + protected boolean isChannelBlockable(NotificationChannel channel) { if (!mAppRow.systemApp) { return true; } @@ -468,6 +542,14 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen || channel.getImportance() == NotificationManager.IMPORTANCE_NONE; } + protected boolean isChannelGroupBlockable(NotificationChannelGroup group) { + if (!mAppRow.systemApp) { + return true; + } + + return group.isBlocked(); + } + protected void startListeningToPackageRemove() { if (mListeningToPackageRemove) { return; @@ -501,4 +583,12 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen } } }; + + protected Comparator mChannelComparator = + (left, right) -> { + if (left.isDeleted() != right.isDeleted()) { + return Boolean.compare(left.isDeleted(), right.isDeleted()); + } + return left.getId().compareTo(right.getId()); + }; } diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index 695342e3be0..a08536a94d5 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -23,6 +23,7 @@ com.android.settings.inputmethod.UserDictionaryList com.android.settings.deviceinfo.Status com.android.settings.datausage.DataSaverSummary com.android.settings.notification.ChannelNotificationSettings +com.android.settings.notification.ChannelGroupNotificationSettings com.android.settings.datausage.AppDataUsage com.android.settings.accessibility.FontSizePreferenceFragmentForSetupWizard com.android.settings.applications.ManageDomainUrls diff --git a/tests/unit/src/com/android/settings/notification/AppNotificationSettingsTest.java b/tests/unit/src/com/android/settings/notification/AppNotificationSettingsTest.java index 22e98c71e5e..16a0b4324ad 100644 --- a/tests/unit/src/com/android/settings/notification/AppNotificationSettingsTest.java +++ b/tests/unit/src/com/android/settings/notification/AppNotificationSettingsTest.java @@ -16,7 +16,29 @@ package com.android.settings.notification; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import android.support.test.espresso.intent.Intents; + +import static android.support.test.espresso.intent.Intents.intended; +import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; + +import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT; + +import static org.hamcrest.Matchers.allOf; +import static org.junit.Assert.fail; + import android.app.Instrumentation; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.provider.Settings; @@ -29,12 +51,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; -import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static org.hamcrest.Matchers.allOf; - @RunWith(AndroidJUnit4.class) @SmallTest public class AppNotificationSettingsTest { @@ -42,10 +58,29 @@ public class AppNotificationSettingsTest { private Context mTargetContext; private Instrumentation mInstrumentation; + NotificationManager mNm; + private NotificationChannelGroup mGroup1; + private NotificationChannel mGroup1Channel1; + private NotificationChannel mGroup1Channel2; + private NotificationChannelGroup mGroup2; + private NotificationChannel mGroup2Channel1; + private NotificationChannel mUngroupedChannel; + @Before public void setUp() { mInstrumentation = InstrumentationRegistry.getInstrumentation(); mTargetContext = mInstrumentation.getTargetContext(); + mNm = (NotificationManager) mTargetContext.getSystemService(Context.NOTIFICATION_SERVICE); + + mGroup1 = new NotificationChannelGroup(this.getClass().getName() + "1", "group1"); + mGroup2 = new NotificationChannelGroup(this.getClass().getName() + "2", "group2"); + mNm.createNotificationChannelGroup(mGroup1); + mNm.createNotificationChannelGroup(mGroup2); + + mGroup1Channel1 = createChannel(mGroup1, this.getClass().getName()+ "c1-1"); + mGroup1Channel2 = createChannel(mGroup1, this.getClass().getName()+ "c1-2"); + mGroup2Channel1 = createChannel(mGroup2, this.getClass().getName()+ "c2-1"); + mUngroupedChannel = createChannel(null, this.getClass().getName()+ "c"); } @Test @@ -60,4 +95,72 @@ public class AppNotificationSettingsTest { .check(doesNotExist()); } + @Test + public void launchNotificationSetting_showGroupsWithMultipleChannels() { + final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, mTargetContext.getPackageName()); + mInstrumentation.startActivitySync(intent); + onView(allOf(withText(mGroup1.getName().toString()))).check( + matches(isDisplayed())); + try { + onView(allOf(withText(mGroup1Channel1.getName().toString()))) + .check(matches(isDisplayed())); + fail("Channel erroneously appearing"); + } catch (Exception e) { + // expected + } + // links to group page + Intents.init(); + onView(allOf(withText(mGroup1.getName().toString()))).perform(click()); + intended(allOf(hasExtra(EXTRA_SHOW_FRAGMENT, + ChannelGroupNotificationSettings.class.getName()))); + Intents.release(); + } + + @Test + public void launchNotificationSetting_showUngroupedChannels() { + final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, mTargetContext.getPackageName()); + mInstrumentation.startActivitySync(intent); + onView(allOf(withText(mUngroupedChannel.getName().toString()))) + .check(matches(isDisplayed())); + // links directly to channel page + Intents.init(); + onView(allOf(withText(mUngroupedChannel.getName().toString()))).perform(click()); + intended(allOf(hasExtra(EXTRA_SHOW_FRAGMENT, ChannelNotificationSettings.class.getName()))); + Intents.release(); + } + + @Test + public void launchNotificationSetting_showGroupsWithOneChannel() { + final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, mTargetContext.getPackageName()); + mInstrumentation.startActivitySync(intent); + + onView(allOf(withText(mGroup2Channel1.getName().toString()))) + .check(matches(isDisplayed())); + try { + onView(allOf(withText(mGroup2.getName().toString()))).check( + matches(isDisplayed())); + fail("Group erroneously appearing"); + } catch (Exception e) { + // expected + } + + // links directly to channel page + Intents.init(); + onView(allOf(withText(mGroup2Channel1.getName().toString()))).perform(click()); + intended(allOf(hasExtra(EXTRA_SHOW_FRAGMENT, ChannelNotificationSettings.class.getName()))); + Intents.release(); + } + + private NotificationChannel createChannel(NotificationChannelGroup group, + String id) { + NotificationChannel channel = new NotificationChannel(id, id, IMPORTANCE_DEFAULT); + if (group != null) { + channel.setGroup(group.getId()); + } + mNm.createNotificationChannel(channel); + return channel; + } } diff --git a/tests/unit/src/com/android/settings/notification/ChannelGroupNotificationSettingsTest.java b/tests/unit/src/com/android/settings/notification/ChannelGroupNotificationSettingsTest.java new file mode 100644 index 00000000000..ce2c408fa50 --- /dev/null +++ b/tests/unit/src/com/android/settings/notification/ChannelGroupNotificationSettingsTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2017 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; + +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_MIN; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; + +import static org.hamcrest.Matchers.allOf; +import static org.junit.Assert.fail; + +import android.app.INotificationManager; +import android.app.Instrumentation; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.os.Process; +import android.os.ServiceManager; +import android.provider.Settings; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ChannelGroupNotificationSettingsTest { + + private Context mTargetContext; + private Instrumentation mInstrumentation; + private NotificationManager mNm; + + @Before + public void setUp() { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mTargetContext = mInstrumentation.getTargetContext(); + mNm = (NotificationManager) mTargetContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + + @Test + public void launchNotificationSetting_displaysChannels() { + NotificationChannelGroup group = + new NotificationChannelGroup(this.getClass().getName(), this.getClass().getName()); + group.setDescription("description"); + NotificationChannel channel = new NotificationChannel(this.getClass().getName(), + "channel" + this.getClass().getName(), IMPORTANCE_MIN); + channel.setGroup(this.getClass().getName()); + NotificationChannel channel2 = new NotificationChannel("2"+this.getClass().getName(), + "2channel" + this.getClass().getName(), IMPORTANCE_MIN); + channel2.setGroup(this.getClass().getName()); + + mNm.createNotificationChannelGroup(group); + mNm.createNotificationChannel(channel); + mNm.createNotificationChannel(channel2); + + final Intent intent = new Intent(Settings.ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, mTargetContext.getPackageName()) + .putExtra(Settings.EXTRA_CHANNEL_GROUP_ID, group.getId()); + + mInstrumentation.startActivitySync(intent); + + onView(allOf(withText(group.getName().toString()))).check(matches(isDisplayed())); + onView(allOf(withText(channel.getName().toString()))).check( + matches(isDisplayed())); + onView(allOf(withText(group.getDescription().toString()))).check( + matches(isDisplayed())); + onView(allOf(withText(channel2.getName().toString()))).check( + matches(isDisplayed())); + try { + onView(allOf(withText("Android is blocking this group of notifications from" + + " appearing on this device"))).check(matches(isDisplayed())); + fail("Blocking footer erroneously appearing"); + } catch (Exception e) { + // expected + } + } + + @Test + public void launchNotificationSettings_blockedGroup() throws Exception { + NotificationChannelGroup blocked = + new NotificationChannelGroup("blocked", "blocked"); + NotificationChannel channel = + new NotificationChannel("channel", "channel", IMPORTANCE_HIGH); + channel.setGroup(blocked.getId()); + mNm.createNotificationChannelGroup(blocked); + mNm.createNotificationChannel(channel); + + INotificationManager sINM = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + blocked.setBlocked(true); + sINM.updateNotificationChannelGroupForPackage( + mTargetContext.getPackageName(), Process.myUid(), blocked); + + final Intent intent = new Intent(Settings.ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, mTargetContext.getPackageName()) + .putExtra(Settings.EXTRA_CHANNEL_GROUP_ID, blocked.getId()); + mInstrumentation.startActivitySync(intent); + + onView(allOf(withText("Off"), isDisplayed())).check(matches(isDisplayed())); + onView(allOf(withText("Android is blocking this group of notifications from" + + " appearing on this device"))).check(matches(isDisplayed())); + + try { + onView(allOf(withText(channel.getName().toString()))).check(matches(isDisplayed())); + fail("settings appearing for blocked group"); + } catch (Exception e) { + // expected + } + } +} diff --git a/tests/unit/src/com/android/settings/notification/ChannelNotificationSettingsTest.java b/tests/unit/src/com/android/settings/notification/ChannelNotificationSettingsTest.java new file mode 100644 index 00000000000..1244dcda4f5 --- /dev/null +++ b/tests/unit/src/com/android/settings/notification/ChannelNotificationSettingsTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 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; + +import static android.app.NotificationManager.IMPORTANCE_MIN; +import static android.app.NotificationManager.IMPORTANCE_NONE; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; + +import static org.hamcrest.Matchers.allOf; +import static org.junit.Assert.fail; + +import android.app.INotificationManager; +import android.app.Instrumentation; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.os.Process; +import android.os.ServiceManager; +import android.provider.Settings; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ChannelNotificationSettingsTest { + + private Context mTargetContext; + private Instrumentation mInstrumentation; + private NotificationChannel mNotificationChannel; + private NotificationManager mNm; + + @Before + public void setUp() { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mTargetContext = mInstrumentation.getTargetContext(); + + mNm = (NotificationManager) mTargetContext.getSystemService(Context.NOTIFICATION_SERVICE); + mNotificationChannel = new NotificationChannel(this.getClass().getName(), + this.getClass().getName(), IMPORTANCE_MIN); + mNm.createNotificationChannel(mNotificationChannel); + } + + @Test + public void launchNotificationSetting_shouldNotCrash() { + final Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, mTargetContext.getPackageName()) + .putExtra(Settings.EXTRA_CHANNEL_ID, mNotificationChannel.getId()); + mInstrumentation.startActivitySync(intent); + + onView(allOf(withText(mNotificationChannel.getName().toString()))).check( + matches(isDisplayed())); + } + + @Test + public void launchNotificationSettings_blockedChannel() throws Exception { + NotificationChannel blocked = + new NotificationChannel("blocked", "blocked", IMPORTANCE_NONE); + mNm.createNotificationChannel(blocked); + + INotificationManager sINM = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + blocked.setImportance(IMPORTANCE_NONE); + sINM.updateNotificationChannelForPackage( + mTargetContext.getPackageName(), Process.myUid(), blocked); + + final Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, mTargetContext.getPackageName()) + .putExtra(Settings.EXTRA_CHANNEL_ID, blocked.getId()); + mInstrumentation.startActivitySync(intent); + + onView(allOf(withText("Off"), isDisplayed())).check(matches(isDisplayed())); + onView(allOf(withText("Android is blocking this category of notifications from" + + " appearing on this device"))).check(matches(isDisplayed())); + + try { + onView(allOf(withText("On the lock screen"))).check(matches(isDisplayed())); + fail("settings appearing for blocked channel"); + } catch (Exception e) { + // expected + } + } +}