diff --git a/res/drawable/ic_notification_alert.xml b/res/drawable/ic_notification_alert.xml new file mode 100644 index 00000000000..07e7b48700b --- /dev/null +++ b/res/drawable/ic_notification_alert.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_notification_block.xml b/res/drawable/ic_notification_block.xml new file mode 100644 index 00000000000..d4f0a2aa877 --- /dev/null +++ b/res/drawable/ic_notification_block.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_notification_silence.xml b/res/drawable/ic_notification_silence.xml new file mode 100644 index 00000000000..673340f6532 --- /dev/null +++ b/res/drawable/ic_notification_silence.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/notif_importance_preference.xml b/res/layout/notif_importance_preference.xml new file mode 100644 index 00000000000..5d79ff3af67 --- /dev/null +++ b/res/layout/notif_importance_preference.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index 7b55a2bb94b..2fbba7e3114 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -124,6 +124,11 @@ #fffdd835 #ff9e9e9e + + #ffff0000 + #fbbc04 + #30a751 + @*android:color/accent_device_default_light diff --git a/res/values/dimens.xml b/res/values/dimens.xml index a36d8abc7ef..f5b6e958789 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -68,6 +68,9 @@ 64dp 20dp 4dp + 48dp + 8dp + 16dp 7dp 17dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 2f338788153..7a7f54e8a90 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7771,7 +7771,7 @@ summary on the channel page--> - Low + Minimize Medium @@ -7780,7 +7780,16 @@ High - Urgent + Pop on screen + + + Block + + + Show silently + + + Alert Allow interruptions diff --git a/res/xml/channel_notification_settings.xml b/res/xml/channel_notification_settings.xml index 94a2cdb3d2c..6015f7f6fe0 100644 --- a/res/xml/channel_notification_settings.xml +++ b/res/xml/channel_notification_settings.xml @@ -25,11 +25,6 @@ android:order="1" android:layout="@layout/settings_entity_header" /> - - - + android:order="4" + android:title="@string/notification_importance_title" + settings:allowDividerBelow="true"/> + + + + + android:order="110" + settings:allowDividerAbove="false"/> diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java index 9f5ece24a56..5fd26a64c95 100644 --- a/src/com/android/settings/notification/AppNotificationSettings.java +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -152,6 +152,10 @@ public class AppNotificationSettings extends NotificationSettingsBase { context, mImportanceListener, mBackend)); mControllers.add(new ImportancePreferenceController( context, mImportanceListener, mBackend)); + mControllers.add(new MinImportancePreferenceController( + context, mImportanceListener, mBackend)); + mControllers.add(new HighImportancePreferenceController( + context, mImportanceListener, mBackend)); mControllers.add(new SoundPreferenceController(context, this, mImportanceListener, mBackend)); mControllers.add(new LightsPreferenceController(context, mBackend)); diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java index f92e529aab1..850fde2f67d 100644 --- a/src/com/android/settings/notification/ChannelNotificationSettings.java +++ b/src/com/android/settings/notification/ChannelNotificationSettings.java @@ -94,9 +94,12 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { protected List createPreferenceControllers(Context context) { mControllers = new ArrayList<>(); mControllers.add(new HeaderPreferenceController(context, this)); - mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend)); mControllers.add(new ImportancePreferenceController( context, mImportanceListener, mBackend)); + mControllers.add(new MinImportancePreferenceController( + context, mImportanceListener, mBackend)); + mControllers.add(new HighImportancePreferenceController( + context, mImportanceListener, mBackend)); mControllers.add(new AllowSoundPreferenceController( context, mImportanceListener, mBackend)); mControllers.add(new SoundPreferenceController(context, this, diff --git a/src/com/android/settings/notification/HighImportancePreferenceController.java b/src/com/android/settings/notification/HighImportancePreferenceController.java new file mode 100644 index 00000000000..fe843fd5078 --- /dev/null +++ b/src/com/android/settings/notification/HighImportancePreferenceController.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2019 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 android.app.NotificationChannel; +import android.content.Context; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.RestrictedSwitchPreference; + +import androidx.preference.Preference; + +public class HighImportancePreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final String KEY_IMPORTANCE = "high_importance"; + private NotificationSettingsBase.ImportanceListener mImportanceListener; + + public HighImportancePreferenceController(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; + } + if (mChannel == null) { + return false; + } + if (isDefaultChannel()) { + return false; + } + return mChannel.getImportance() >= IMPORTANCE_DEFAULT; + } + + @Override + public void updateState(Preference preference) { + if (mAppRow!= null && mChannel != null) { + preference.setEnabled(mAdmin == null && isChannelConfigurable()); + + RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference; + pref.setChecked(mChannel.getImportance() >= IMPORTANCE_HIGH); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mChannel != null) { + final boolean checked = (boolean) newValue; + + mChannel.setImportance(checked ? IMPORTANCE_HIGH : IMPORTANCE_DEFAULT); + mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); + saveChannel(); + mImportanceListener.onImportanceChanged(); + } + return true; + } +} diff --git a/src/com/android/settings/notification/ImportancePreference.java b/src/com/android/settings/notification/ImportancePreference.java new file mode 100644 index 00000000000..b8f3e4587c3 --- /dev/null +++ b/src/com/android/settings/notification/ImportancePreference.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2019 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 android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageButton; + +import com.android.settingslib.R; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +public class ImportancePreference extends Preference { + + boolean mIsBlockable = true; + boolean mIsConfigurable = true; + int mImportance; + ImageButton blockButton; + ImageButton silenceButton; + ImageButton alertButton; + ArrayMap mImageButtons = new ArrayMap<>(); + Context mContext; + + public ImportancePreference(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context); + } + + public ImportancePreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + public ImportancePreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public ImportancePreference(Context context) { + super(context); + init(context); + } + + private void init(Context context) { + mContext = context; + setLayoutResource(R.layout.notif_importance_preference); + } + + public void setImportance(int importance) { + mImportance = importance; + } + + public void setBlockable(boolean blockable) { + mIsBlockable = blockable; + } + + public void setConfigurable(boolean configurable) { + mIsConfigurable = configurable; + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + View blockView = holder.itemView.findViewById(R.id.block); + View alertView = holder.itemView.findViewById(R.id.alert); + View silenceView = holder.itemView.findViewById(R.id.silence); + if (!mIsBlockable) { + blockView.setVisibility(View.GONE); + if (mImportance == IMPORTANCE_NONE) { + mImportance = IMPORTANCE_LOW; + callChangeListener(IMPORTANCE_LOW); + } + + } + blockButton = blockView.findViewById(R.id.block_icon); + silenceButton = silenceView.findViewById(R.id.silence_icon); + alertButton = alertView.findViewById(R.id.alert_icon); + mImageButtons.put(blockButton, mContext.getColor(R.color.notification_block_color)); + mImageButtons.put(silenceButton, mContext.getColor(R.color.notification_silence_color)); + mImageButtons.put(alertButton, mContext.getColor(R.color.notification_alert_color)); + + switch (mImportance) { + case IMPORTANCE_NONE: + colorizeImageButton(blockButton.getId()); + if (!mIsConfigurable) { + alertView.setVisibility(View.GONE); + silenceView.setVisibility(View.GONE); + } + break; + case IMPORTANCE_MIN: + case IMPORTANCE_LOW: + colorizeImageButton(silenceButton.getId()); + if (!mIsConfigurable) { + alertView.setVisibility(View.GONE); + blockView.setVisibility(View.GONE); + } + break; + case IMPORTANCE_HIGH: + default: + colorizeImageButton(alertButton.getId()); + if (!mIsConfigurable) { + blockView.setVisibility(View.GONE); + silenceView.setVisibility(View.GONE); + } + break; + } + + blockButton.setOnClickListener(v -> { + callChangeListener(IMPORTANCE_NONE); + colorizeImageButton(blockButton.getId()); + }); + silenceButton.setOnClickListener(v -> { + callChangeListener(IMPORTANCE_LOW); + colorizeImageButton(silenceButton.getId()); + }); + alertButton.setOnClickListener(v -> { + callChangeListener(IMPORTANCE_DEFAULT); + colorizeImageButton(alertButton.getId()); + }); + } + + private void colorizeImageButton(int buttonId) { + if (mImageButtons != null) { + for (int i = 0; i < mImageButtons.size(); i++) { + final ImageButton imageButton = mImageButtons.keyAt(i); + final int color = mImageButtons.valueAt(i); + if (imageButton != null) { + LayerDrawable drawable = (LayerDrawable) imageButton.getDrawable(); + Drawable foreground = drawable.findDrawableByLayerId(R.id.fore); + GradientDrawable background = + (GradientDrawable) drawable.findDrawableByLayerId(R.id.back); + if (buttonId == imageButton.getId()) { + foreground.setTint(Color.WHITE); + background.setColor(color); + } else { + foreground.setTint(color); + background.setColor(Color.TRANSPARENT); + } + } + } + } + } +} diff --git a/src/com/android/settings/notification/ImportancePreferenceController.java b/src/com/android/settings/notification/ImportancePreferenceController.java index 4c20a46409d..09555718b6f 100644 --- a/src/com/android/settings/notification/ImportancePreferenceController.java +++ b/src/com/android/settings/notification/ImportancePreferenceController.java @@ -18,21 +18,15 @@ package com.android.settings.notification; import static android.app.NotificationChannel.USER_LOCKED_SOUND; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_MIN; -import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import android.app.NotificationChannel; -import android.app.NotificationManager; import android.content.Context; import android.media.RingtoneManager; -import androidx.preference.Preference; - -import com.android.settings.R; -import com.android.settings.RestrictedListPreference; import com.android.settings.core.PreferenceControllerMixin; +import androidx.preference.Preference; + public class ImportancePreferenceController extends NotificationPreferenceController implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { @@ -53,44 +47,33 @@ public class ImportancePreferenceController extends NotificationPreferenceContro @Override public boolean isAvailable() { - if (!super.isAvailable()) { + if (mAppRow == null) { return false; } if (mChannel == null) { return false; } - return !isDefaultChannel(); + if (isDefaultChannel()) { + return false; + } + return true; } @Override public void updateState(Preference preference) { if (mAppRow!= null && mChannel != null) { preference.setEnabled(mAdmin == null && isChannelConfigurable()); - preference.setSummary(getImportanceSummary(mChannel)); - - int importances = IMPORTANCE_HIGH - IMPORTANCE_MIN + 1; - CharSequence[] entries = new CharSequence[importances]; - CharSequence[] values = new CharSequence[importances]; - - int index = 0; - for (int i = IMPORTANCE_HIGH; i >= IMPORTANCE_MIN; i--) { - NotificationChannel channel = new NotificationChannel("", "", i); - entries[index] = getImportanceSummary(channel); - values[index] = String.valueOf(i); - index++; - } - - RestrictedListPreference pref = (RestrictedListPreference) preference; - pref.setEntries(entries); - pref.setEntryValues(values); - pref.setValue(String.valueOf(mChannel.getImportance())); + ImportancePreference pref = (ImportancePreference) preference; + pref.setBlockable(isChannelBlockable()); + pref.setConfigurable(isChannelConfigurable()); + pref.setImportance(mChannel.getImportance()); } } @Override public boolean onPreferenceChange(Preference preference, Object newValue) { if (mChannel != null) { - final int importance = Integer.parseInt((String) newValue); + final int importance = (Integer) newValue; // If you are moving from an importance level without sound to one with sound, // but the sound you had selected was "Silence", @@ -111,39 +94,4 @@ public class ImportancePreferenceController extends NotificationPreferenceContro } return true; } - - protected String getImportanceSummary(NotificationChannel channel) { - String summary = ""; - int importance = channel.getImportance(); - switch (importance) { - case IMPORTANCE_UNSPECIFIED: - summary = mContext.getString(R.string.notification_importance_unspecified); - break; - case NotificationManager.IMPORTANCE_MIN: - summary = mContext.getString(R.string.notification_importance_min); - break; - case NotificationManager.IMPORTANCE_LOW: - summary = mContext.getString(R.string.notification_importance_low); - break; - case NotificationManager.IMPORTANCE_DEFAULT: - if (SoundPreferenceController.hasValidSound(channel)) { - summary = mContext.getString(R.string.notification_importance_default); - } else { - summary = mContext.getString(R.string.notification_importance_low); - } - break; - case NotificationManager.IMPORTANCE_HIGH: - case NotificationManager.IMPORTANCE_MAX: - if (SoundPreferenceController.hasValidSound(channel)) { - summary = mContext.getString(R.string.notification_importance_high); - } else { - summary = mContext.getString(R.string.notification_importance_high_silent); - } - break; - default: - return ""; - } - - return summary; - } } diff --git a/src/com/android/settings/notification/MinImportancePreferenceController.java b/src/com/android/settings/notification/MinImportancePreferenceController.java new file mode 100644 index 00000000000..771ac609fc5 --- /dev/null +++ b/src/com/android/settings/notification/MinImportancePreferenceController.java @@ -0,0 +1,84 @@ +/* + * 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_MIN; + +import android.app.NotificationChannel; +import android.content.Context; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.RestrictedSwitchPreference; + +import androidx.preference.Preference; + +public class MinImportancePreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final String KEY_IMPORTANCE = "min_importance"; + private NotificationSettingsBase.ImportanceListener mImportanceListener; + + public MinImportancePreferenceController(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; + } + if (mChannel == null) { + return false; + } + if (isDefaultChannel()) { + return false; + } + return mChannel.getImportance() <= IMPORTANCE_LOW; + } + + @Override + public void updateState(Preference preference) { + if (mAppRow!= null && mChannel != null) { + preference.setEnabled(mAdmin == null && isChannelConfigurable()); + + RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference; + pref.setChecked(mChannel.getImportance() == IMPORTANCE_MIN); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mChannel != null) { + final boolean checked = (boolean) newValue; + + mChannel.setImportance(checked ? IMPORTANCE_MIN : IMPORTANCE_LOW); + mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); + saveChannel(); + mImportanceListener.onImportanceChanged(); + } + return true; + } +} diff --git a/tests/robotests/src/com/android/settings/notification/BlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BlockPreferenceControllerTest.java index 4f6944a3ce4..bdbf40a35ac 100644 --- a/tests/robotests/src/com/android/settings/notification/BlockPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/BlockPreferenceControllerTest.java @@ -110,6 +110,26 @@ public class BlockPreferenceControllerTest { assertFalse(mController.isAvailable()); } + @Test + public void testIsAvailable_notIfChannelNonDefault() { + 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_ifChannelDefault() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + when(channel.getId()).thenReturn(DEFAULT_CHANNEL_ID); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + @Test public void testIsAvailable_notIfGroupNotBlockable() { NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); diff --git a/tests/robotests/src/com/android/settings/notification/HighImportancePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/HighImportancePreferenceControllerTest.java new file mode 100644 index 00000000000..6e6dad4c839 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/HighImportancePreferenceControllerTest.java @@ -0,0 +1,199 @@ +/* + * 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_NONE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +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.UserManager; + +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.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowApplication; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +@RunWith(RobolectricTestRunner.class) +public class HighImportancePreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationManager mNm; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationSettingsBase.ImportanceListener mImportanceListener; + @Mock + private UserManager mUm; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + + private HighImportancePreferenceController 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 = RuntimeEnvironment.application; + mController = spy(new HighImportancePreferenceController( + mContext, mImportanceListener, mBackend)); + } + + @Test + public void testNoCrashIfNoOnResume() { + mController.isAvailable(); + mController.updateState(mock(Preference.class)); + } + + @Test + public void testIsAvailable_notIfNull() { + mController.onResume(null, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_ifAppBlocked() { + 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() { + 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() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + when(channel.getId()).thenReturn(DEFAULT_CHANNEL_ID); + mController.onResume(appRow, channel, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState_disabledByAdmin() { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( + RestrictedLockUtils.EnforcedAdmin.class)); + + Preference pref = new RestrictedSwitchPreference(mContext, null); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_notConfigurable() { + String lockedId = "locked"; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = lockedId; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(lockedId); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(mContext, null); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_high() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext); + mController.updateState(pref); + + assertTrue(pref.isChecked()); + } + + @Test + public void testUpdateState_default() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_DEFAULT); + mController.onResume(appRow, channel, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext); + mController.updateState(pref); + + assertFalse(pref.isChecked()); + } + + @Test + public void onPreferenceChange() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext, null); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, false); + + assertEquals(IMPORTANCE_DEFAULT, channel.getImportance()); + verify(mImportanceListener, times(1)).onImportanceChanged(); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ImportancePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ImportancePreferenceControllerTest.java index 99d3376e82e..c180acede19 100644 --- a/tests/robotests/src/com/android/settings/notification/ImportancePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/ImportancePreferenceControllerTest.java @@ -27,8 +27,11 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; 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; @@ -36,12 +39,10 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.os.UserManager; -import android.text.TextUtils; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import com.android.settings.RestrictedListPreference; import com.android.settingslib.RestrictedLockUtils; import org.junit.Before; @@ -95,20 +96,20 @@ public class ImportancePreferenceControllerTest { } @Test - public void testIsAvailable_notIfAppBlocked() { + public void testIsAvailable_ifAppBlocked() { NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); appRow.banned = true; mController.onResume(appRow, mock(NotificationChannel.class), null, null); - assertFalse(mController.isAvailable()); + assertTrue(mController.isAvailable()); } @Test - public void testIsAvailable_notIfChannelBlocked() { + public void testIsAvailable_evenIfChannelBlocked() { 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()); + assertTrue(mController.isAvailable()); } @Test @@ -137,11 +138,10 @@ public class ImportancePreferenceControllerTest { mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( RestrictedLockUtils.EnforcedAdmin.class)); - Preference pref = new RestrictedListPreference(mContext, null); + Preference pref = new ImportancePreference(mContext, null); mController.updateState(pref); assertFalse(pref.isEnabled()); - assertFalse(TextUtils.isEmpty(pref.getSummary())); } @Test @@ -154,11 +154,10 @@ public class ImportancePreferenceControllerTest { when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); mController.onResume(appRow, channel, null, null); - Preference pref = new RestrictedListPreference(mContext, null); + Preference pref = new ImportancePreference(mContext, null); mController.updateState(pref); assertFalse(pref.isEnabled()); - assertFalse(TextUtils.isEmpty(pref.getSummary())); } @Test @@ -167,11 +166,12 @@ public class ImportancePreferenceControllerTest { NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); mController.onResume(appRow, channel, null, null); - Preference pref = new RestrictedListPreference(mContext, null); + ImportancePreference pref = mock(ImportancePreference.class); mController.updateState(pref); - assertTrue(pref.isEnabled()); - assertFalse(TextUtils.isEmpty(pref.getSummary())); + verify(pref, times(1)).setConfigurable(anyBoolean()); + verify(pref, times(1)).setBlockable(anyBoolean()); + verify(pref, times(1)).setImportance(IMPORTANCE_HIGH); } @Test @@ -181,13 +181,12 @@ public class ImportancePreferenceControllerTest { channel.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); mController.onResume(new NotificationBackend.AppRow(), channel, null, null); - RestrictedListPreference pref = new RestrictedListPreference(mContext, null); + ImportancePreference pref = new ImportancePreference(mContext, null); when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); mController.displayPreference(mScreen); mController.updateState(pref); - pref.setValue(String.valueOf(IMPORTANCE_HIGH)); - mController.onPreferenceChange(pref, pref.getValue()); + mController.onPreferenceChange(pref, IMPORTANCE_HIGH); assertEquals(IMPORTANCE_HIGH, channel.getImportance()); assertNotNull(channel.getSound()); @@ -200,13 +199,12 @@ public class ImportancePreferenceControllerTest { channel.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); mController.onResume(new NotificationBackend.AppRow(), channel, null, null); - RestrictedListPreference pref = new RestrictedListPreference(mContext, null); + ImportancePreference pref = new ImportancePreference(mContext, null); when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); mController.displayPreference(mScreen); mController.updateState(pref); - pref.setValue(String.valueOf(IMPORTANCE_LOW)); - mController.onPreferenceChange(pref, pref.getValue()); + mController.onPreferenceChange(pref, IMPORTANCE_LOW); assertEquals(IMPORTANCE_LOW, channel.getImportance()); assertNull(channel.getSound()); diff --git a/tests/robotests/src/com/android/settings/notification/ImportancePreferenceTest.java b/tests/robotests/src/com/android/settings/notification/ImportancePreferenceTest.java new file mode 100644 index 00000000000..eebfbd1bda1 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ImportancePreferenceTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2019 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 com.google.common.truth.Truth.assertThat; + +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 android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Switch; + +import com.android.settings.R; +import com.android.settingslib.RestrictedLockUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +@RunWith(RobolectricTestRunner.class) +public class ImportancePreferenceTest { + + private Context mContext; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + } + + private GradientDrawable getBackground(ImageButton button) { + return (GradientDrawable) ((LayerDrawable) button.getDrawable()) + .findDrawableByLayerId(R.id.back); + } + + @Test + public void createNewPreference_shouldSetLayout() { + final ImportancePreference preference = new ImportancePreference(mContext); + assertThat(preference.getLayoutResource()).isEqualTo( + R.layout.notif_importance_preference); + } + + @Test + public void onBindViewHolder_hideBlockNonBlockable() { + final ImportancePreference preference = new ImportancePreference(mContext); + final LayoutInflater inflater = LayoutInflater.from(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + inflater.inflate(R.layout.notif_importance_preference, null)); + + preference.setBlockable(false); + preference.setConfigurable(true); + preference.setImportance(IMPORTANCE_DEFAULT); + preference.onBindViewHolder(holder); + + assertThat(holder.itemView.findViewById(R.id.block).getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void onBindViewHolder_hideNonSelectedNonConfigurable() { + final ImportancePreference preference = new ImportancePreference(mContext); + final LayoutInflater inflater = LayoutInflater.from(mContext); + PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + inflater.inflate(R.layout.notif_importance_preference, null)); + + preference.setBlockable(true); + preference.setConfigurable(false); + preference.setImportance(IMPORTANCE_DEFAULT); + preference.onBindViewHolder(holder); + + assertThat(holder.itemView.findViewById(R.id.block).getVisibility()).isEqualTo(View.GONE); + assertThat(holder.itemView.findViewById(R.id.silence).getVisibility()).isEqualTo(View.GONE); + assertThat(holder.itemView.findViewById(R.id.alert).getVisibility()) + .isEqualTo(View.VISIBLE); + + // other button + preference.setImportance(IMPORTANCE_LOW); + holder = PreferenceViewHolder.createInstanceForTests( + inflater.inflate(R.layout.notif_importance_preference, null)); + preference.onBindViewHolder(holder); + + assertThat(holder.itemView.findViewById(R.id.block).getVisibility()).isEqualTo(View.GONE); + assertThat(holder.itemView.findViewById(R.id.silence).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(holder.itemView.findViewById(R.id.alert).getVisibility()) + .isEqualTo(View.GONE); + } + + @Test + public void onBindViewHolder_selectButton() { + final ImportancePreference preference = new ImportancePreference(mContext); + final LayoutInflater inflater = LayoutInflater.from(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + inflater.inflate(R.layout.notif_importance_preference, null)); + + preference.setBlockable(true); + preference.setConfigurable(true); + preference.setImportance(IMPORTANCE_DEFAULT); + + ImageButton blockButton = (ImageButton) holder.findViewById(R.id.block_icon); + ImageButton silenceButton = (ImageButton) holder.findViewById(R.id.silence_icon); + ImageButton alertButton = (ImageButton) holder.findViewById(R.id.alert_icon); + + preference.onBindViewHolder(holder); + + // selected has full color background. others are transparent + assertThat(getBackground(alertButton).getColor().getColors()[0]).isNotEqualTo( + Color.TRANSPARENT); + assertThat(getBackground(silenceButton).getColor().getColors()[0]).isEqualTo( + Color.TRANSPARENT); + assertThat(getBackground(blockButton).getColor().getColors()[0]).isEqualTo( + Color.TRANSPARENT); + } + + @Test + public void onClick_changesUICallsListener() { + final ImportancePreference preference = spy(new ImportancePreference(mContext)); + final LayoutInflater inflater = LayoutInflater.from(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + inflater.inflate(R.layout.notif_importance_preference, null)); + + preference.setBlockable(true); + preference.setConfigurable(true); + preference.setImportance(IMPORTANCE_DEFAULT); + preference.onBindViewHolder(holder); + + ImageButton blockButton = (ImageButton) holder.findViewById(R.id.block_icon); + ImageButton silenceButton = (ImageButton) holder.findViewById(R.id.silence_icon); + ImageButton alertButton = (ImageButton) holder.findViewById(R.id.alert_icon); + + silenceButton.callOnClick(); + + // selected has full color background. others are transparent + assertThat(getBackground(silenceButton).getColor().getColors()[0]).isNotEqualTo( + Color.TRANSPARENT); + assertThat(getBackground(alertButton).getColor().getColors()[0]).isEqualTo( + Color.TRANSPARENT); + assertThat(getBackground(blockButton).getColor().getColors()[0]).isEqualTo( + Color.TRANSPARENT); + + verify(preference, times(1)).callChangeListener(IMPORTANCE_LOW); + } + + @Test + public void onBindViewHolder_allButtonsVisible() { + final ImportancePreference preference = new ImportancePreference(mContext); + final LayoutInflater inflater = LayoutInflater.from(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + inflater.inflate(R.layout.notif_importance_preference, null)); + + preference.setBlockable(true); + preference.setConfigurable(true); + preference.onBindViewHolder(holder); + + assertThat(holder.itemView.findViewById(R.id.block).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(holder.itemView.findViewById(R.id.silence).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(holder.itemView.findViewById(R.id.alert).getVisibility()) + .isEqualTo(View.VISIBLE); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/MinImportancePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/MinImportancePreferenceControllerTest.java new file mode 100644 index 00000000000..28058a409ef --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/MinImportancePreferenceControllerTest.java @@ -0,0 +1,199 @@ +/* + * 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_MIN; +import static android.app.NotificationManager.IMPORTANCE_NONE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +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.UserManager; + +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.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowApplication; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +@RunWith(RobolectricTestRunner.class) +public class MinImportancePreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationManager mNm; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationSettingsBase.ImportanceListener mImportanceListener; + @Mock + private UserManager mUm; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + + private MinImportancePreferenceController 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 = RuntimeEnvironment.application; + mController = spy(new MinImportancePreferenceController( + mContext, mImportanceListener, mBackend)); + } + + @Test + public void testNoCrashIfNoOnResume() { + mController.isAvailable(); + mController.updateState(mock(Preference.class)); + } + + @Test + public void testIsAvailable_notIfNull() { + mController.onResume(null, null, null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_ifAppBlocked() { + 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() { + 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() { + 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() { + 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() { + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( + RestrictedLockUtils.EnforcedAdmin.class)); + + Preference pref = new RestrictedSwitchPreference(mContext, null); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_notConfigurable() { + String lockedId = "locked"; + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.lockedChannelId = lockedId; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(lockedId); + when(channel.getImportance()).thenReturn(IMPORTANCE_LOW); + mController.onResume(appRow, channel, null, null); + + Preference pref = new RestrictedSwitchPreference(mContext, null); + mController.updateState(pref); + + assertFalse(pref.isEnabled()); + } + + @Test + public void testUpdateState_min() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_MIN); + mController.onResume(appRow, channel, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext); + mController.updateState(pref); + + assertTrue(pref.isChecked()); + } + + @Test + public void testUpdateState_low() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_LOW); + mController.onResume(appRow, channel, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext); + mController.updateState(pref); + + assertFalse(pref.isChecked()); + } + + @Test + public void onPreferenceChange() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_LOW); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext, null); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + mController.onPreferenceChange(pref, true); + + assertEquals(IMPORTANCE_MIN, channel.getImportance()); + verify(mImportanceListener, times(1)).onImportanceChanged(); + } +}