diff --git a/res/xml/legacy_channel_notification_settings.xml b/res/xml/app_notification_settings.xml similarity index 51% rename from res/xml/legacy_channel_notification_settings.xml rename to res/xml/app_notification_settings.xml index 519bf5d4875..00a914202eb 100644 --- a/res/xml/legacy_channel_notification_settings.xml +++ b/res/xml/app_notification_settings.xml @@ -15,7 +15,15 @@ --> + xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"> + + + + - - + - - + - - + + + + + diff --git a/res/xml/channel_notification_settings.xml b/res/xml/channel_notification_settings.xml new file mode 100644 index 00000000000..aaadce4f1a9 --- /dev/null +++ b/res/xml/channel_notification_settings.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/upgraded_app_notification_settings.xml b/res/xml/notification_group_settings.xml similarity index 55% rename from res/xml/upgraded_app_notification_settings.xml rename to res/xml/notification_group_settings.xml index f9a3304267e..c1381974467 100644 --- a/res/xml/upgraded_app_notification_settings.xml +++ b/res/xml/notification_group_settings.xml @@ -17,11 +17,24 @@ - - + + + + + + + + + + diff --git a/res/xml/upgraded_channel_notification_settings.xml b/res/xml/upgraded_channel_notification_settings.xml deleted file mode 100644 index ee23435576b..00000000000 --- a/res/xml/upgraded_channel_notification_settings.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/com/android/settings/notification/AllowSoundPreferenceController.java b/src/com/android/settings/notification/AllowSoundPreferenceController.java new file mode 100644 index 00000000000..dcd5e45de93 --- /dev/null +++ b/src/com/android/settings/notification/AllowSoundPreferenceController.java @@ -0,0 +1,81 @@ +/* + * 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_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; + +import android.app.NotificationChannel; +import android.content.Context; +import android.support.v7.preference.Preference; +import android.util.Log; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.RestrictedSwitchPreference; + +public class AllowSoundPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final String TAG = "AllowSoundPrefContr"; + private static final String KEY_IMPORTANCE = "allow_sound"; + private NotificationSettingsBase.ImportanceListener mImportanceListener; + + public AllowSoundPreferenceController(Context context, + NotificationSettingsBase.ImportanceListener importanceListener, + NotificationBackend backend) { + super(context, backend); + mImportanceListener = importanceListener; + } + + @Override + public String getPreferenceKey() { + return KEY_IMPORTANCE; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + return mChannel != null && NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId()); + + } + + public void updateState(Preference preference) { + if (mChannel != null) { + RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference; + pref.setDisabledByAdmin(mAdmin); + pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin()); + pref.setChecked(mChannel.getImportance() >= IMPORTANCE_DEFAULT + || mChannel.getImportance() == IMPORTANCE_UNSPECIFIED); + } else { Log.i(TAG, "tried to updatestate on a null channel?!"); } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mChannel != null) { + final int importance = + ((Boolean) newValue ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_LOW); + mChannel.setImportance(importance); + mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); + saveChannel(); + mImportanceListener.onImportanceChanged(); + } + return true; + } +} diff --git a/src/com/android/settings/notification/AppLinkPreferenceController.java b/src/com/android/settings/notification/AppLinkPreferenceController.java new file mode 100644 index 00000000000..ff5945b5141 --- /dev/null +++ b/src/com/android/settings/notification/AppLinkPreferenceController.java @@ -0,0 +1,55 @@ +/* + * 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.content.Context; +import android.support.v7.preference.Preference; + +import com.android.settings.core.PreferenceControllerMixin; + +/** + * Controls link to reach more preference settings inside the app. + */ +public class AppLinkPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin { + + private static final String TAG = "AppLinkPrefContr"; + private static final String KEY_APP_LINK = "app_link"; + + public AppLinkPreferenceController(Context context) { + super(context, null); + } + + @Override + public String getPreferenceKey() { + return KEY_APP_LINK; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + return mAppRow.settingsIntent != null; + } + + public void updateState(Preference preference) { + if (mAppRow != null) { + preference.setIntent(mAppRow.settingsIntent); + } + } +} diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java index 95c9560d165..af168d60c80 100644 --- a/src/com/android/settings/notification/AppNotificationSettings.java +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -16,12 +16,10 @@ package com.android.settings.notification; -import android.app.Activity; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; -import android.app.NotificationManager; +import android.content.Context; import android.content.Intent; -import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.provider.Settings; @@ -29,43 +27,29 @@ 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; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Switch; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; -import com.android.settings.applications.LayoutPreference; -import com.android.settings.notification.NotificationBackend.AppRow; -import com.android.settings.widget.EntityHeaderController; import com.android.settings.widget.MasterSwitchPreference; -import com.android.settings.widget.SwitchBar; -import com.android.settingslib.RestrictedSwitchPreference; -import com.android.settingslib.widget.FooterPreference; +import com.android.settingslib.core.AbstractPreferenceController; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import static android.app.NotificationManager.IMPORTANCE_NONE; -import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; - /** These settings are per app, so should not be returned in global search results. */ public class AppNotificationSettings extends NotificationSettingsBase { private static final String TAG = "AppNotificationSettings"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static String KEY_GENERAL_CATEGORY = "categories"; - private static String KEY_DELETED = "deleted"; private List mChannelGroupList; - private List mChannelGroups = new ArrayList(); - private FooterPreference mDeletedChannels; @Override public int getMetricsCategory() { @@ -84,24 +68,13 @@ public class AppNotificationSettings extends NotificationSettingsBase { if (getPreferenceScreen() != null) { getPreferenceScreen().removeAll(); - mChannelGroups.clear(); - mDeletedChannels = null; - mShowLegacyChannelConfig = false; + mDynamicPreferences.clear(); } - addPreferencesFromResource(R.xml.notification_settings); - getPreferenceScreen().setOrderingAsAdded(true); - setupBlock(); - addHeaderPref(); - - mShowLegacyChannelConfig = mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid); if (mShowLegacyChannelConfig) { - mChannel = mBackend.getChannel( - mAppRow.pkg, mAppRow.uid, NotificationChannel.DEFAULT_CHANNEL_ID); - populateDefaultChannelPrefs(); + addPreferencesFromResource(R.xml.channel_notification_settings); } else { - addPreferencesFromResource(R.xml.upgraded_app_notification_settings); - setupBadge(); + addPreferencesFromResource(R.xml.app_notification_settings); // Load channel settings new AsyncTask() { @Override @@ -117,41 +90,59 @@ public class AppNotificationSettings extends NotificationSettingsBase { return; } populateList(); - addAppLinkPref(); } }.execute(); } + getPreferenceScreen().setOrderingAsAdded(true); - updateDependents(mAppRow.banned); + for (NotificationPreferenceController controller : mControllers) { + controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin); + controller.displayPreference(getPreferenceScreen()); + } + updatePreferenceStates(); } - private void addHeaderPref() { - ArrayMap rows = new ArrayMap<>(); - rows.put(mAppRow.pkg, mAppRow); - collectConfigActivities(rows); - final Activity activity = getActivity(); - final Preference pref = EntityHeaderController - .newInstance(activity, this /* fragment */, null /* header */) - .setRecyclerView(getListView(), getLifecycle()) - .setIcon(mAppRow.icon) - .setLabel(mAppRow.label) - .setPackageName(mAppRow.pkg) - .setUid(mAppRow.uid) - .setHasAppInfoLink(true) - .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE, - EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE) - .done(activity, getPrefContext()); - pref.setKey(KEY_HEADER); - getPreferenceScreen().addPreference(pref); + @Override + protected String getLogTag() { + return TAG; } + @Override + protected int getPreferenceScreenResId() { + return R.xml.notification_settings; + } + + @Override + protected List getPreferenceControllers(Context context) { + mControllers = new ArrayList<>(); + mControllers.add(new HeaderPreferenceController(context, this)); + mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend)); + mControllers.add(new BadgePreferenceController(context, mBackend)); + mControllers.add(new AllowSoundPreferenceController( + context, mImportanceListener, mBackend)); + mControllers.add(new ImportancePreferenceController(context)); + mControllers.add(new SoundPreferenceController(context, this, + mImportanceListener, mBackend)); + mControllers.add(new LightsPreferenceController(context, mBackend)); + mControllers.add(new VibrationPreferenceController(context, mBackend)); + mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context), + mBackend)); + mControllers.add(new DndPreferenceController(context, getLifecycle(), mBackend)); + mControllers.add(new AppLinkPreferenceController(context)); + mControllers.add(new DescriptionPreferenceController(context)); + mControllers.add(new NotificationsOffPreferenceController(context)); + mControllers.add(new DeletedChannelsPreferenceController(context, mBackend)); + return new ArrayList<>(mControllers); + } + + private void populateList() { - if (!mChannelGroups.isEmpty()) { + if (!mDynamicPreferences.isEmpty()) { // If there's anything in mChannelGroups, we've called populateChannelList twice. // Clear out existing channels and log. Log.w(TAG, "Notification channel group posted twice to settings - old size " + - mChannelGroups.size() + ", new size " + mChannelGroupList.size()); - for (Preference p : mChannelGroups) { + mDynamicPreferences.size() + ", new size " + mChannelGroupList.size()); + for (Preference p : mDynamicPreferences) { getPreferenceScreen().removePreference(p); } } @@ -160,7 +151,7 @@ public class AppNotificationSettings extends NotificationSettingsBase { groupCategory.setTitle(R.string.notification_channels); groupCategory.setKey(KEY_GENERAL_CATEGORY); getPreferenceScreen().addPreference(groupCategory); - mChannelGroups.add(groupCategory); + mDynamicPreferences.add(groupCategory); Preference empty = new Preference(getPrefContext()); empty.setTitle(R.string.no_channels); @@ -168,20 +159,8 @@ public class AppNotificationSettings extends NotificationSettingsBase { groupCategory.addPreference(empty); } else { populateGroupList(); - int deletedChannelCount = mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid); - if (deletedChannelCount > 0 && - getPreferenceScreen().findPreference(KEY_DELETED) == null) { - 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); - } + mImportanceListener.onImportanceChanged(); } - updateDependents(mAppRow.banned); } private void populateGroupList() { @@ -190,7 +169,7 @@ public class AppNotificationSettings extends NotificationSettingsBase { groupCategory.setKey(KEY_GENERAL_CATEGORY); groupCategory.setOrderingAsAdded(true); getPreferenceScreen().addPreference(groupCategory); - mChannelGroups.add(groupCategory); + mDynamicPreferences.add(groupCategory); for (NotificationChannelGroup group : mChannelGroupList) { final List channels = group.getChannels(); int N = channels.size(); @@ -240,91 +219,6 @@ public class AppNotificationSettings extends NotificationSettingsBase { parent.addPreference(groupPref); } - void setupBadge() { - mBadge = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BADGE); - mBadge.setDisabledByAdmin(mSuspendedAppsAdmin); - if (mChannel == null) { - mBadge.setChecked(mAppRow.showBadge); - } else { - mBadge.setChecked(mChannel.canShowBadge()); - } - mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - final boolean value = (Boolean) newValue; - if (mChannel == null) { - mBackend.setShowBadge(mPkg, mUid, value); - } else { - mChannel.setShowBadge(value); - mChannel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE); - mBackend.updateChannel(mPkg, mUid, mChannel); - } - return true; - } - }); - } - - protected 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(!mAppRow.banned); - mSwitchBar.addOnSwitchChangeListener(new SwitchBar.OnSwitchChangeListener() { - @Override - public void onSwitchChanged(Switch switchView, boolean isChecked) { - if (mShowLegacyChannelConfig && mChannel != null) { - final int importance = isChecked ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE; - mImportanceToggle.setChecked(importance == IMPORTANCE_UNSPECIFIED); - mChannel.setImportance(importance); - mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); - mBackend.updateChannel(mPkg, mUid, mChannel); - } - mBackend.setNotificationsEnabledForPackage(mPkgInfo.packageName, mUid, isChecked); - mAppRow.banned = true; - updateDependents(!isChecked); - } - }); - - mBlockBar = new LayoutPreference(getPrefContext(), switchBarContainer); - mBlockBar.setOrder(ORDER_FIRST); - mBlockBar.setKey(KEY_BLOCK); - getPreferenceScreen().addPreference(mBlockBar); - - if (mAppRow.systemApp && !mAppRow.banned) { - setVisible(mBlockBar, false); - } - - setupBlockDesc(R.string.app_notifications_off_desc); - } - - protected void updateDependents(boolean banned) { - for (PreferenceCategory category : mChannelGroups) { - setVisible(category, !banned); - } - if (mDeletedChannels != null) { - setVisible(mDeletedChannels, !banned); - } - setVisible(mBlockedDesc, banned); - setVisible(mBadge, !banned); - if (mShowLegacyChannelConfig) { - setVisible(mImportanceToggle, !banned); - setVisible(mPriority, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT) - || (checkCanBeVisible(NotificationManager.IMPORTANCE_LOW) - && mDndVisualEffectsSuppressed)); - setVisible(mVisibilityOverride, !banned && - checkCanBeVisible(NotificationManager.IMPORTANCE_LOW) && isLockScreenSecure()); - } - if (mAppLink != null) { - setVisible(mAppLink, !banned); - } - if (mAppRow.systemApp && !mAppRow.banned) { - setVisible(mBlockBar, false); - } - } - - private Comparator mChannelGroupComparator = new Comparator() { diff --git a/src/com/android/settings/notification/BadgePreferenceController.java b/src/com/android/settings/notification/BadgePreferenceController.java new file mode 100644 index 00000000000..6b72c50bb71 --- /dev/null +++ b/src/com/android/settings/notification/BadgePreferenceController.java @@ -0,0 +1,90 @@ +/* + * 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.provider.Settings.Secure.NOTIFICATION_BADGING; + +import android.content.Context; +import android.provider.Settings; +import android.support.v7.preference.Preference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.RestrictedSwitchPreference; + +public class BadgePreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final String TAG = "BadgePrefContr"; + private static final String KEY_BADGE = "badge"; + private static final int SYSTEM_WIDE_ON = 1; + private static final int SYSTEM_WIDE_OFF = 0; + + public BadgePreferenceController(Context context, + NotificationBackend backend) { + super(context, backend); + } + + @Override + public String getPreferenceKey() { + return KEY_BADGE; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + if (mAppRow == null && mChannel == null) { + return false; + } + if (Settings.Secure.getInt(mContext.getContentResolver(), + NOTIFICATION_BADGING, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) { + return false; + } + if (mChannel != null && !mAppRow.showBadge) { + return false; + } + return true; + } + + public void updateState(Preference preference) { + if (mAppRow != null) { + RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference; + pref.setDisabledByAdmin(mAdmin); + if (mChannel != null) { + pref.setChecked(mChannel.canShowBadge()); + pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin()); + } else { + pref.setChecked(mAppRow.showBadge); + } + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean showBadge = (Boolean) newValue; + if (mChannel != null) { + mChannel.setShowBadge(showBadge); + saveChannel(); + } else if (mAppRow != null){ + mAppRow.showBadge = showBadge; + mBackend.setShowBadge(mAppRow.pkg, mAppRow.uid, showBadge); + } + return true; + } + +} diff --git a/src/com/android/settings/notification/BlockPreferenceController.java b/src/com/android/settings/notification/BlockPreferenceController.java new file mode 100644 index 00000000000..5c366eafc96 --- /dev/null +++ b/src/com/android/settings/notification/BlockPreferenceController.java @@ -0,0 +1,113 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_NONE; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; + +import android.app.NotificationManager; +import android.content.Context; +import android.support.v7.preference.Preference; +import android.widget.Switch; + +import com.android.settings.R; +import com.android.settings.applications.LayoutPreference; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.widget.SwitchBar; + +public class BlockPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, SwitchBar.OnSwitchChangeListener { + + private static final String KEY_BLOCK = "block"; + private NotificationSettingsBase.ImportanceListener mImportanceListener; + + public BlockPreferenceController(Context context, + NotificationSettingsBase.ImportanceListener importanceListener, + NotificationBackend backend) { + super(context, backend); + mImportanceListener = importanceListener; + } + + @Override + public String getPreferenceKey() { + return KEY_BLOCK; + } + + @Override + public boolean isAvailable() { + if (mAppRow == null) { + return false; + } + if (mChannel != null) { + return isChannelBlockable(); + } else if (mChannelGroup != null && mChannelGroup.getGroup() != null) { + return isChannelGroupBlockable(); + } else { + return !mAppRow.systemApp || (mAppRow.systemApp && mAppRow.banned); + } + } + + public void updateState(Preference preference) { + LayoutPreference pref = (LayoutPreference) preference; + SwitchBar bar = pref.findViewById(R.id.switch_bar); + if (bar != null) { + bar.show(); + try { + bar.addOnSwitchChangeListener(this); + } catch (IllegalStateException e) { + // an exception is thrown if you try to add the listener twice + } + bar.setDisabledByAdmin(mAdmin); + + if (mChannel != null) { + bar.setChecked(!mAppRow.banned + && mChannel.getImportance() != NotificationManager.IMPORTANCE_NONE); + } else if (mChannelGroup != null && mChannelGroup.getGroup() != null) { + bar.setChecked(!mAppRow.banned && !mChannelGroup.isBlocked()); + } else { + bar.setChecked(!mAppRow.banned); + } + } + } + + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + boolean blocked = !isChecked; + if (mChannel != null) { + final int originalImportance = mChannel.getImportance(); + // setting the initial state of the switch in updateState() triggers this callback. + // It's always safe to override the importance if it's meant to be blocked or if + // it was blocked and we are unblocking it. + if (blocked || originalImportance == IMPORTANCE_NONE) { + final int importance = blocked ? IMPORTANCE_NONE + : DEFAULT_CHANNEL_ID.equals(mChannel.getId()) + ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_DEFAULT; + mChannel.setImportance(importance); + saveChannel(); + } + } else if (mChannelGroup != null && mChannelGroup.getGroup() != null) { + mChannelGroup.setBlocked(blocked); + mBackend.updateChannelGroup(mAppRow.pkg, mAppRow.uid, mChannelGroup.getGroup()); + } else if (mAppRow != null) { + mAppRow.banned = blocked; + mBackend.setNotificationsEnabledForPackage(mAppRow.pkg, mAppRow.uid, !blocked); + } + mImportanceListener.onImportanceChanged(); + } +} diff --git a/src/com/android/settings/notification/ChannelGroupNotificationSettings.java b/src/com/android/settings/notification/ChannelGroupNotificationSettings.java index 7837ec866df..68dd91bfdfc 100644 --- a/src/com/android/settings/notification/ChannelGroupNotificationSettings.java +++ b/src/com/android/settings/notification/ChannelGroupNotificationSettings.java @@ -16,20 +16,15 @@ package com.android.settings.notification; -import android.app.Activity; import android.app.NotificationChannel; +import android.content.Context; 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 com.android.settingslib.core.AbstractPreferenceController; import java.util.ArrayList; import java.util.Collections; @@ -38,12 +33,6 @@ 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; @@ -52,137 +41,68 @@ public class ChannelGroupNotificationSettings extends NotificationSettingsBase { @Override public void onResume() { super.onResume(); - if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mChannelGroup == null) { + if (mAppRow == null || mChannelGroup == null || mChannelGroup.getGroup() == 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()); + for (NotificationPreferenceController controller : mControllers) { + controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin); + controller.displayPreference(getPreferenceScreen()); + } + updatePreferenceStates(); } @Override - void setupBadge() { + protected String getLogTag() { + return TAG; + } + @Override + protected int getPreferenceScreenResId() { + return R.xml.notification_group_settings; + } + + @Override + protected List getPreferenceControllers(Context context) { + mControllers = new ArrayList<>(); + mControllers.add(new HeaderPreferenceController(context, this)); + mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend)); + mControllers.add(new AppLinkPreferenceController(context)); + mControllers.add(new NotificationsOffPreferenceController(context)); + mControllers.add(new DescriptionPreferenceController(context)); + return new ArrayList<>(mControllers); } private void populateChannelList() { - if (!mChannels.isEmpty()) { - // If there's anything in mChannels, we've called populateChannelList twice. + if (!mDynamicPreferences.isEmpty()) { + // If there's anything in mDynamicPreferences, 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) { + mDynamicPreferences.size() + ", new size " + mDynamicPreferences.size()); + for (Preference p : mDynamicPreferences) { getPreferenceScreen().removePreference(p); } } - if (mChannelGroup.getChannels().isEmpty()) { + if (mChannelGroup.getGroup().getChannels().isEmpty()) { Preference empty = new Preference(getPrefContext()); empty.setTitle(R.string.no_channels); empty.setEnabled(false); getPreferenceScreen().addPreference(empty); - mChannels.add(empty); + mDynamicPreferences.add(empty); } else { - final List channels = mChannelGroup.getChannels(); + final List channels = mChannelGroup.getGroup().getChannels(); Collections.sort(channels, mChannelComparator); for (NotificationChannel channel : channels) { - mChannels.add(populateSingleChannelPrefs( - getPreferenceScreen(), channel, getImportanceSummary(channel))); + mDynamicPreferences.add(populateSingleChannelPrefs( + getPreferenceScreen(), channel, + ImportancePreferenceController.getImportanceSummary( + getPrefContext(), 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/ChannelImportanceSettings.java b/src/com/android/settings/notification/ChannelImportanceSettings.java index 9e9ffd6aa55..bffed08079c 100644 --- a/src/com/android/settings/notification/ChannelImportanceSettings.java +++ b/src/com/android/settings/notification/ChannelImportanceSettings.java @@ -37,6 +37,7 @@ import com.android.settings.R; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settings.widget.RadioButtonPreference; +import com.android.settingslib.core.AbstractPreferenceController; import java.util.ArrayList; import java.util.List; @@ -60,8 +61,8 @@ public class ChannelImportanceSettings extends NotificationSettingsBase @Override public void onResume() { super.onResume(); - if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mChannel == null) { - Log.w(TAG, "Missing package or uid or packageinfo or channel"); + if (mAppRow == null || mChannel == null) { + Log.w(TAG, "Missing package or channel"); finish(); return; } @@ -69,10 +70,19 @@ public class ChannelImportanceSettings extends NotificationSettingsBase } @Override - void setupBadge() {} + protected String getLogTag() { + return TAG; + } @Override - void updateDependents(boolean banned) {} + protected int getPreferenceScreenResId() { + return R.xml.notification_importance; + } + + @Override + protected List getPreferenceControllers(Context context) { + return null; + } @Override public void onPause() { @@ -81,11 +91,6 @@ public class ChannelImportanceSettings extends NotificationSettingsBase private PreferenceScreen createPreferenceHierarchy() { PreferenceScreen root = getPreferenceScreen(); - if (root != null) { - root.removeAll(); - } - addPreferencesFromResource(R.xml.notification_importance); - root = getPreferenceScreen(); for (int i = 0; i < root.getPreferenceCount(); i++) { Preference pref = root.getPreference(i); @@ -148,8 +153,9 @@ public class ChannelImportanceSettings extends NotificationSettingsBase // but the sound you had selected was "Silence", // then set sound for this channel to your default sound, // because you probably intended to cause this channel to actually start making sound. - if (oldImportance < IMPORTANCE_DEFAULT && !hasValidSound(mChannel) && - mChannel.getImportance() >= IMPORTANCE_DEFAULT) { + if (oldImportance < IMPORTANCE_DEFAULT + && !SoundPreferenceController.hasValidSound(mChannel) + && mChannel.getImportance() >= IMPORTANCE_DEFAULT) { mChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), mChannel.getAudioAttributes()); mChannel.lockFields(USER_LOCKED_SOUND); diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java index 9484f7e6bf5..ea17a05c6d3 100644 --- a/src/com/android/settings/notification/ChannelNotificationSettings.java +++ b/src/com/android/settings/notification/ChannelNotificationSettings.java @@ -16,59 +16,23 @@ package com.android.settings.notification; -import android.app.Activity; -import android.app.NotificationChannel; -import android.app.NotificationChannelGroup; -import android.app.NotificationManager; +import android.content.Context; import android.content.Intent; -import android.net.Uri; -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.preference.PreferenceManager; import android.text.TextUtils; -import android.text.BidiFormatter; -import android.text.SpannableStringBuilder; -import android.util.ArrayMap; import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Switch; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; -import com.android.settings.RingtonePreference; -import com.android.settings.Utils; -import com.android.settings.applications.AppInfoBase; -import com.android.settings.applications.LayoutPreference; -import com.android.settings.widget.EntityHeaderController; -import com.android.settings.widget.SwitchBar; -import com.android.settingslib.RestrictedSwitchPreference; -import com.android.settingslib.widget.FooterPreference; +import com.android.settingslib.core.AbstractPreferenceController; -import static android.app.NotificationManager.IMPORTANCE_LOW; -import static android.app.NotificationManager.IMPORTANCE_NONE; -import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import java.util.ArrayList; +import java.util.List; public class ChannelNotificationSettings extends NotificationSettingsBase { private static final String TAG = "ChannelSettings"; - private static final String KEY_LIGHTS = "lights"; - 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; - private RestrictedSwitchPreference mVibrate; - private NotificationSoundPreference mRingtone; - private FooterPreference mFooter; - private NotificationChannelGroup mChannelGroup; - private EntityHeaderController mHeaderPref; - private PreferenceGroup mAdvanced; - @Override public int getMetricsCategory() { return MetricsEvent.NOTIFICATION_TOPIC_NOTIFICATION; @@ -83,308 +47,52 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { return; } - if (getPreferenceScreen() != null) { - getPreferenceScreen().removeAll(); + for (NotificationPreferenceController controller : mControllers) { + controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin); + controller.displayPreference(getPreferenceScreen()); } - addPreferencesFromResource(R.xml.notification_settings); - setupBlock(); - addHeaderPref(); - addAppLinkPref(); - addFooterPref(); - - if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) { - populateDefaultChannelPrefs(); - mShowLegacyChannelConfig = true; - } else { - populateUpgradedChannelPrefs(); - - if (mChannel.getGroup() != null) { - mChannelGroup = mBackend.getGroup(mPkg, mUid, mChannel.getGroup()); - if (mChannelGroup != null) { - setChannelGroupLabel(mChannelGroup.getName()); - } - } - } - - updateDependents(mChannel.getImportance() == IMPORTANCE_NONE); - } - - private void populateUpgradedChannelPrefs() { - addPreferencesFromResource(R.xml.upgraded_channel_notification_settings); - setupBadge(); - setupPriorityPref(mChannel.canBypassDnd()); - setupVisOverridePref(mChannel.getLockscreenVisibility()); - setupLights(); - setupVibrate(); - setupRingtone(); - setupImportance(); - mAdvanced = (PreferenceGroup) findPreference(KEY_ADVANCED); - } - - 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(mChannel.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 setChannelGroupLabel(CharSequence groupName) { - final SpannableStringBuilder summary = new SpannableStringBuilder(); - BidiFormatter bidi = BidiFormatter.getInstance(); - summary.append(bidi.unicodeWrap(mAppRow.label.toString())); - if (groupName != null) { - summary.append(bidi.unicodeWrap(mContext.getText( - R.string.notification_header_divider_symbol_with_spaces))); - summary.append(bidi.unicodeWrap(groupName.toString())); - } - final Activity activity = getActivity(); - mHeaderPref.setSummary(summary.toString()); - mHeaderPref.done(activity, getPrefContext()); - } - - private void addFooterPref() { - if (!TextUtils.isEmpty(mChannel.getDescription())) { - FooterPreference descPref = new FooterPreference(getPrefContext()); - descPref.setOrder(ORDER_LAST); - descPref.setSelectable(false); - descPref.setTitle(mChannel.getDescription()); - getPreferenceScreen().addPreference(descPref); - } - } - - protected void setupBadge() { - mBadge = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BADGE); - mBadge.setDisabledByAdmin(mSuspendedAppsAdmin); - mBadge.setEnabled(mAppRow.showBadge); - mBadge.setChecked(mChannel.canShowBadge()); - - mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - final boolean value = (Boolean) newValue; - mChannel.setShowBadge(value); - mChannel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE); - mBackend.updateChannel(mPkg, mUid, mChannel); - return true; - } - }); - } - - private void setupLights() { - mLights = (RestrictedSwitchPreference) findPreference(KEY_LIGHTS); - mLights.setDisabledByAdmin(mSuspendedAppsAdmin); - mLights.setChecked(mChannel.shouldShowLights()); - mLights.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - final boolean lights = (Boolean) newValue; - mChannel.enableLights(lights); - mChannel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS); - mBackend.updateChannel(mPkg, mUid, mChannel); - return true; - } - }); - } - - private void setupVibrate() { - mVibrate = (RestrictedSwitchPreference) findPreference(KEY_VIBRATE); - mVibrate.setDisabledByAdmin(mSuspendedAppsAdmin); - mVibrate.setEnabled(!mVibrate.isDisabledByAdmin() && isChannelConfigurable(mChannel)); - mVibrate.setChecked(mChannel.shouldVibrate()); - mVibrate.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - final boolean vibrate = (Boolean) newValue; - mChannel.enableVibration(vibrate); - mChannel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION); - mBackend.updateChannel(mPkg, mUid, mChannel); - return true; - } - }); - } - - private void setupRingtone() { - mRingtone = (NotificationSoundPreference) findPreference(KEY_RINGTONE); - mRingtone.setRingtone(mChannel.getSound()); - mRingtone.setEnabled(isChannelConfigurable(mChannel)); - mRingtone.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - mChannel.setSound((Uri) newValue, mChannel.getAudioAttributes()); - mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND); - mBackend.updateChannel(mPkg, mUid, mChannel); - return false; - } - }); - } - - 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(mChannel.getImportance() != NotificationManager.IMPORTANCE_NONE); - mSwitchBar.addOnSwitchChangeListener(new SwitchBar.OnSwitchChangeListener() { - @Override - public void onSwitchChanged(Switch switchView, boolean isChecked) { - int importance = 0; - if (mShowLegacyChannelConfig) { - importance = isChecked ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE; - mImportanceToggle.setChecked(importance == IMPORTANCE_UNSPECIFIED); - } else { - importance = isChecked ? IMPORTANCE_LOW : IMPORTANCE_NONE; - mImportance.setSummary(getImportanceSummary(importance)); - } - mChannel.setImportance(importance); - mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); - mBackend.updateChannel(mPkg, mUid, mChannel); - updateDependents(mChannel.getImportance() == IMPORTANCE_NONE); - } - }); - - mBlockBar = new LayoutPreference(getPrefContext(), switchBarContainer); - mBlockBar.setOrder(ORDER_FIRST); - mBlockBar.setKey(KEY_BLOCK); - getPreferenceScreen().addPreference(mBlockBar); - - if (!isChannelBlockable(mChannel)) { - setVisible(mBlockBar, false); - } - - setupBlockDesc(R.string.channel_notifications_off_desc); - } - - private void setupImportance() { - mImportance = findPreference(KEY_IMPORTANCE); - Bundle channelArgs = new Bundle(); - channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid); - channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg); - channelArgs.putString(Settings.EXTRA_CHANNEL_ID, mChannel.getId()); - mImportance.setEnabled(mSuspendedAppsAdmin == null && isChannelConfigurable(mChannel)); - // Set up intent to show importance selection only if this setting is enabled. - if (mImportance.isEnabled()) { - Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(), - ChannelImportanceSettings.class.getName(), - channelArgs, null, R.string.notification_importance_title, null, - false, getMetricsCategory()); - mImportance.setIntent(channelIntent); - } - mImportance.setSummary(getImportanceSummary(mChannel.getImportance())); - } - - private String getImportanceSummary(int importance) { - String title; - String summary = null; - switch (importance) { - case IMPORTANCE_UNSPECIFIED: - title = getContext().getString(R.string.notification_importance_unspecified); - break; - case NotificationManager.IMPORTANCE_MIN: - title = getContext().getString(R.string.notification_importance_min_title); - summary = getContext().getString(R.string.notification_importance_min); - break; - case NotificationManager.IMPORTANCE_LOW: - title = getContext().getString(R.string.notification_importance_low_title); - summary = getContext().getString(R.string.notification_importance_low); - break; - case NotificationManager.IMPORTANCE_DEFAULT: - title = getContext().getString(R.string.notification_importance_default_title); - if (hasValidSound(mChannel)) { - summary = getContext().getString(R.string.notification_importance_default); - } else { - summary = getContext().getString(R.string.notification_importance_low); - } - break; - case NotificationManager.IMPORTANCE_HIGH: - case NotificationManager.IMPORTANCE_MAX: - title = getContext().getString(R.string.notification_importance_high_title); - if (hasValidSound(mChannel)) { - summary = getContext().getString(R.string.notification_importance_high); - } else { - summary = getContext().getString(R.string.notification_importance_high_silent); - } - break; - default: - return ""; - } - - if (summary != null) { - return getContext().getString(R.string.notification_importance_divider, title, summary); - } else { - return title; - } - } - - @Override - public boolean onPreferenceTreeClick(Preference preference) { - if (preference instanceof RingtonePreference) { - mRingtone.onPrepareRingtonePickerIntent(mRingtone.getIntent()); - startActivityForResult(preference.getIntent(), 200); - return true; - } - return super.onPreferenceTreeClick(preference); + updatePreferenceStates(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mRingtone != null) { - mRingtone.onActivityResult(requestCode, resultCode, data); - } - if (mChannel != null) { - mImportance.setSummary(getImportanceSummary(mChannel.getImportance())); + for (NotificationPreferenceController controller : mControllers) { + if (controller instanceof PreferenceManager.OnActivityResultListener) { + ((PreferenceManager.OnActivityResultListener) controller) + .onActivityResult(requestCode, resultCode, data); + } } } - boolean canPulseLight() { - if (!getResources() - .getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed)) { - return false; - } - return Settings.System.getInt(getContentResolver(), - Settings.System.NOTIFICATION_LIGHT_PULSE, 0) == 1; + @Override + protected String getLogTag() { + return TAG; } - void updateDependents(boolean banned) { - PreferenceGroup parent; - if (mShowLegacyChannelConfig) { - parent = getPreferenceScreen(); - setVisible(mImportanceToggle, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); - } else { - setVisible(mAdvanced, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); - setVisible(mImportance, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); - setVisible(mAdvanced, mLights, checkCanBeVisible( - NotificationManager.IMPORTANCE_DEFAULT) && canPulseLight()); - setVisible(mVibrate, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)); - setVisible(mRingtone, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)); - parent = mAdvanced; - } - setVisible(parent, mBadge, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); - setVisible(parent, mPriority, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT) - || (checkCanBeVisible(NotificationManager.IMPORTANCE_LOW) - && mDndVisualEffectsSuppressed)); - setVisible(parent, mVisibilityOverride, isLockScreenSecure() - &&checkCanBeVisible(NotificationManager.IMPORTANCE_LOW)); - setVisible(mBlockedDesc, mChannel.getImportance() == IMPORTANCE_NONE); - if (mAppLink != null) { - setVisible(mAppLink, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); - } - if (mFooter != null) { - setVisible(mFooter, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); - } + @Override + protected int getPreferenceScreenResId() { + return R.xml.channel_notification_settings; + } + + @Override + protected List getPreferenceControllers(Context context) { + mControllers = new ArrayList<>(); + mControllers.add(new HeaderPreferenceController(context, this)); + mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend)); + mControllers.add(new ImportancePreferenceController(context)); + mControllers.add(new AllowSoundPreferenceController( + context, mImportanceListener, mBackend)); + mControllers.add(new SoundPreferenceController(context, this, + mImportanceListener, mBackend)); + mControllers.add(new VibrationPreferenceController(context, mBackend)); + mControllers.add(new AppLinkPreferenceController(context)); + mControllers.add(new DescriptionPreferenceController(context)); + mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context), + mBackend)); + mControllers.add(new LightsPreferenceController(context, mBackend)); + mControllers.add(new BadgePreferenceController(context, mBackend)); + mControllers.add(new DndPreferenceController(context, getLifecycle(), mBackend)); + mControllers.add(new NotificationsOffPreferenceController(context)); + return new ArrayList<>(mControllers); } } diff --git a/src/com/android/settings/notification/DeletedChannelsPreferenceController.java b/src/com/android/settings/notification/DeletedChannelsPreferenceController.java new file mode 100644 index 00000000000..16eb9edb742 --- /dev/null +++ b/src/com/android/settings/notification/DeletedChannelsPreferenceController.java @@ -0,0 +1,61 @@ +/* + * 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.content.Context; +import android.support.v7.preference.Preference; + +import com.android.settings.R; +import com.android.settings.core.PreferenceControllerMixin; + +public class DeletedChannelsPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin { + + private static final String KEY_DELETED = "deleted"; + + public DeletedChannelsPreferenceController(Context context, NotificationBackend backend) { + super(context, backend); + } + + @Override + public String getPreferenceKey() { + return KEY_DELETED; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + // only visible on app screen + if (mChannel != null || hasValidGroup()) { + return false; + } + + return mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid) > 0; + } + + public void updateState(Preference preference) { + if (mAppRow != null) { + int deletedChannelCount = mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid); + preference.setTitle(mContext.getResources().getQuantityString( + R.plurals.deleted_channels, deletedChannelCount, deletedChannelCount)); + } + preference.setEnabled(false); + preference.setSelectable(false); + } +} diff --git a/src/com/android/settings/notification/DescriptionPreferenceController.java b/src/com/android/settings/notification/DescriptionPreferenceController.java new file mode 100644 index 00000000000..fae2f5f222d --- /dev/null +++ b/src/com/android/settings/notification/DescriptionPreferenceController.java @@ -0,0 +1,67 @@ +/* + * 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.content.Context; +import android.support.v7.preference.Preference; +import android.text.TextUtils; + +import com.android.settings.core.PreferenceControllerMixin; + +public class DescriptionPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin { + + private static final String KEY_DESC = "desc"; + + public DescriptionPreferenceController(Context context) { + super(context, null); + } + + @Override + public String getPreferenceKey() { + return KEY_DESC; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + if (mChannel == null && !hasValidGroup()) { + return false; + } + if (mChannel != null && !TextUtils.isEmpty(mChannel.getDescription())) { + return true; + } + if (hasValidGroup() && !TextUtils.isEmpty(mChannelGroup.getDescription())) { + return true; + } + return false; + } + + public void updateState(Preference preference) { + if (mAppRow != null) { + if (mChannel != null) { + preference.setTitle(mChannel.getDescription()); + } else if (hasValidGroup()) { + preference.setTitle(mChannelGroup.getDescription()); + } + } + preference.setEnabled(false); + preference.setSelectable(false); + } +} diff --git a/src/com/android/settings/notification/DndPreferenceController.java b/src/com/android/settings/notification/DndPreferenceController.java new file mode 100644 index 00000000000..af60401754d --- /dev/null +++ b/src/com/android/settings/notification/DndPreferenceController.java @@ -0,0 +1,86 @@ +/* + * 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.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.support.v7.preference.Preference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnResume; + +public class DndPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener, + LifecycleObserver, OnResume { + + private static final String KEY_BYPASS_DND = "bypass_dnd"; + private boolean mVisualEffectsSuppressed; + + public DndPreferenceController(Context context, Lifecycle lifecycle, + NotificationBackend backend) { + super(context, backend); + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public void onResume() { + NotificationManager.Policy policy = mNm.getNotificationPolicy(); + mVisualEffectsSuppressed = policy != null && policy.suppressedVisualEffects != 0; + } + + @Override + public String getPreferenceKey() { + return KEY_BYPASS_DND; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + return checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT) + || (checkCanBeVisible(NotificationManager.IMPORTANCE_LOW) + && mVisualEffectsSuppressed); + } + + public void updateState(Preference preference) { + if (mChannel != null) { + RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference; + pref.setDisabledByAdmin(mAdmin); + pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin()); + pref.setChecked(mChannel.canBypassDnd()); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mChannel != null) { + final boolean bypassZenMode = (Boolean) newValue; + mChannel.setBypassDnd(bypassZenMode); + mChannel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY); + saveChannel(); + } + return true; + } + +} diff --git a/src/com/android/settings/notification/HeaderPreferenceController.java b/src/com/android/settings/notification/HeaderPreferenceController.java new file mode 100644 index 00000000000..3d51b25ea7a --- /dev/null +++ b/src/com/android/settings/notification/HeaderPreferenceController.java @@ -0,0 +1,100 @@ +/* + * 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 com.android.settings.widget.EntityHeaderController.PREF_KEY_APP_HEADER; + +import android.content.Context; +import android.support.v14.preference.PreferenceFragment; +import android.support.v7.preference.Preference; +import android.text.BidiFormatter; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.view.View; + +import com.android.settings.R; +import com.android.settings.applications.LayoutPreference; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.widget.EntityHeaderController; + +public class HeaderPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin { + + private final PreferenceFragment mFragment; + + public HeaderPreferenceController(Context context, PreferenceFragment fragment) { + super(context, null); + mFragment = fragment; + } + + @Override + public String getPreferenceKey() { + return PREF_KEY_APP_HEADER; + } + + @Override + public boolean isAvailable() { + return mAppRow != null; + } + + public void updateState(Preference preference) { + if (mAppRow != null && mFragment != null) { + LayoutPreference pref = (LayoutPreference) preference; + EntityHeaderController controller = EntityHeaderController + .newInstance(mFragment.getActivity(), mFragment, + pref.findViewById(R.id.entity_header)); + pref = controller.setIcon(mAppRow.icon) + .setLabel(getLabel()) + .setSummary(getSummary()) + .setPackageName(mAppRow.pkg) + .setUid(mAppRow.uid) + .setButtonActions(EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE, + EntityHeaderController.ActionType.ACTION_NONE) + .setHasAppInfoLink(true) + .done(mFragment.getActivity(), mContext); + pref.findViewById(R.id.entity_header).setVisibility(View.VISIBLE); + } + } + + CharSequence getLabel() { + return mChannel != null ? mChannel.getName() + : mChannelGroup != null && mChannelGroup.getGroup() != null + ? mChannelGroup.getGroup().getName() + : mAppRow.label; + } + + CharSequence getSummary() { + if (mChannel != null) { + if (mChannelGroup != null && mChannelGroup.getGroup() != null + && !TextUtils.isEmpty(mChannelGroup.getGroup().getName())) { + final SpannableStringBuilder summary = new SpannableStringBuilder(); + BidiFormatter bidi = BidiFormatter.getInstance(); + summary.append(bidi.unicodeWrap(mAppRow.label.toString())); + summary.append(bidi.unicodeWrap(mContext.getText( + R.string.notification_header_divider_symbol_with_spaces))); + summary.append(bidi.unicodeWrap(mChannelGroup.getGroup().getName().toString())); + return summary; + } else { + return mAppRow.label; + } + } else if (mChannelGroup != null && mChannelGroup.getGroup() != null) { + return mAppRow.label; + } else { + return ""; + } + } +} diff --git a/src/com/android/settings/notification/ImportancePreferenceController.java b/src/com/android/settings/notification/ImportancePreferenceController.java new file mode 100644 index 00000000000..ba47c54a4e2 --- /dev/null +++ b/src/com/android/settings/notification/ImportancePreferenceController.java @@ -0,0 +1,127 @@ +/* + * 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_UNSPECIFIED; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; +import android.support.v7.preference.Preference; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.applications.AppInfoBase; +import com.android.settings.core.PreferenceControllerMixin; + +public class ImportancePreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin { + + private static final String KEY_IMPORTANCE = "importance"; + + // Ironically doesn't take an importance listener because the importance is not changed + // by this controller's preference but by the screen it links to. + public ImportancePreferenceController(Context context) { + super(context, null); + } + + @Override + public String getPreferenceKey() { + return KEY_IMPORTANCE; + } + + private int getMetricsCategory() { + return MetricsProto.MetricsEvent.NOTIFICATION_TOPIC_NOTIFICATION; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + if (mChannel == null) { + return false; + } + return !NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId()); + } + + public void updateState(Preference preference) { + if (mAppRow!= null && mChannel != null) { + preference.setEnabled(mAdmin == null && isChannelConfigurable()); + 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, mChannel.getId()); + if (preference.isEnabled()) { + Intent channelIntent = Utils.onBuildStartFragmentIntent(mContext, + ChannelImportanceSettings.class.getName(), + channelArgs, null, R.string.notification_importance_title, null, + false, getMetricsCategory()); + preference.setIntent(channelIntent); + preference.setSummary(getImportanceSummary(mContext, mChannel)); + } + } + } + + protected static String getImportanceSummary(Context context, NotificationChannel channel) { + String title; + String summary = null; + int importance = channel.getImportance(); + switch (importance) { + case IMPORTANCE_UNSPECIFIED: + title = context.getString(R.string.notification_importance_unspecified); + break; + case NotificationManager.IMPORTANCE_MIN: + title = context.getString(R.string.notification_importance_min_title); + summary = context.getString(R.string.notification_importance_min); + break; + case NotificationManager.IMPORTANCE_LOW: + title = context.getString(R.string.notification_importance_low_title); + summary = context.getString(R.string.notification_importance_low); + break; + case NotificationManager.IMPORTANCE_DEFAULT: + title = context.getString(R.string.notification_importance_default_title); + if (SoundPreferenceController.hasValidSound(channel)) { + summary = context.getString(R.string.notification_importance_default); + } else { + summary = context.getString(R.string.notification_importance_low); + } + break; + case NotificationManager.IMPORTANCE_HIGH: + case NotificationManager.IMPORTANCE_MAX: + title = context.getString(R.string.notification_importance_high_title); + if (SoundPreferenceController.hasValidSound(channel)) { + summary = context.getString(R.string.notification_importance_high); + } else { + summary = context.getString(R.string.notification_importance_high_silent); + } + break; + default: + return ""; + } + + if (summary != null) { + return context.getString(R.string.notification_importance_divider, title, summary); + } else { + return title; + } + } +} diff --git a/src/com/android/settings/notification/LightsPreferenceController.java b/src/com/android/settings/notification/LightsPreferenceController.java new file mode 100644 index 00000000000..230c3e295d2 --- /dev/null +++ b/src/com/android/settings/notification/LightsPreferenceController.java @@ -0,0 +1,85 @@ +/* + * 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.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.provider.Settings; +import android.support.v7.preference.Preference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnResume; + +public class LightsPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final String KEY_LIGHTS = "lights"; + + public LightsPreferenceController(Context context, NotificationBackend backend) { + super(context, backend); + } + + @Override + public String getPreferenceKey() { + return KEY_LIGHTS; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + if (mChannel == null) { + return false; + } + return checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT) && canPulseLight() + && !NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId()); + } + + public void updateState(Preference preference) { + if (mChannel != null) { + RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference; + pref.setDisabledByAdmin(mAdmin); + pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin()); + pref.setChecked(mChannel.shouldShowLights()); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mChannel != null) { + final boolean lights = (Boolean) newValue; + mChannel.enableLights(lights); + saveChannel(); + } + return true; + } + + boolean canPulseLight() { + if (!mContext.getResources() + .getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed)) { + return false; + } + return Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_LIGHT_PULSE, 0) == 1; + } + +} diff --git a/src/com/android/settings/notification/NotificationFooterPreference.java b/src/com/android/settings/notification/NotificationFooterPreference.java new file mode 100644 index 00000000000..d44ebee2473 --- /dev/null +++ b/src/com/android/settings/notification/NotificationFooterPreference.java @@ -0,0 +1,57 @@ +/* + * 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.content.Context; +import android.support.v4.content.res.TypedArrayUtils; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceViewHolder; +import android.text.method.LinkMovementMethod; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.android.settingslib.R; + +/** + * FooterPreference that can have any key or ordering. + */ +public class NotificationFooterPreference extends Preference { + + public NotificationFooterPreference(Context context, AttributeSet attrs) { + super(context, attrs, TypedArrayUtils.getAttr( + context, R.attr.footerPreferenceStyle, android.R.attr.preferenceStyle)); + init(); + } + + public NotificationFooterPreference(Context context) { + this(context, null); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + TextView title = holder.itemView.findViewById(android.R.id.title); + title.setMovementMethod(new LinkMovementMethod()); + title.setClickable(false); + title.setLongClickable(false); + } + + private void init() { + setIcon(R.drawable.ic_info_outline_24dp); + setSelectable(false); + } +} diff --git a/src/com/android/settings/notification/NotificationPreferenceController.java b/src/com/android/settings/notification/NotificationPreferenceController.java new file mode 100644 index 00000000000..b1ef69eaacf --- /dev/null +++ b/src/com/android/settings/notification/NotificationPreferenceController.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 static android.app.NotificationManager.IMPORTANCE_NONE; + +import android.annotation.Nullable; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.UserManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceGroup; +import android.support.v7.preference.PreferenceScreen; +import android.util.Log; + +import com.android.settings.wrapper.NotificationChannelGroupWrapper; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.core.AbstractPreferenceController; + +import java.util.Objects; + +/** + * Parent class for preferences appearing on notification setting pages at the app, + * notification channel group, or notification channel level. + */ +public abstract class NotificationPreferenceController extends AbstractPreferenceController +{ + private static final String TAG = "ChannelPrefContr"; + @Nullable protected NotificationChannel mChannel; + @Nullable protected NotificationChannelGroupWrapper mChannelGroup; + protected RestrictedLockUtils.EnforcedAdmin mAdmin; + protected NotificationBackend.AppRow mAppRow; + protected final NotificationManager mNm; + protected final NotificationBackend mBackend; + protected final Context mContext; + protected final UserManager mUm; + protected final PackageManager mPm; + protected Preference mPreference; + + public NotificationPreferenceController(Context context, NotificationBackend backend) { + super(context); + mContext = context; + mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + mBackend = backend; + mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mPm = mContext.getPackageManager(); + } + + /** + * Returns true if field's parent object is not blocked. + */ + @Override + public boolean isAvailable() { + if (mAppRow == null) { + return false; + } + if (mAppRow.banned) { + return false; + } + if (mChannel != null) { + return mChannel.getImportance() != IMPORTANCE_NONE; + } + if (mChannelGroup != null && mChannelGroup.getGroup() == null) { + return !mChannelGroup.isBlocked(); + } + return true; + } + + /** + * Displays or removes preference in this controller. + */ + @Override + public void displayPreference(PreferenceScreen screen) { + if (isAvailable()) { + final Preference preference = screen.findPreference(getPreferenceKey()); + if (mPreference != null && preference == null) { + screen.addPreference(mPreference); + } + if (preference != null) { + mPreference = preference; + } + if (this instanceof Preference.OnPreferenceChangeListener) { + mPreference.setOnPreferenceChangeListener( + (Preference.OnPreferenceChangeListener) this); + } + } else { + findAndRemovePreference(screen, getPreferenceKey()); + } + } + + // finds the preference recursively and removes it from its parent + private void findAndRemovePreference(PreferenceGroup prefGroup, String key) { + final int preferenceCount = prefGroup.getPreferenceCount(); + for (int i = preferenceCount - 1; i >= 0; i--) { + final Preference preference = prefGroup.getPreference(i); + final String curKey = preference.getKey(); + + if (curKey != null && curKey.equals(key)) { + mPreference = preference; + prefGroup.removePreference(preference); + } + + if (preference instanceof PreferenceGroup) { + findAndRemovePreference((PreferenceGroup) preference, key); + } + } + } + + protected void onResume(NotificationBackend.AppRow appRow, + @Nullable NotificationChannel channel, @Nullable NotificationChannelGroupWrapper group, + RestrictedLockUtils.EnforcedAdmin admin) { + mAppRow = appRow; + mChannel = channel; + mChannelGroup = group; + mAdmin = admin; + } + + protected boolean checkCanBeVisible(int minImportanceVisible) { + if (mChannel == null) { + Log.w(TAG, "No channel"); + return false; + } + + int importance = mChannel.getImportance(); + if (importance == NotificationManager.IMPORTANCE_UNSPECIFIED) { + return true; + } + return importance >= minImportanceVisible; + } + + protected void saveChannel() { + if (mChannel != null && mAppRow != null) { + mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, mChannel); + } + } + + protected boolean isChannelConfigurable() { + if (mChannel != null && mAppRow != null) { + return !Objects.equals(mChannel.getId(), mAppRow.lockedChannelId); + } + return false; + } + + protected boolean isChannelBlockable() { + if (mChannel != null && mAppRow != null) { + if (!mAppRow.systemApp) { + return true; + } + + return mChannel.isBlockableSystem() + || mChannel.getImportance() == IMPORTANCE_NONE; + } + return false; + } + + protected boolean isChannelGroupBlockable() { + if (mChannelGroup != null && mChannelGroup.getGroup() == null && mAppRow != null) { + if (!mAppRow.systemApp) { + return true; + } + + return mChannelGroup.isBlocked(); + } + return false; + } + + protected boolean hasValidGroup() { + return mChannelGroup != null && mChannelGroup.getGroup() != null; + } +} diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java index 3f366e1be3e..e9da5d96f32 100644 --- a/src/com/android/settings/notification/NotificationSettingsBase.java +++ b/src/com/android/settings/notification/NotificationSettingsBase.java @@ -27,10 +27,15 @@ 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.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; import com.android.settings.widget.MasterSwitchPreference; import com.android.settings.widget.SwitchBar; +import com.android.settings.wrapper.NotificationChannelGroupWrapper; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.widget.FooterPreference; import android.app.Notification; @@ -53,10 +58,12 @@ import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; +import android.provider.SearchIndexableResource; import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; +import android.support.v7.preference.PreferenceScreen; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -68,66 +75,30 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -abstract public class NotificationSettingsBase extends SettingsPreferenceFragment { +abstract public class NotificationSettingsBase extends DashboardFragment { private static final String TAG = "NotifiSettingsBase"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final Intent APP_NOTIFICATION_PREFS_CATEGORY_INTENT - = new Intent(Intent.ACTION_MAIN) - .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES); - - protected static final int ORDER_FIRST = -500; - protected static final int ORDER_LAST = 1000; - - protected static final String KEY_APP_LINK = "app_link"; - protected static final String KEY_HEADER = "header"; - protected static final String KEY_BLOCK = "block"; - protected static final String KEY_BADGE = "badge"; - protected static final String KEY_BYPASS_DND = "bypass_dnd"; - protected static final String KEY_VISIBILITY_OVERRIDE = "visibility_override"; - protected static final String KEY_BLOCKED_DESC = "block_desc"; - protected static final String KEY_ALLOW_SOUND = "allow_sound"; - protected PackageManager mPm; - protected UserManager mUm; protected NotificationBackend mBackend = new NotificationBackend(); - protected LockPatternUtils mLockPatternUtils; protected NotificationManager mNm; protected Context mContext; - protected boolean mCreated; + protected int mUid; protected int mUserId; protected String mPkg; protected PackageInfo mPkgInfo; - protected RestrictedSwitchPreference mBadge; - protected RestrictedSwitchPreference mPriority; - protected RestrictedDropDownPreference mVisibilityOverride; - protected RestrictedSwitchPreference mImportanceToggle; - protected LayoutPreference mBlockBar; - protected SwitchBar mSwitchBar; - protected FooterPreference mBlockedDesc; - protected Preference mAppLink; - protected EnforcedAdmin mSuspendedAppsAdmin; - protected boolean mDndVisualEffectsSuppressed; - - protected NotificationChannelGroup mChannelGroup; + protected NotificationChannelGroupWrapper mChannelGroup; protected NotificationChannel mChannel; protected NotificationBackend.AppRow mAppRow; - protected boolean mShowLegacyChannelConfig = false; + protected boolean mShowLegacyChannelConfig = false; protected boolean mListeningToPackageRemove; - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (DEBUG) Log.d(TAG, "onActivityCreated mCreated=" + mCreated); - if (mCreated) { - Log.w(TAG, "onActivityCreated: ignoring duplicate call"); - return; - } - mCreated = true; - } + protected List mControllers = new ArrayList<>(); + protected List mDynamicPreferences = new ArrayList<>(); + protected ImportanceListener mImportanceListener = new ImportanceListener(); @Override public void onCreate(Bundle savedInstanceState) { @@ -143,7 +114,6 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen } mPm = getPackageManager(); - mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); mNm = NotificationManager.from(mContext); mPkg = args != null && args.containsKey(AppInfoBase.ARG_PACKAGE_NAME) @@ -187,35 +157,41 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen return; } mAppRow = mBackend.loadAppRow(mContext, mPm, mPkgInfo); + if (mAppRow == null) { + Log.w(TAG, "Can't load package"); + finish(); + return; + } + collectConfigActivities(); Bundle args = getArguments(); 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, + NotificationChannelGroup group = + (args != null && args.containsKey(Settings.EXTRA_CHANNEL_GROUP_ID)) + ? mBackend.getGroupWithChannels(mPkg, mUid, args.getString(Settings.EXTRA_CHANNEL_GROUP_ID)) - : null; + : null; + if (group != null) { + mChannelGroup = new NotificationChannelGroupWrapper(group); + } mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended( mContext, mPkg, mUserId); - NotificationManager.Policy policy = mNm.getNotificationPolicy(); - mDndVisualEffectsSuppressed = policy == null ? false : policy.suppressedVisualEffects != 0; - mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended( - mContext, mPkg, mUserId); - } + mShowLegacyChannelConfig = mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid) + || (mChannel != null + && NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())); - protected void setVisible(Preference p, boolean visible) { - setVisible(getPreferenceScreen(), p, visible); - } - - protected void setVisible(PreferenceGroup parent, Preference p, boolean visible) { - final boolean isVisible = parent.findPreference(p.getKey()) != null; - if (isVisible == visible) return; - if (visible) { - parent.addPreference(p); - } else { - parent.removePreference(p); + if (mShowLegacyChannelConfig) { + mChannel = mBackend.getChannel( + mAppRow.pkg, mAppRow.uid, NotificationChannel.DEFAULT_CHANNEL_ID); + } + if (mChannel != null && !TextUtils.isEmpty(mChannel.getGroup())) { + group = mBackend.getGroup(mPkg, mUid, mChannel.getGroup()); + if (group != null) { + mChannelGroup = new NotificationChannelGroupWrapper(group); + } } } @@ -224,49 +200,34 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen getActivity().finish(); } - private List queryNotificationConfigActivities() { - if (DEBUG) Log.d(TAG, "APP_NOTIFICATION_PREFS_CATEGORY_INTENT is " - + APP_NOTIFICATION_PREFS_CATEGORY_INTENT); + protected void collectConfigActivities() { + Intent intent = new Intent(Intent.ACTION_MAIN) + .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES) + .setPackage(mAppRow.pkg); final List resolveInfos = mPm.queryIntentActivities( - APP_NOTIFICATION_PREFS_CATEGORY_INTENT, + intent, 0 //PackageManager.MATCH_DEFAULT_ONLY ); - return resolveInfos; - } - - protected void collectConfigActivities(ArrayMap rows) { - final List resolveInfos = queryNotificationConfigActivities(); - applyConfigActivities(rows, resolveInfos); - } - - private void applyConfigActivities(ArrayMap rows, - List resolveInfos) { if (DEBUG) Log.d(TAG, "Found " + resolveInfos.size() + " preference activities" + (resolveInfos.size() == 0 ? " ;_;" : "")); for (ResolveInfo ri : resolveInfos) { final ActivityInfo activityInfo = ri.activityInfo; final ApplicationInfo appInfo = activityInfo.applicationInfo; - final NotificationBackend.AppRow row = rows.get(appInfo.packageName); - if (row == null) { - if (DEBUG) Log.v(TAG, "Ignoring notification preference activity (" - + activityInfo.name + ") for unknown package " - + activityInfo.packageName); - continue; - } - if (row.settingsIntent != null) { + if (mAppRow.settingsIntent != null) { if (DEBUG) Log.v(TAG, "Ignoring duplicate notification preference activity (" + activityInfo.name + ") for package " + activityInfo.packageName); continue; } - row.settingsIntent = new Intent(APP_NOTIFICATION_PREFS_CATEGORY_INTENT) + mAppRow.settingsIntent = intent + .setPackage(null) .setClassName(activityInfo.packageName, activityInfo.name); if (mChannel != null) { - row.settingsIntent.putExtra(Notification.EXTRA_CHANNEL_ID, mChannel.getId()); + mAppRow.settingsIntent.putExtra(Notification.EXTRA_CHANNEL_ID, mChannel.getId()); } if (mChannelGroup != null) { - row.settingsIntent.putExtra( - Notification.EXTRA_CHANNEL_GROUP_ID, mChannelGroup.getId()); + mAppRow.settingsIntent.putExtra( + Notification.EXTRA_CHANNEL_GROUP_ID, mChannelGroup.getGroup().getId()); } } } @@ -292,134 +253,6 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen return null; } - protected void addAppLinkPref() { - if (mAppRow.settingsIntent != null && mAppLink == null) { - addPreferencesFromResource(R.xml.inapp_notification_settings); - mAppLink = findPreference(KEY_APP_LINK); - mAppLink.setIntent(mAppRow.settingsIntent); - } - } - - protected void populateDefaultChannelPrefs() { - if (mPkgInfo != null && mChannel != null) { - addPreferencesFromResource(R.xml.legacy_channel_notification_settings); - setupPriorityPref(mChannel.canBypassDnd()); - setupVisOverridePref(mChannel.getLockscreenVisibility()); - setupImportanceToggle(); - setupBadge(); - } - mSwitchBar.setChecked(!mAppRow.banned - && mChannel.getImportance() != NotificationManager.IMPORTANCE_NONE); - } - - abstract void setupBadge(); - - abstract void updateDependents(boolean banned); - - // 'allow sound' - private void setupImportanceToggle() { - mImportanceToggle = (RestrictedSwitchPreference) findPreference(KEY_ALLOW_SOUND); - mImportanceToggle.setDisabledByAdmin(mSuspendedAppsAdmin); - mImportanceToggle.setEnabled(isChannelConfigurable(mChannel) - && !mImportanceToggle.isDisabledByAdmin()); - mImportanceToggle.setChecked(mChannel.getImportance() >= IMPORTANCE_DEFAULT - || mChannel.getImportance() == IMPORTANCE_UNSPECIFIED); - mImportanceToggle.setOnPreferenceChangeListener( - new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - final int importance = - ((Boolean) newValue ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_LOW); - mChannel.setImportance(importance); - mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); - mBackend.updateChannel(mPkg, mUid, mChannel); - updateDependents(mChannel.getImportance() == IMPORTANCE_NONE); - return true; - } - }); - } - - protected void setupPriorityPref(boolean priority) { - mPriority = (RestrictedSwitchPreference) findPreference(KEY_BYPASS_DND); - mPriority.setDisabledByAdmin(mSuspendedAppsAdmin); - mPriority.setEnabled(isChannelConfigurable(mChannel) && !mPriority.isDisabledByAdmin()); - mPriority.setChecked(priority); - mPriority.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - final boolean bypassZenMode = (Boolean) newValue; - mChannel.setBypassDnd(bypassZenMode); - mChannel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY); - mBackend.updateChannel(mPkg, mUid, mChannel); - return true; - } - }); - } - - protected void setupVisOverridePref(int sensitive) { - mVisibilityOverride = - (RestrictedDropDownPreference) findPreference(KEY_VISIBILITY_OVERRIDE); - ArrayList entries = new ArrayList<>(); - ArrayList values = new ArrayList<>(); - - mVisibilityOverride.clearRestrictedItems(); - if (getLockscreenNotificationsEnabled() && getLockscreenAllowPrivateNotifications()) { - final String summaryShowEntry = - getString(R.string.lock_screen_notifications_summary_show); - final String summaryShowEntryValue = - Integer.toString(NotificationManager.VISIBILITY_NO_OVERRIDE); - entries.add(summaryShowEntry); - values.add(summaryShowEntryValue); - setRestrictedIfNotificationFeaturesDisabled(summaryShowEntry, summaryShowEntryValue, - DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS - | DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - } - - final String summaryHideEntry = getString(R.string.lock_screen_notifications_summary_hide); - final String summaryHideEntryValue = Integer.toString(Notification.VISIBILITY_PRIVATE); - entries.add(summaryHideEntry); - values.add(summaryHideEntryValue); - setRestrictedIfNotificationFeaturesDisabled(summaryHideEntry, summaryHideEntryValue, - DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - entries.add(getString(R.string.lock_screen_notifications_summary_disable)); - values.add(Integer.toString(Notification.VISIBILITY_SECRET)); - mVisibilityOverride.setEntries(entries.toArray(new CharSequence[entries.size()])); - mVisibilityOverride.setEntryValues(values.toArray(new CharSequence[values.size()])); - - if (sensitive == NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) { - mVisibilityOverride.setValue(Integer.toString(getGlobalVisibility())); - } else { - mVisibilityOverride.setValue(Integer.toString(sensitive)); - } - mVisibilityOverride.setSummary("%s"); - - mVisibilityOverride.setOnPreferenceChangeListener( - new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - int sensitive = Integer.parseInt((String) newValue); - if (sensitive == getGlobalVisibility()) { - sensitive = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; - } - mChannel.setLockscreenVisibility(sensitive); - mChannel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY); - mBackend.updateChannel(mPkg, mUid, mChannel); - return true; - } - }); - mVisibilityOverride.setDisabledByAdmin(mSuspendedAppsAdmin); - } - - protected void setupBlockDesc(int summaryResId) { - 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( @@ -461,105 +294,48 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen return channelPref; } - protected boolean checkCanBeVisible(int minImportanceVisible) { - int importance = mChannel.getImportance(); - if (importance == NotificationManager.IMPORTANCE_UNSPECIFIED) { - return true; - } - 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: - if (hasValidSound(channel)) { - return getContext().getString(R.string.notification_importance_default); - } else { // Silent - return getContext().getString(R.string.notification_importance_low); - } - case NotificationManager.IMPORTANCE_HIGH: - case NotificationManager.IMPORTANCE_MAX: - default: - if (hasValidSound(channel)) { - return getContext().getString(R.string.notification_importance_high); - } else { // Silent - return getContext().getString(R.string.notification_importance_high_silent); - } - } - } - - private void setRestrictedIfNotificationFeaturesDisabled(CharSequence entry, - CharSequence entryValue, int keyguardNotificationFeatures) { - RestrictedLockUtils.EnforcedAdmin admin = - RestrictedLockUtils.checkIfKeyguardFeaturesDisabled( - mContext, keyguardNotificationFeatures, mUserId); - if (admin != null) { - RestrictedDropDownPreference.RestrictedItem item = - new RestrictedDropDownPreference.RestrictedItem(entry, entryValue, admin); - mVisibilityOverride.addRestrictedItem(item); - } - } - - private int getGlobalVisibility() { - int globalVis = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; - if (!getLockscreenNotificationsEnabled()) { - globalVis = Notification.VISIBILITY_SECRET; - } else if (!getLockscreenAllowPrivateNotifications()) { - globalVis = Notification.VISIBILITY_PRIVATE; - } - return globalVis; - } - - private boolean getLockscreenNotificationsEnabled() { - return Settings.Secure.getInt(getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; - } - - private boolean getLockscreenAllowPrivateNotifications() { - return Settings.Secure.getInt(getContentResolver(), - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0; - } - - protected boolean isLockScreenSecure() { - if (mLockPatternUtils == null) { - mLockPatternUtils = new LockPatternUtils(getActivity()); - } - boolean lockscreenSecure = mLockPatternUtils.isSecure(UserHandle.myUserId()); - UserInfo parentUser = mUm.getProfileParent(UserHandle.myUserId()); - if (parentUser != null){ - lockscreenSecure |= mLockPatternUtils.isSecure(parentUser.id); - } - - return lockscreenSecure; - } - protected boolean isChannelConfigurable(NotificationChannel channel) { - return !channel.getId().equals(mAppRow.lockedChannelId); + if (channel != null && mAppRow != null) { + return !channel.getId().equals(mAppRow.lockedChannelId); + } + return false; } protected boolean isChannelBlockable(NotificationChannel channel) { - if (!mAppRow.systemApp) { - return true; - } + if (channel != null && mAppRow != null) { + if (!mAppRow.systemApp) { + return true; + } - return channel.isBlockableSystem() - || channel.getImportance() == NotificationManager.IMPORTANCE_NONE; + return channel.isBlockableSystem() + || channel.getImportance() == NotificationManager.IMPORTANCE_NONE; + } + return false; } protected boolean isChannelGroupBlockable(NotificationChannelGroup group) { - if (!mAppRow.systemApp) { - return true; - } + if (group != null && mAppRow != null) { + if (!mAppRow.systemApp) { + return true; + } - return group.isBlocked(); + return group.isBlocked(); + } + return false; + } + + protected void setVisible(Preference p, boolean visible) { + setVisible(getPreferenceScreen(), p, visible); + } + + protected void setVisible(PreferenceGroup parent, Preference p, boolean visible) { + final boolean isVisible = parent.findPreference(p.getKey()) != null; + if (isVisible == visible) return; + if (visible) { + parent.addPreference(p); + } else { + parent.removePreference(p); + } } protected void startListeningToPackageRemove() { @@ -604,7 +380,45 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen return left.getId().compareTo(right.getId()); }; - boolean hasValidSound(NotificationChannel channel) { - return channel.getSound() != null && !Uri.EMPTY.equals(channel.getSound()); + /** + * These screens aren't searchable - they only make sense in the context of an app, so + * surfacing a generic version would be impossible. + */ + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex(Context context, + boolean enabled) { + return new ArrayList<>(); + } + + @Override + public List getPreferenceControllers(Context context) { + return getPreferenceControllers(context); + } + }; + + protected class ImportanceListener { + protected void onImportanceChanged() { + final PreferenceScreen screen = getPreferenceScreen(); + for (NotificationPreferenceController controller : mControllers) { + controller.displayPreference(screen); + } + updatePreferenceStates(); + + boolean hideDynamicFields = false; + if (mAppRow == null || mAppRow.banned) { + hideDynamicFields = true; + } else { + if (mChannel != null) { + hideDynamicFields = mChannel.getImportance() == IMPORTANCE_NONE; + } else if (mChannelGroup != null) { + hideDynamicFields = mChannelGroup.isBlocked(); + } + } + for (Preference preference : mDynamicPreferences) { + setVisible(getPreferenceScreen(), preference, !hideDynamicFields); + } + } } } diff --git a/src/com/android/settings/notification/NotificationsOffPreferenceController.java b/src/com/android/settings/notification/NotificationsOffPreferenceController.java new file mode 100644 index 00000000000..74591cfd4ce --- /dev/null +++ b/src/com/android/settings/notification/NotificationsOffPreferenceController.java @@ -0,0 +1,63 @@ +/* + * 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.content.Context; +import android.support.v7.preference.Preference; + +import com.android.settings.R; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.widget.FooterPreference; + +public class NotificationsOffPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin { + + private static final String KEY_BLOCKED_DESC = "block_desc"; + + public NotificationsOffPreferenceController(Context context) { + super(context, null); + } + + @Override + public String getPreferenceKey() { + return KEY_BLOCKED_DESC; + } + + @Override + public boolean isAvailable() { + if (mAppRow == null) { + return false; + } + // Available only when other controllers are unavailable - this UI replaces the UI that + // would give more detailed notification controls. + return !super.isAvailable(); + } + + public void updateState(Preference preference) { + if (mAppRow != null) { + if (mChannel != null) { + preference.setTitle(R.string.channel_notifications_off_desc); + } else if (mChannelGroup != null && mChannelGroup.getGroup() == null) { + preference.setTitle(R.string.channel_group_notifications_off_desc); + } else { + preference.setTitle(R.string.app_notifications_off_desc); + } + } + preference.setEnabled(false); + preference.setSelectable(false); + } +} diff --git a/src/com/android/settings/notification/SoundPreferenceController.java b/src/com/android/settings/notification/SoundPreferenceController.java new file mode 100644 index 00000000000..e4414b6908a --- /dev/null +++ b/src/com/android/settings/notification/SoundPreferenceController.java @@ -0,0 +1,119 @@ +/* + * 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.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.preference.PreferenceManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.core.PreferenceControllerMixin; + +public class SoundPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener, + PreferenceManager.OnActivityResultListener { + + private static final String KEY_SOUND = "ringtone"; + private final SettingsPreferenceFragment mFragment; + private final NotificationSettingsBase.ImportanceListener mListener; + private NotificationSoundPreference mPreference; + protected static final int CODE = 200; + + public SoundPreferenceController(Context context, SettingsPreferenceFragment hostFragment, + NotificationSettingsBase.ImportanceListener importanceListener, + NotificationBackend backend) { + super(context, backend); + mFragment = hostFragment; + mListener = importanceListener; + } + + @Override + public String getPreferenceKey() { + return KEY_SOUND; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + if (mChannel == null) { + return false; + } + return checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT) + && !NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId()); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreference = (NotificationSoundPreference) screen.findPreference(getPreferenceKey()); + } + + public void updateState(Preference preference) { + if (mAppRow!= null && mChannel != null) { + NotificationSoundPreference pref = (NotificationSoundPreference) preference; + pref.setEnabled(mAdmin == null && isChannelConfigurable()); + pref.setRingtone(mChannel.getSound()); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mChannel != null) { + mChannel.setSound((Uri) newValue, mChannel.getAudioAttributes()); + saveChannel(); + } + return true; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (KEY_SOUND.equals(preference.getKey()) && mFragment != null) { + NotificationSoundPreference pref = (NotificationSoundPreference) preference; + pref.onPrepareRingtonePickerIntent(pref.getIntent()); + mFragment.startActivityForResult(preference.getIntent(), CODE); + return true; + } + return false; + } + + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (CODE == requestCode) { + if (mPreference != null) { + mPreference.onActivityResult(requestCode, resultCode, data); + } + // the importance hasn't changed, but the importance description might as a result of + // user's selection. + mListener.onImportanceChanged(); + return true; + } + return false; + } + + protected static boolean hasValidSound(NotificationChannel channel) { + return channel != null + && channel.getSound() != null && !Uri.EMPTY.equals(channel.getSound()); + } +} diff --git a/src/com/android/settings/notification/VibrationPreferenceController.java b/src/com/android/settings/notification/VibrationPreferenceController.java new file mode 100644 index 00000000000..f9b786dc2d8 --- /dev/null +++ b/src/com/android/settings/notification/VibrationPreferenceController.java @@ -0,0 +1,73 @@ +/* + * 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.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Vibrator; +import android.support.v7.preference.Preference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.RestrictedSwitchPreference; + +public class VibrationPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final String KEY_VIBRATE = "vibrate"; + private final Vibrator mVibrator; + + public VibrationPreferenceController(Context context, NotificationBackend backend) { + super(context, backend); + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + } + + @Override + public String getPreferenceKey() { + return KEY_VIBRATE; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable() || mChannel == null) { + return false; + } + return checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT) + && !NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId()) + && mVibrator != null + && mVibrator.hasVibrator(); + } + + public void updateState(Preference preference) { + if (mChannel != null) { + RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference; + pref.setDisabledByAdmin(mAdmin); + pref.setEnabled(!pref.isDisabledByAdmin() && isChannelConfigurable()); + pref.setChecked(mChannel.shouldVibrate()); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mChannel != null) { + final boolean vibrate = (Boolean) newValue; + mChannel.enableVibration(vibrate); + saveChannel(); + } + return true; + } +} diff --git a/src/com/android/settings/notification/VisibilityPreferenceController.java b/src/com/android/settings/notification/VisibilityPreferenceController.java new file mode 100644 index 00000000000..76caac0c852 --- /dev/null +++ b/src/com/android/settings/notification/VisibilityPreferenceController.java @@ -0,0 +1,165 @@ +/* + * 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.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.notification.NotificationListenerService; +import android.support.v7.preference.Preference; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.R; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.RestrictedLockUtils; + +import java.util.ArrayList; + +public class VisibilityPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final String TAG = "ChannelVisPrefContr"; + private static final String KEY_VISIBILITY_OVERRIDE = "visibility_override"; + private LockPatternUtils mLockPatternUtils; + + public VisibilityPreferenceController(Context context, LockPatternUtils utils, + NotificationBackend backend) { + super(context, backend); + mLockPatternUtils = utils; + } + + @Override + public String getPreferenceKey() { + return KEY_VISIBILITY_OVERRIDE; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + if (mChannel == null || mAppRow.banned) { + return false; + } + return checkCanBeVisible(NotificationManager.IMPORTANCE_LOW) && isLockScreenSecure(); + } + + public void updateState(Preference preference) { + if (mChannel != null && mAppRow != null) { + RestrictedDropDownPreference pref = (RestrictedDropDownPreference) preference; + ArrayList entries = new ArrayList<>(); + ArrayList values = new ArrayList<>(); + + pref.clearRestrictedItems(); + if (getLockscreenNotificationsEnabled() && getLockscreenAllowPrivateNotifications()) { + final String summaryShowEntry = + mContext.getString(R.string.lock_screen_notifications_summary_show); + final String summaryShowEntryValue = + Integer.toString(NotificationManager.VISIBILITY_NO_OVERRIDE); + entries.add(summaryShowEntry); + values.add(summaryShowEntryValue); + setRestrictedIfNotificationFeaturesDisabled(pref, summaryShowEntry, + summaryShowEntryValue, + DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS + | DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + } + + final String summaryHideEntry = + mContext.getString(R.string.lock_screen_notifications_summary_hide); + final String summaryHideEntryValue = Integer.toString(Notification.VISIBILITY_PRIVATE); + entries.add(summaryHideEntry); + values.add(summaryHideEntryValue); + setRestrictedIfNotificationFeaturesDisabled(pref, + summaryHideEntry, summaryHideEntryValue, + DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + entries.add(mContext.getString(R.string.lock_screen_notifications_summary_disable)); + values.add(Integer.toString(Notification.VISIBILITY_SECRET)); + pref.setEntries(entries.toArray(new CharSequence[entries.size()])); + pref.setEntryValues(values.toArray(new CharSequence[values.size()])); + + if (mChannel.getLockscreenVisibility() + == NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) { + pref.setValue(Integer.toString(getGlobalVisibility())); + } else { + pref.setValue(Integer.toString(mChannel.getLockscreenVisibility())); + } + pref.setSummary("%s"); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mChannel != null) { + int sensitive = Integer.parseInt((String) newValue); + if (sensitive == getGlobalVisibility()) { + sensitive = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; + } + mChannel.setLockscreenVisibility(sensitive); + mChannel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY); + saveChannel(); + } + return true; + } + + private void setRestrictedIfNotificationFeaturesDisabled(RestrictedDropDownPreference pref, + CharSequence entry, CharSequence entryValue, int keyguardNotificationFeatures) { + RestrictedLockUtils.EnforcedAdmin admin = + RestrictedLockUtils.checkIfKeyguardFeaturesDisabled( + mContext, keyguardNotificationFeatures, mAppRow.userId); + if (admin != null) { + RestrictedDropDownPreference.RestrictedItem item = + new RestrictedDropDownPreference.RestrictedItem(entry, entryValue, admin); + pref.addRestrictedItem(item); + } + } + + private int getGlobalVisibility() { + int globalVis = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; + if (!getLockscreenNotificationsEnabled()) { + globalVis = Notification.VISIBILITY_SECRET; + } else if (!getLockscreenAllowPrivateNotifications()) { + globalVis = Notification.VISIBILITY_PRIVATE; + } + return globalVis; + } + + private boolean getLockscreenNotificationsEnabled() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; + } + + private boolean getLockscreenAllowPrivateNotifications() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0; + } + + protected boolean isLockScreenSecure() { + boolean lockscreenSecure = mLockPatternUtils.isSecure(UserHandle.myUserId()); + UserInfo parentUser = mUm.getProfileParent(UserHandle.myUserId()); + if (parentUser != null){ + lockscreenSecure |= mLockPatternUtils.isSecure(parentUser.id); + } + + return lockscreenSecure; + } + +} diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java index 69b2f9fa5d3..98e0bf48f67 100644 --- a/src/com/android/settings/search/SearchIndexableResources.java +++ b/src/com/android/settings/search/SearchIndexableResources.java @@ -67,7 +67,10 @@ import com.android.settings.location.LocationSettings; import com.android.settings.location.ScanningSettings; import com.android.settings.network.NetworkDashboardFragment; import com.android.settings.nfc.PaymentSettings; +import com.android.settings.notification.AppNotificationSettings; +import com.android.settings.notification.ChannelGroupNotificationSettings; import com.android.settings.notification.ChannelImportanceSettings; +import com.android.settings.notification.ChannelNotificationSettings; import com.android.settings.notification.ConfigureNotificationSettings; import com.android.settings.notification.SoundSettings; import com.android.settings.notification.ZenModeAutomationSettings; @@ -173,6 +176,10 @@ public final class SearchIndexableResources { addIndex(LockscreenDashboardFragment.class); addIndex(ZenModeBehaviorSettings.class); addIndex(ZenModeAutomationSettings.class); + addIndex(AppNotificationSettings.class); + addIndex(ChannelNotificationSettings.class); + addIndex(ChannelImportanceSettings.class); + addIndex(ChannelGroupNotificationSettings.class); } private SearchIndexableResources() { diff --git a/src/com/android/settings/wrapper/NotificationChannelGroupWrapper.java b/src/com/android/settings/wrapper/NotificationChannelGroupWrapper.java new file mode 100644 index 00000000000..dbfff1a6c4b --- /dev/null +++ b/src/com/android/settings/wrapper/NotificationChannelGroupWrapper.java @@ -0,0 +1,64 @@ +/* + * 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.wrapper; + +import android.app.NotificationChannelGroup; + +/** + * Wrapper for {@link NotificationChannelGroup} until roboletric supports O MR1. + */ +public class NotificationChannelGroupWrapper { + + private final NotificationChannelGroup mGroup; + + public NotificationChannelGroupWrapper(NotificationChannelGroup group) { + mGroup = group; + } + + /** + * Get the real group object so we can call APIs directly on it. + */ + public NotificationChannelGroup getGroup() { + return mGroup; + } + + public String getDescription() { + if (mGroup != null) { + return mGroup.getDescription(); + } + return null; + } + + public void setDescription(String desc) { + if (mGroup != null) { + mGroup.setDescription(desc); + } + } + + public boolean isBlocked() { + if (mGroup != null) { + return mGroup.isBlocked(); + } + return true; + } + + public void setBlocked(boolean blocked) { + if (mGroup != null) { + mGroup.setBlocked(blocked); + } + } +} \ No newline at end of file diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index 608e44c459c..e6285ad27ab 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -19,8 +19,6 @@ com.android.settings.wifi.WifiInfo com.android.settings.applications.VrListenerSettings com.android.settings.inputmethod.UserDictionaryList com.android.settings.datausage.DataSaverSummary -com.android.settings.notification.ChannelNotificationSettings -com.android.settings.notification.ChannelGroupNotificationSettings com.android.settings.datausage.AppDataUsage com.android.settings.datausage.DataPlanUsageSummary com.android.settings.accessibility.FontSizePreferenceFragmentForSetupWizard @@ -55,7 +53,6 @@ com.android.settings.applications.InstalledAppDetails com.android.settings.accessibility.ToggleAccessibilityServicePreferenceFragment com.android.settings.print.PrintServiceSettingsFragment com.android.settings.wfd.WifiDisplaySettings -com.android.settings.notification.AppNotificationSettings com.android.settings.deviceinfo.PrivateVolumeSettings com.android.settings.users.AppRestrictionsFragment com.android.settings.deviceinfo.PrivateVolumeUnmount diff --git a/tests/robotests/assets/grandfather_not_sharing_pref_controllers_with_search_provider b/tests/robotests/assets/grandfather_not_sharing_pref_controllers_with_search_provider index b3290725c98..a71b040b080 100644 --- a/tests/robotests/assets/grandfather_not_sharing_pref_controllers_with_search_provider +++ b/tests/robotests/assets/grandfather_not_sharing_pref_controllers_with_search_provider @@ -1 +1 @@ -com.android.settings.fuelgauge.PowerUsageSummary +com.android.settings.fuelgauge.PowerUsageSummary \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/notification/AllowSoundPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/AllowSoundPreferenceControllerTest.java new file mode 100644 index 00000000000..9ba8706b4ca --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/AllowSoundPreferenceControllerTest.java @@ -0,0 +1,244 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.os.UserManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedSwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class AllowSoundPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + + @Mock + NotificationSettingsBase.ImportanceListener mImportanceListener; + + private AllowSoundPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = + spy(new AllowSoundPreferenceController(mContext, mImportanceListener, mBackend)); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(RestrictedSwitchPreference.class)); + mController.onPreferenceChange(mock(RestrictedSwitchPreference.class), true); + } + + @Test + public void testIsAvailable_notIfNull() throws Exception { + mController.onResume(null, mock(NotificationChannel.class), null, null); + assertFalse(mController.isAvailable()); + + mController.onResume(mock(NotificationBackend.AppRow.class), null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfAppBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, mock(NotificationChannel.class), null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfAppCreatedChannel() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something new"); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + when(channel.getId()).thenReturn(DEFAULT_CHANNEL_ID); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState_disabledByAdmin() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( + RestrictedLockUtils.EnforcedAdmin.class)); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_notConfigurable() throws Exception { + String lockedId = "locked"; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = lockedId; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(lockedId); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_configurable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertTrue(pref.isEnabled()); + } + + @Test + public void testUpdateState_checkedForHighImportanceChannel() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + assertTrue(pref.isChecked()); + } + + @Test + public void testUpdateState_checkedForUnspecifiedImportanceChannel() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_UNSPECIFIED); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + assertTrue(pref.isChecked()); + } + + @Test + public void testUpdateState_notCheckedForLowImportanceChannel() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + assertFalse(pref.isChecked()); + } + + @Test + public void testOnPreferenceChange_on() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_LOW); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + pref.setChecked(true); + mController.onPreferenceChange(pref, true); + + assertEquals(IMPORTANCE_UNSPECIFIED, mController.mChannel.getImportance()); + verify(mImportanceListener, times(1)).onImportanceChanged(); + } + + @Test + public void testOnPreferenceChange_off() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + pref.setChecked(false); + mController.onPreferenceChange(pref, false); + + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + assertEquals(IMPORTANCE_LOW, mController.mChannel.getImportance()); + verify(mImportanceListener, times(1)).onImportanceChanged(); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/AppLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/AppLinkPreferenceControllerTest.java new file mode 100644 index 00000000000..b440704d260 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/AppLinkPreferenceControllerTest.java @@ -0,0 +1,135 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_NONE; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.UserManager; +import android.support.v7.preference.Preference; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class AppLinkPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + + private AppLinkPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = spy(new AppLinkPreferenceController(mContext)); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(Preference.class)); + } + + @Test + public void testIsAvailable_notIfNull() throws Exception { + mController.onResume(null, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfAppBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfChannelBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_NONE); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notNoIntent() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + when(channel.getId()).thenReturn(DEFAULT_CHANNEL_ID); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.settingsIntent = new Intent("test"); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + when(channel.getId()).thenReturn(DEFAULT_CHANNEL_ID); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + Intent intent = new Intent("action"); + appRow.settingsIntent = intent; + mController.onResume(appRow, null, null, null); + + Preference pref = new Preference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertEquals(intent, pref.getIntent()); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/BadgePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BadgePreferenceControllerTest.java new file mode 100644 index 00000000000..60524782af0 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BadgePreferenceControllerTest.java @@ -0,0 +1,298 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_NONE; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.provider.Settings.Secure.NOTIFICATION_BADGING; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.os.UserManager; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedSwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class BadgePreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + + private BadgePreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = spy(new BadgePreferenceController(mContext, mBackend)); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(RestrictedSwitchPreference.class)); + mController.onPreferenceChange(mock(RestrictedSwitchPreference.class), true); + } + + @Test + public void testIsAvailable_notIfAppBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, mock(NotificationChannel.class), null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfChannelBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_NONE); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_channel_notIfAppOff() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.showBadge = false; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfOffGlobally() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BADGING, 0); + + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_app() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + mController.onResume(appRow, null, null, null); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BADGING, 1); + + assertTrue(mController.isAvailable()); + } + + @Test + public void testIsAvailable_channel() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.showBadge = true; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BADGING, 1); + + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState_disabledByAdmin() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( + RestrictedLockUtils.EnforcedAdmin.class)); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_channelNotConfigurable() throws Exception { + String lockedId = "locked"; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = lockedId; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(lockedId); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_channel() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = "a"; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.canShowBadge()).thenReturn(true); + mController.onResume(appRow, channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertTrue(pref.isChecked()); + + when(channel.canShowBadge()).thenReturn(false); + mController.onResume(appRow, channel, null, null); + mController.updateState(pref); + + assertFalse(pref.isChecked()); + } + + @Test + public void testUpdateState_app() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.showBadge = true; + mController.onResume(appRow, null, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + assertTrue(pref.isChecked()); + + appRow.showBadge = false; + mController.onResume(appRow, null, null, null); + + mController.updateState(pref); + assertFalse(pref.isChecked()); + } + + @Test + public void testOnPreferenceChange_on_channel() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.showBadge = true; + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_LOW); + channel.setShowBadge(false); + mController.onResume(appRow, channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, true); + assertTrue(channel.canShowBadge()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } + + @Test + public void testOnPreferenceChange_off_channel() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.showBadge = true; + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH); + channel.setShowBadge(true); + mController.onResume(appRow, channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, false); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + assertFalse(channel.canShowBadge()); + } + + @Test + public void testOnPreferenceChange_on_app() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.showBadge = false; + mController.onResume(appRow, null, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, true); + + assertTrue(appRow.showBadge); + verify(mBackend, times(1)).setShowBadge(any(), anyInt(), eq(true)); + } + + @Test + public void testOnPreferenceChange_off_app() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.showBadge = true; + mController.onResume(appRow, null, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, false); + + assertFalse(appRow.showBadge); + verify(mBackend, times(1)).setShowBadge(any(), anyInt(), eq(false)); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/BlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BlockPreferenceControllerTest.java new file mode 100644 index 00000000000..9014f4ee5c1 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BlockPreferenceControllerTest.java @@ -0,0 +1,272 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +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_NONE; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.os.UserManager; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.applications.LayoutPreference; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.widget.SwitchBar; +import com.android.settings.wrapper.NotificationChannelGroupWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class BlockPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + + @Mock + NotificationSettingsBase.ImportanceListener mImportanceListener; + + private BlockPreferenceController mController; + @Mock + private LayoutPreference mPreference; + private SwitchBar mSwitch; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = spy(new BlockPreferenceController(mContext, mImportanceListener, mBackend)); + mSwitch = new SwitchBar(mContext); + when(mPreference.findViewById(R.id.switch_bar)).thenReturn(mSwitch); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(LayoutPreference.class)); + mController.onSwitchChanged(null, false); + } + + @Test + public void testIsAvailable_notIfNull() throws Exception { + mController.onResume(null, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfChannelNotBlockable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = true; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfGroupNotBlockable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = true; + mController.onResume(appRow, null, mock(NotificationChannelGroupWrapper.class), null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfAppNotBlockable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = true; + mController.onResume(appRow, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_systemApp() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = true; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_NONE); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testIsAvailable_nonSystemApp() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = false; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState_app() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, null, null, null); + mController.updateState(mPreference); + + assertNotNull(mPreference.findViewById(R.id.switch_bar)); + + assertFalse(mSwitch.isChecked()); + + appRow.banned = false; + mController.onResume(appRow, null, null, null); + mController.updateState(mPreference); + + assertTrue(mSwitch.isChecked()); + } + + @Test + public void testUpdateState_group() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + when(group.getGroup()).thenReturn(mock(NotificationChannelGroup.class)); + when(group.isBlocked()).thenReturn(true); + mController.onResume(appRow, null, group, null); + mController.updateState(mPreference); + + assertFalse(mSwitch.isChecked()); + + appRow.banned = true; + mController.onResume(appRow, null, group, null); + when(group.isBlocked()).thenReturn(true); + mController.updateState(mPreference); + + assertFalse(mSwitch.isChecked()); + + appRow.banned = false; + mController.onResume(appRow, null, group, null); + when(group.isBlocked()).thenReturn(false); + mController.updateState(mPreference); + + assertTrue(mSwitch.isChecked()); + } + + @Test + public void testUpdateState_channelBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_NONE); + mController.onResume(appRow, channel, null, null); + mController.updateState(mPreference); + + assertFalse(mSwitch.isChecked()); + + appRow.banned = true; + channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + mController.updateState(mPreference); + + assertFalse(mSwitch.isChecked()); + + appRow.banned = false; + channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + mController.updateState(mPreference); + + assertTrue(mSwitch.isChecked()); + } + + @Test + public void testUpdateState_noCrashIfCalledTwice() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); + mController.onResume(appRow, channel, null, null); + mController.updateState(mPreference); + mController.updateState(mPreference); + } + + @Test + public void testUpdateState_doesNotResetImportance() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); + mController.onResume(appRow, channel, null, null); + mController.updateState(mPreference); + + assertEquals(IMPORTANCE_LOW, channel.getImportance()); + } + + @Test + public void testOnSwitchChanged_channel_default() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_UNSPECIFIED); + mController.onResume(appRow, channel, null, null); + mController.updateState(mPreference); + + mController.onSwitchChanged(null, false); + assertEquals(IMPORTANCE_NONE, channel.getImportance()); + + mController.onSwitchChanged(null, true); + assertEquals(IMPORTANCE_UNSPECIFIED, channel.getImportance()); + + verify(mBackend, times(2)).updateChannel(any(), anyInt(), any()); + + } + + @Test + public void testOnSwitchChanged_channel_nonDefault() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + mController.updateState(mPreference); + + mController.onSwitchChanged(null, false); + assertEquals(IMPORTANCE_NONE, channel.getImportance()); + + mController.onSwitchChanged(null, true); + assertEquals(IMPORTANCE_DEFAULT, channel.getImportance()); + + verify(mBackend, times(2)).updateChannel(any(), anyInt(), any()); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/DeletedChannelsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/DeletedChannelsPreferenceControllerTest.java new file mode 100644 index 00000000000..fd903f9322d --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/DeletedChannelsPreferenceControllerTest.java @@ -0,0 +1,129 @@ +/* + * 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 junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.UserManager; +import android.support.v7.preference.Preference; + +import com.android.settings.TestConfig; +import com.android.settings.wrapper.NotificationChannelGroupWrapper; + +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.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class DeletedChannelsPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + + private DeletedChannelsPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = new DeletedChannelsPreferenceController(mContext, mBackend); + } + + @Test + public void noCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(Preference.class)); + } + + @Test + public void isAvailable_appScreen_notIfAppBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void isAvailable_groupScreen_never() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + mController.onResume(appRow, null, mock(NotificationChannelGroupWrapper.class), null); + assertFalse(mController.isAvailable()); + } + + @Test + public void isAvailable_channelScreen_never() throws Exception { + mController.onResume( + new NotificationBackend.AppRow(), mock(NotificationChannel.class), null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void isAvailable_appScreen_notIfNoDeletedChannels() throws Exception { + when(mBackend.getDeletedChannelCount(any(), anyInt())).thenReturn(0); + mController.onResume(new NotificationBackend.AppRow(), null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void isAvailable_appScreen() throws Exception { + when(mBackend.getDeletedChannelCount(any(), anyInt())).thenReturn(1); + mController.onResume(new NotificationBackend.AppRow(), null, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void updateState() throws Exception { + when(mBackend.getDeletedChannelCount(any(), anyInt())).thenReturn(1); + mController.onResume(new NotificationBackend.AppRow(), null, null, null); + + Preference pref = mock(Preference.class); + mController.updateState(pref); + + verify(pref, times(1)).setEnabled(false); + verify(pref, times(1)).setSelectable(false); + verify(mBackend, times(1)).getDeletedChannelCount(any(), anyInt()); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(CharSequence.class); + verify(pref, times(1)).setTitle(argumentCaptor.capture()); + assertTrue(argumentCaptor.getValue().toString().contains("1")); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/DescriptionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/DescriptionPreferenceControllerTest.java new file mode 100644 index 00000000000..1776a9b2f0b --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/DescriptionPreferenceControllerTest.java @@ -0,0 +1,175 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_NONE; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.UserManager; +import android.support.v7.preference.Preference; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.wrapper.NotificationChannelGroupWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class DescriptionPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + + private DescriptionPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = spy(new DescriptionPreferenceController(mContext)); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(Preference.class)); + } + + @Test + public void testIsAvailable_notIfNull() throws Exception { + mController.onResume(null, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfChannelGroupBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + when(group.isBlocked()).thenReturn(true); + mController.onResume(appRow, null, group, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfChannelBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_NONE); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfNoChannelDesc() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfNoChannelGroupDesc() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + when(group.getGroup()).thenReturn(mock(NotificationChannelGroup.class)); + mController.onResume(appRow, null, group, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_channel() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + when(channel.getDescription()).thenReturn("AAA"); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testIsAvailable_channelGroup() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + when(group.getGroup()).thenReturn(mock(NotificationChannelGroup.class)); + when(group.getDescription()).thenReturn("something"); + when(group.isBlocked()).thenReturn(false); + mController.onResume(appRow, null, group, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState_channel() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + when(channel.getDescription()).thenReturn("AAA"); + mController.onResume(appRow, channel, null, null); + + Preference pref = new Preference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertEquals("AAA", pref.getTitle()); + assertFalse(pref.isEnabled()); + assertFalse(pref.isSelectable()); + } + + @Test + public void testUpdateState_channelGroup() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + when(group.getGroup()).thenReturn(mock(NotificationChannelGroup.class)); + when(group.getDescription()).thenReturn("something"); + mController.onResume(appRow, null, group, null); + + Preference pref = new Preference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertEquals("something", pref.getTitle()); + assertFalse(pref.isEnabled()); + assertFalse(pref.isSelectable()); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/DndPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/DndPreferenceControllerTest.java new file mode 100644 index 00000000000..241e279088b --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/DndPreferenceControllerTest.java @@ -0,0 +1,240 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +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 junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.os.UserManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class DndPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + @Mock + private Lifecycle mLifecycle; + + private DndPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = spy(new DndPreferenceController(mContext, mLifecycle, mBackend)); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(RestrictedSwitchPreference.class)); + mController.onPreferenceChange(mock(RestrictedSwitchPreference.class), true); + mController.onResume(); + } + + @Test + public void testIsAvailable_notIfNotImportant_noVisEffects() throws Exception { + when(mNm.getNotificationPolicy()).thenReturn(new NotificationManager.Policy(0, 0, 0, 0)); + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_LOW); + mController.onResume(); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfNotImportant_visEffects() throws Exception { + when(mNm.getNotificationPolicy()).thenReturn(new NotificationManager.Policy(0, 0, 0, 1)); + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_MIN); + mController.onResume(); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_importance_noVisEffects() throws Exception { + when(mNm.getNotificationPolicy()).thenReturn(new NotificationManager.Policy(0, 0, 0, 0)); + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_DEFAULT); + mController.onResume(); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testIsAvailable_important_visEffects() throws Exception { + when(mNm.getNotificationPolicy()).thenReturn(new NotificationManager.Policy(0, 0, 0, 1)); + assertTrue(mNm.getNotificationPolicy().suppressedVisualEffects != 0); + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_LOW); + mController.onResume(); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState_disabledByAdmin() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( + RestrictedLockUtils.EnforcedAdmin.class)); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_notConfigurable() throws Exception { + String lockedId = "locked"; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = lockedId; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(lockedId); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_configurable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertTrue(pref.isEnabled()); + } + + @Test + public void testUpdateState_bypassDnd() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.canBypassDnd()).thenReturn(true); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + assertTrue(pref.isChecked()); + } + + @Test + public void testUpdateState_noBypassDnd() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.canBypassDnd()).thenReturn(false); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + assertFalse(pref.isChecked()); + } + + @Test + public void testOnPreferenceChange_on() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_LOW); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, true); + + assertTrue(channel.canBypassDnd()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } + + @Test + public void testOnPreferenceChange_off() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, false); + + assertFalse(channel.canBypassDnd()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/HeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/HeaderPreferenceControllerTest.java new file mode 100644 index 00000000000..385376f7be1 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/HeaderPreferenceControllerTest.java @@ -0,0 +1,146 @@ +/* + * 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_NONE; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.os.UserManager; +import android.support.v14.preference.PreferenceFragment; +import android.view.View; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.applications.LayoutPreference; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.wrapper.NotificationChannelGroupWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class HeaderPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + + private HeaderPreferenceController mController; + @Mock + private LayoutPreference mPreference; + @Mock + private View mView; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + PreferenceFragment fragment = mock(PreferenceFragment.class); + when(fragment.getContext()).thenReturn(mContext); + Activity activity = mock(Activity.class); + when(activity.getApplicationContext()).thenReturn(mContext); + when(fragment.getActivity()).thenReturn(activity); + mController = spy(new HeaderPreferenceController(mContext, fragment)); + when(mPreference.findViewById(anyInt())).thenReturn(mView); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(LayoutPreference.class)); + } + + @Test + public void testIsAvailable_notIfNull() throws Exception { + mController.onResume(null, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, null, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testGetLabel() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.label = "bananas"; + mController.onResume(appRow, null, null, null); + assertEquals(appRow.label, mController.getLabel()); + + NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); + NotificationChannelGroupWrapper gWrapper = new NotificationChannelGroupWrapper(group); + mController.onResume(appRow, null, gWrapper, null); + assertEquals(group.getName(), mController.getLabel()); + + NotificationChannel channel = new NotificationChannel("cid", "cname", IMPORTANCE_NONE); + mController.onResume(appRow, channel, gWrapper, null); + assertEquals(channel.getName(), mController.getLabel()); + } + + @Test + public void testGetSummary() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.label = "bananas"; + mController.onResume(appRow, null, null, null); + assertEquals("", mController.getSummary()); + + NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); + NotificationChannelGroupWrapper gWrapper = new NotificationChannelGroupWrapper(group); + mController.onResume(appRow, null, gWrapper, null); + assertEquals(appRow.label, mController.getSummary()); + + NotificationChannel channel = new NotificationChannel("cid", "cname", IMPORTANCE_NONE); + mController.onResume(appRow, channel, gWrapper, null); + assertTrue(mController.getSummary().toString().contains(group.getName())); + assertTrue(mController.getSummary().toString().contains(appRow.label)); + + mController.onResume(appRow, channel, null, null); + assertFalse(mController.getSummary().toString().contains(group.getName())); + assertTrue(mController.getSummary().toString().contains(appRow.label)); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ImportancePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ImportancePreferenceControllerTest.java new file mode 100644 index 00000000000..aebd6c9c4b9 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ImportancePreferenceControllerTest.java @@ -0,0 +1,166 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_NONE; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.os.UserManager; +import android.support.v7.preference.Preference; +import android.text.TextUtils; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.RestrictedLockUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class ImportancePreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + + private ImportancePreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = spy(new ImportancePreferenceController(mContext)); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(Preference.class)); + } + + @Test + public void testIsAvailable_notIfNull() throws Exception { + mController.onResume(null, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfAppBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, mock(NotificationChannel.class), null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfChannelBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_NONE); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notForDefaultChannel() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + when(channel.getId()).thenReturn(DEFAULT_CHANNEL_ID); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState_disabledByAdmin() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( + RestrictedLockUtils.EnforcedAdmin.class)); + + Preference pref = new Preference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + assertNull(pref.getIntent()); + } + + @Test + public void testUpdateState_notConfigurable() throws Exception { + String lockedId = "locked"; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = lockedId; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(lockedId); + mController.onResume(appRow, channel, null, null); + + Preference pref = new Preference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + assertNull(pref.getIntent()); + } + + @Test + public void testUpdateState() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + + Preference pref = new Preference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertTrue(pref.isEnabled()); + assertNotNull(pref.getIntent()); + assertFalse(TextUtils.isEmpty(pref.getSummary())); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/LightsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/LightsPreferenceControllerTest.java new file mode 100644 index 00000000000..017cb88818e --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/LightsPreferenceControllerTest.java @@ -0,0 +1,245 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.provider.Settings.System.NOTIFICATION_LIGHT_PULSE; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.os.UserManager; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedSwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O, shadows = { + SettingsShadowResources.class, +}) +public class LightsPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + + private LightsPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = spy(new LightsPreferenceController(mContext, mBackend)); + + // By default allow lights + SettingsShadowResources.overrideResource( + com.android.internal.R.bool.config_intrusiveNotificationLed, true); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_LIGHT_PULSE, 1); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(RestrictedSwitchPreference.class)); + mController.onPreferenceChange(mock(RestrictedSwitchPreference.class), true); + } + + @Test + public void testIsAvailable_notIfConfigNotAllowed() throws Exception { + SettingsShadowResources.overrideResource( + com.android.internal.R.bool.config_intrusiveNotificationLed, false); + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfSettingNotAllowed() throws Exception { + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_LIGHT_PULSE, 0); + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfNotImportant() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_LOW); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfDefaultChannel() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState_disabledByAdmin() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( + RestrictedLockUtils.EnforcedAdmin.class)); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_notConfigurable() throws Exception { + String lockedId = "locked"; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = lockedId; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(lockedId); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_configurable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertTrue(pref.isEnabled()); + } + + @Test + public void testUpdateState_lightsOn() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.shouldShowLights()).thenReturn(true); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + assertTrue(pref.isChecked()); + } + + @Test + public void testUpdateState_lightsOff() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.shouldShowLights()).thenReturn(false); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + assertFalse(pref.isChecked()); + } + + @Test + public void testOnPreferenceChange_on() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_DEFAULT); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, true); + + assertTrue(channel.shouldShowLights()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } + + @Test + public void testOnPreferenceChange_off() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, false); + + assertFalse(channel.shouldShowLights()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/NotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/NotificationPreferenceControllerTest.java new file mode 100644 index 00000000000..d6857402861 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/NotificationPreferenceControllerTest.java @@ -0,0 +1,316 @@ +/* + * 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_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 junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.os.UserManager; +import android.support.v7.preference.Preference; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.wrapper.NotificationChannelGroupWrapper; +import com.android.settingslib.RestrictedLockUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class NotificationPreferenceControllerTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + + private TestPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = new TestPreferenceController(mContext, mBackend); + } + + @Test + public void noCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(Preference.class)); + assertFalse(mController.checkCanBeVisible(IMPORTANCE_UNSPECIFIED)); + mController.saveChannel(); + assertFalse(mController.isChannelConfigurable()); + assertFalse(mController.isChannelBlockable()); + assertFalse(mController.isChannelGroupBlockable()); + } + + @Test + public void isAvailable_notIfNull() throws Exception { + mController.onResume(null, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void isAvailable_notIfAppBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, mock(NotificationChannel.class), + mock(NotificationChannelGroupWrapper.class), null); + assertFalse(mController.isAvailable()); + } + + @Test + public void isAvailable_notIfChannelBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_NONE); + + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void isAvailable_notIfChannelGroupBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + + mController.onResume(appRow, channel, group, null); + when(group.isBlocked()).thenReturn(true); + assertFalse(mController.isAvailable()); + } + + @Test + public void isAvailable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_DEFAULT); + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + when(group.isBlocked()).thenReturn(false); + + mController.onResume(appRow, channel, group, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testOnResume() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + RestrictedLockUtils.EnforcedAdmin admin = mock(RestrictedLockUtils.EnforcedAdmin.class); + + mController.onResume(appRow, channel, group, admin); + + assertEquals(appRow, mController.mAppRow); + assertEquals(channel, mController.mChannel); + assertEquals(group, mController.mChannelGroup); + assertEquals(admin, mController.mAdmin); + } + + @Test + public void testCanBeVisible_unspecified() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_UNSPECIFIED); + + mController.onResume(appRow, channel, null, null); + assertTrue(mController.checkCanBeVisible(IMPORTANCE_MIN)); + } + + @Test + public void testCanBeVisible_sameImportance() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + + mController.onResume(appRow, channel, null, null); + assertTrue(mController.checkCanBeVisible(IMPORTANCE_LOW)); + } + + @Test + public void testCanBeVisible_greaterImportance() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + + mController.onResume(appRow, channel, null, null); + assertTrue(mController.checkCanBeVisible(IMPORTANCE_MIN)); + } + + @Test + public void testCanBeVisible_lesserImportance() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + + mController.onResume(appRow, channel, null, null); + assertFalse(mController.checkCanBeVisible(IMPORTANCE_DEFAULT)); + } + + @Test + public void testSaveImportance() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_DEFAULT); + + mController.onResume(appRow, channel, null, null); + mController.saveChannel(); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } + + @Test + public void testIsConfigurable() { + String sameId = "bananas"; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = sameId; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(sameId); + + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isChannelConfigurable()); + + when(channel.getId()).thenReturn("something new"); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isChannelConfigurable()); + } + + @Test + public void testIsChannelBlockable_nonSystemAppsBlockable() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = false; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.isBlockableSystem()).thenReturn(false); + + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isChannelBlockable()); + } + + @Test + public void testIsChannelBlockable_mostSystemAppsNotBlockable() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = true; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.isBlockableSystem()).thenReturn(false); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isChannelBlockable()); + } + + @Test + public void testIsChannelBlockable_someSystemAppsAreBlockable() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = true; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.isBlockableSystem()).thenReturn(true); + + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isChannelBlockable()); + } + + @Test + public void testIsChannelBlockable_canUndoSystemBlock() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = true; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.isBlockableSystem()).thenReturn(false); + when(channel.getImportance()).thenReturn(IMPORTANCE_NONE); + + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isChannelBlockable()); + } + + @Test + public void testIsChannelGroupBlockable_nonSystemBlockable() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = false; + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + when(group.isBlocked()).thenReturn(false); + + mController.onResume(appRow, null, group, null); + assertTrue(mController.isChannelGroupBlockable()); + } + + @Test + public void testIsChannelGroupBlockable_SystemNotBlockable() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = true; + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + when(group.isBlocked()).thenReturn(false); + + mController.onResume(appRow, null, group, null); + assertFalse(mController.isChannelGroupBlockable()); + } + + @Test + public void testIsChannelGroupBlockable_canUndoSystemBlock() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.systemApp = true; + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + when(group.isBlocked()).thenReturn(true); + + mController.onResume(appRow, null, group, null); + assertTrue(mController.isChannelGroupBlockable()); + } + + private final class TestPreferenceController extends NotificationPreferenceController { + + public TestPreferenceController(Context context, + NotificationBackend backend) { + super(context, backend); + } + + @Override + public String getPreferenceKey() { + return null; + } + } +} diff --git a/tests/robotests/src/com/android/settings/notification/NotificationsOffPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/NotificationsOffPreferenceControllerTest.java new file mode 100644 index 00000000000..e1f9eb70b7a --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/NotificationsOffPreferenceControllerTest.java @@ -0,0 +1,147 @@ +/* + * 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_LOW; +import static android.app.NotificationManager.IMPORTANCE_NONE; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.os.UserManager; +import android.support.v7.preference.Preference; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.wrapper.NotificationChannelGroupWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class NotificationsOffPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + + private NotificationsOffPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = spy(new NotificationsOffPreferenceController(mContext)); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(Preference.class)); + } + + @Test + public void testIsAvailable_yesIfAppBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, null, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testIsAvailable_yesIfChannelGroupBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + when(group.isBlocked()).thenReturn(true); + mController.onResume(appRow, null, group, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testIsAvailable_yesIfChannelBlocked() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_NONE); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState_channel() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_NONE); + mController.onResume(appRow, channel, null, null); + + Preference pref = new Preference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertTrue(pref.getTitle().toString().contains("category")); + assertFalse(pref.isEnabled()); + assertFalse(pref.isSelectable()); + } + + @Test + public void testUpdateState_channelGroup() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannelGroupWrapper group = mock(NotificationChannelGroupWrapper.class); + when(group.isBlocked()).thenReturn(true); + mController.onResume(appRow, null, group, null); + + Preference pref = new Preference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertTrue(pref.getTitle().toString().contains("group")); + assertFalse(pref.isEnabled()); + assertFalse(pref.isSelectable()); + } + + @Test + public void testUpdateState_app() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, null, null, null); + + Preference pref = new Preference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertTrue(pref.getTitle().toString().contains("app")); + assertFalse(pref.isEnabled()); + assertFalse(pref.isSelectable()); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/SoundPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/SoundPreferenceControllerTest.java new file mode 100644 index 00000000000..1d5a791d28b --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/SoundPreferenceControllerTest.java @@ -0,0 +1,257 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Fragment; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.UserManager; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.util.AttributeSet; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.RestrictedLockUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class SoundPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + @Mock + private SettingsPreferenceFragment mFragment; + @Mock + private NotificationSettingsBase.ImportanceListener mImportanceListener; + + private SoundPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = shadowApplication.getApplicationContext(); + mController = spy(new SoundPreferenceController( + mContext, mFragment, mImportanceListener, mBackend)); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(NotificationSoundPreference.class)); + mController.onPreferenceChange(mock(NotificationSoundPreference.class), Uri.EMPTY); + mController.handlePreferenceTreeClick(mock(NotificationSoundPreference.class)); + mController.onActivityResult(1, 1, null); + mController.hasValidSound(null); + } + + @Test + public void testIsAvailable_notIfChannelNull() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + mController.onResume(appRow, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfNotImportant() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_LOW); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfDefaultChannel() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testDisplayPreference_savesPreference() throws Exception { + NotificationSoundPreference pref = mock(NotificationSoundPreference.class); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + + mController.onActivityResult(SoundPreferenceController.CODE, 1, new Intent()); + verify(pref, times(1)).onActivityResult(anyInt(), anyInt(), any()); + } + + @Test + public void testUpdateState_disabledByAdmin() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( + RestrictedLockUtils.EnforcedAdmin.class)); + + Preference pref = new NotificationSoundPreference(mContext, mock(AttributeSet.class)); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_notConfigurable() throws Exception { + String lockedId = "locked"; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = lockedId; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(lockedId); + mController.onResume(appRow, channel, null, null); + + Preference pref = new NotificationSoundPreference(mContext, mock(AttributeSet.class)); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_configurable() throws Exception { + Uri sound = Settings.System.DEFAULT_ALARM_ALERT_URI; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + when(channel.getSound()).thenReturn(sound); + mController.onResume(appRow, channel, null, null); + + NotificationSoundPreference pref = + new NotificationSoundPreference(mContext, mock(AttributeSet.class)); + mController.updateState(pref); + + assertEquals(sound, pref.onRestoreRingtone()); + assertTrue(pref.isEnabled()); + } + + @Test + public void testOnPreferenceChange() throws Exception { + Uri sound = Settings.System.DEFAULT_ALARM_ALERT_URI; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); + channel.setSound(sound, Notification.AUDIO_ATTRIBUTES_DEFAULT); + mController.onResume(appRow, channel, null, null); + + NotificationSoundPreference pref = + new NotificationSoundPreference(mContext, mock(AttributeSet.class)); + mController.updateState(pref); + + mController.onPreferenceChange(pref, Uri.EMPTY); + assertEquals(Uri.EMPTY, channel.getSound()); + assertEquals(Notification.AUDIO_ATTRIBUTES_DEFAULT, channel.getAudioAttributes()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } + + @Test + public void testOnPreferenceTreeClick_incorrectPref() throws Exception { + NotificationSoundPreference pref = mock(NotificationSoundPreference.class); + mController.handlePreferenceTreeClick(pref); + + verify(pref, never()).onPrepareRingtonePickerIntent(any()); + verify(mFragment, never()).startActivityForResult(any(), anyInt()); + } + + + @Test + public void testOnPreferenceTreeClick_correctPref() throws Exception { + NotificationSoundPreference pref = + spy(new NotificationSoundPreference(mContext, mock(AttributeSet.class))); + pref.setKey(mController.getPreferenceKey()); + mController.handlePreferenceTreeClick(pref); + + verify(pref, times(1)).onPrepareRingtonePickerIntent(any()); + verify(mFragment, times(1)).startActivityForResult(any(), anyInt()); + } + + @Test + public void testOnActivityResult() { + NotificationSoundPreference pref = mock(NotificationSoundPreference.class); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + + mController.onActivityResult(SoundPreferenceController.CODE, 1, new Intent("hi")); + verify(pref, times(1)).onActivityResult(anyInt(), anyInt(), any()); + verify(mImportanceListener, times(1)).onImportanceChanged(); + } + + @Test + public void testHasValidSound() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH); + assertTrue(mController.hasValidSound(channel)); + + channel.setSound(Uri.EMPTY, Notification.AUDIO_ATTRIBUTES_DEFAULT); + assertFalse(mController.hasValidSound(channel)); + + channel.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); + assertFalse(mController.hasValidSound(channel)); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/VibrationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/VibrationPreferenceControllerTest.java new file mode 100644 index 00000000000..4695590cb64 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/VibrationPreferenceControllerTest.java @@ -0,0 +1,230 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.os.UserManager; +import android.os.Vibrator; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedSwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O) +public class VibrationPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + Vibrator mVibrator; + @Mock + private UserManager mUm; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + + private VibrationPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + shadowApplication.setSystemService(Context.VIBRATOR_SERVICE, mVibrator); + mContext = shadowApplication.getApplicationContext(); + mController = spy(new VibrationPreferenceController(mContext, mBackend)); + + // by default allow vibration + when(mVibrator.hasVibrator()).thenReturn(true); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(RestrictedSwitchPreference.class)); + mController.onPreferenceChange(mock(RestrictedSwitchPreference.class), true); + } + + @Test + public void testIsAvailable_notSystemDoesNotHave() throws Exception { + when(mVibrator.hasVibrator()).thenReturn(false); + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfNotImportant() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_LOW); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfDefaultChannel() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState_disabledByAdmin() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( + RestrictedLockUtils.EnforcedAdmin.class)); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_notConfigurable() throws Exception { + String lockedId = "locked"; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = lockedId; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(lockedId); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_configurable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + assertTrue(pref.isEnabled()); + } + + @Test + public void testUpdateState_vibrateOn() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.shouldVibrate()).thenReturn(true); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + assertTrue(pref.isChecked()); + } + + @Test + public void testUpdateState_vibrateOff() throws Exception { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.shouldVibrate()).thenReturn(false); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + assertFalse(pref.isChecked()); + } + + @Test + public void testOnPreferenceChange_on() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_DEFAULT); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + mController.updateState(pref); + + mController.onPreferenceChange(pref, true); + + assertTrue(channel.shouldVibrate()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } + + @Test + public void testOnPreferenceChange_off() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, false); + + assertFalse(channel.shouldVibrate()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/VisibilityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/VisibilityPreferenceControllerTest.java new file mode 100644 index 00000000000..ed658feafc6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/VisibilityPreferenceControllerTest.java @@ -0,0 +1,326 @@ +/* + * 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.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_MIN; +import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.Build; +import android.os.UserManager; +import android.provider.Settings; +import android.support.v7.preference.PreferenceScreen; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowRestrictionUtils; +import com.android.settingslib.RestrictedLockUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = Build.VERSION_CODES.O, shadows = { + ShadowRestrictionUtils.class, +}) +public class VisibilityPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + private LockPatternUtils mLockUtils; + @Mock + private UserManager mUm; + @Mock + private DevicePolicyManager mDm; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + + private VisibilityPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + shadowApplication.setSystemService(Context.DEVICE_POLICY_SERVICE, mDm); + mContext = shadowApplication.getApplicationContext(); + mController = spy(new VisibilityPreferenceController(mContext, mLockUtils, mBackend)); + + // by default the lockscreen is secure + when(mLockUtils.isSecure(anyInt())).thenReturn(true); + // and notifications are visible in redacted form + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); + // and not restricted + ShadowRestrictionUtils.setRestricted(false); + // with no managed profile + UserInfo userInfo = new UserInfo(); + when(mUm.getUserInfo(anyInt())).thenReturn(userInfo); + } + + @Test + public void testNoCrashIfNoOnResume() throws Exception { + mController.isAvailable(); + mController.updateState(mock(RestrictedDropDownPreference.class)); + mController.onPreferenceChange(mock(RestrictedDropDownPreference.class), true); + } + + @Test + public void testIsAvailable_notSecure() throws Exception { + when(mLockUtils.isSecure(anyInt())).thenReturn(false); + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfNotImportant() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_MIN); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + + channel = new NotificationChannel("", "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState_disabledByAdmin_disableSecure() throws Exception { + ShadowRestrictionUtils.setRestricted(true); + UserInfo userInfo = new UserInfo(2, "user 2", UserInfo.FLAG_MANAGED_PROFILE); + when(mUm.getUserInfo(anyInt())).thenReturn(userInfo); + List components = new ArrayList<>(); + components.add(new ComponentName("", "")); + when(mDm.getActiveAdminsAsUser(anyInt())).thenReturn(components); + when(mDm.getKeyguardDisabledFeatures(any(), anyInt())) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( + RestrictedLockUtils.EnforcedAdmin.class)); + + RestrictedDropDownPreference pref = mock(RestrictedDropDownPreference.class); + mController.updateState(pref); + + verify(pref, times(2)).addRestrictedItem(any()); + } + + @Test + public void testUpdateState_disabledByAdmin_disableUnredacted() throws Exception { + ShadowRestrictionUtils.setRestricted(true); + UserInfo userInfo = new UserInfo(2, "user 2", UserInfo.FLAG_MANAGED_PROFILE); + when(mUm.getUserInfo(anyInt())).thenReturn(userInfo); + List components = new ArrayList<>(); + components.add(new ComponentName("", "")); + when(mDm.getActiveAdminsAsUser(anyInt())).thenReturn(components); + when(mDm.getKeyguardDisabledFeatures(any(), anyInt())) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn("something"); + mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( + RestrictedLockUtils.EnforcedAdmin.class)); + + RestrictedDropDownPreference pref = mock(RestrictedDropDownPreference.class); + mController.updateState(pref); + + verify(pref, times(1)).addRestrictedItem(any()); + } + + @Test + public void testUpdateState_noLockScreenNotificationsGlobally() throws Exception { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); + + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + mController.onResume(appRow, channel, null, null); + + RestrictedDropDownPreference pref = mock(RestrictedDropDownPreference.class); + mController.updateState(pref); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String[].class); + verify(pref, times(1)).setEntryValues(argumentCaptor.capture()); + assertFalse(Arrays.asList(argumentCaptor.getValue()) + .contains(VISIBILITY_NO_OVERRIDE)); + } + + @Test + public void testUpdateState_noPrivateLockScreenNotificationsGlobally() throws Exception { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0); + + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + mController.onResume(appRow, channel, null, null); + + RestrictedDropDownPreference pref = mock(RestrictedDropDownPreference.class); + mController.updateState(pref); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String[].class); + verify(pref, times(1)).setEntryValues(argumentCaptor.capture()); + assertFalse(Arrays.asList(argumentCaptor.getValue()) + .contains(VISIBILITY_NO_OVERRIDE)); + } + + @Test + public void testUpdateState_noGlobalRestriction() throws Exception { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + mController.onResume(appRow, channel, null, null); + + RestrictedDropDownPreference pref = mock(RestrictedDropDownPreference.class); + mController.updateState(pref); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String[].class); + verify(pref, times(1)).setEntryValues(argumentCaptor.capture()); + List values = Arrays.asList(argumentCaptor.getValue()); + assertEquals(3, values.size()); + assertTrue(values.contains(String.valueOf(VISIBILITY_NO_OVERRIDE))); + assertTrue(values.contains(String.valueOf(Notification.VISIBILITY_PRIVATE))); + assertTrue(values.contains(String.valueOf(Notification.VISIBILITY_SECRET))); + } + + @Test + public void testUpdateState_noChannelOverride() throws Exception { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0); + + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getLockscreenVisibility()).thenReturn(VISIBILITY_NO_OVERRIDE); + mController.onResume(appRow, channel, null, null); + + RestrictedDropDownPreference pref = mock(RestrictedDropDownPreference.class); + mController.updateState(pref); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + verify(pref, times(1)).setValue(argumentCaptor.capture()); + + assertEquals(String.valueOf(Notification.VISIBILITY_PRIVATE), argumentCaptor.getValue()); + } + + @Test + public void testUpdateState_channelOverride() throws Exception { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0); + + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getLockscreenVisibility()).thenReturn(Notification.VISIBILITY_SECRET); + mController.onResume(appRow, channel, null, null); + + RestrictedDropDownPreference pref = mock(RestrictedDropDownPreference.class); + mController.updateState(pref); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + verify(pref, times(1)).setValue(argumentCaptor.capture()); + + assertEquals(String.valueOf(Notification.VISIBILITY_SECRET), argumentCaptor.getValue()); + } + + @Test + public void testOnPreferenceChange_noOverride() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0); + + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", 4); + channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); + mController.onResume(appRow, channel, null, null); + + RestrictedDropDownPreference pref = mock(RestrictedDropDownPreference.class); + mController.updateState(pref); + + mController.onPreferenceChange(pref, String.valueOf(Notification.VISIBILITY_PRIVATE)); + + assertEquals(VISIBILITY_NO_OVERRIDE, channel.getLockscreenVisibility()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } + + @Test + public void testOnPreferenceChange_override() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0); + + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", 4); + channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE); + mController.onResume(appRow, channel, null, null); + + RestrictedDropDownPreference pref = mock(RestrictedDropDownPreference.class); + mController.updateState(pref); + + mController.onPreferenceChange(pref, String.valueOf(Notification.VISIBILITY_SECRET)); + + assertEquals(Notification.VISIBILITY_SECRET, channel.getLockscreenVisibility()); + verify(mBackend, times(1)).updateChannel(any(), anyInt(), any()); + } +}