From 9f82e7381a2f5bec4e3973eba6237c504018635f Mon Sep 17 00:00:00 2001 From: Valentin Iftime Date: Fri, 30 Jun 2023 15:04:56 +0200 Subject: [PATCH] Settings for Polite Notifications Adds settings page for polite notifications. Test: atest PoliteNotificationsPreferenceControllerTest Test: atest PoliteNotificationFilterControllerTest Test: atest PoliteNotifVibrateUnlockedToggleControllerTest Test: atest PoliteNotifWorkProfileToggleControllerTest Bug: 270456865 Change-Id: I3ec497b068c15205df97bb26ee007beed8da9ee1 --- Android.bp | 5 +- res/values/arrays.xml | 15 ++ res/values/strings.xml | 14 ++ res/xml/configure_notification_settings.xml | 8 + res/xml/polite_notifications_settings.xml | 44 +++++ ...eNotifVibrateUnlockedToggleController.java | 66 +++++++ ...oliteNotifWorkProfileToggleController.java | 79 +++++++++ .../PoliteNotificationFilterController.java | 114 ++++++++++++ ...liteNotificationsPreferenceController.java | 39 +++++ ...PoliteNotificationsPreferenceFragment.java | 50 ++++++ tests/robotests/Android.bp | 1 + ...ifVibrateUnlockedToggleControllerTest.java | 123 +++++++++++++ ...eNotifWorkProfileToggleControllerTest.java | 136 +++++++++++++++ ...oliteNotificationFilterControllerTest.java | 165 ++++++++++++++++++ ...NotificationsPreferenceControllerTest.java | 69 ++++++++ 15 files changed, 925 insertions(+), 3 deletions(-) create mode 100644 res/xml/polite_notifications_settings.xml create mode 100644 src/com/android/settings/notification/PoliteNotifVibrateUnlockedToggleController.java create mode 100644 src/com/android/settings/notification/PoliteNotifWorkProfileToggleController.java create mode 100644 src/com/android/settings/notification/PoliteNotificationFilterController.java create mode 100644 src/com/android/settings/notification/PoliteNotificationsPreferenceController.java create mode 100644 src/com/android/settings/notification/PoliteNotificationsPreferenceFragment.java create mode 100644 tests/robotests/src/com/android/settings/notification/PoliteNotifVibrateUnlockedToggleControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/PoliteNotifWorkProfileToggleControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/PoliteNotificationFilterControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/PoliteNotificationsPreferenceControllerTest.java diff --git a/Android.bp b/Android.bp index a62aa1101c0..6d94bb471a1 100644 --- a/Android.bp +++ b/Android.bp @@ -117,11 +117,10 @@ android_library { "aconfig_settings_flags_lib", "android.content.pm.flags-aconfig-java", "FingerprintManagerInteractor", + "notification_flags_lib", ], - plugins: [ - "androidx.room_room-compiler-plugin", - ], + plugins: ["androidx.room_room-compiler-plugin"], libs: [ "telephony-common", diff --git a/res/values/arrays.xml b/res/values/arrays.xml index f45495433fc..803b4030c68 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1475,4 +1475,19 @@ en-XA ar-XB + + + + @string/notification_polite_all_apps + @string/notification_polite_conversations + @string/notification_polite_disabled + + + + + 0 + 1 + 2 + + diff --git a/res/values/strings.xml b/res/values/strings.xml index dc06f3a9975..c9cab85576f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8198,6 +8198,20 @@ More settings More settings are available inside this app + + Notification cooldown + Apply cooldown to all notifications + Gradually lower the notification volume when you get many successive notifications from the same app + Apply cooldown to conversations + Gradually lower the notification volume when you get many messages from the same chat within a short period of time + Don\'t use notification cooldown + Never lower notification volume, regardless of the amount of successive notifications from the same app + Vibrate when unlocked + Only vibrate when screen is unlocked + Apply to work profiles + Apply the notification cooldown settings from your personal profile to your work profile + + VR helper services diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml index 32081cf18de..ee88215985f 100644 --- a/res/xml/configure_notification_settings.xml +++ b/res/xml/configure_notification_settings.xml @@ -180,5 +180,13 @@ android:title="@string/notification_assistant_title" android:summary="@string/notification_assistant_summary" settings:controller="com.android.settings.notification.NotificationAssistantPreferenceController"/> + + diff --git a/res/xml/polite_notifications_settings.xml b/res/xml/polite_notifications_settings.xml new file mode 100644 index 00000000000..5e09c3b3051 --- /dev/null +++ b/res/xml/polite_notifications_settings.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + diff --git a/src/com/android/settings/notification/PoliteNotifVibrateUnlockedToggleController.java b/src/com/android/settings/notification/PoliteNotifVibrateUnlockedToggleController.java new file mode 100644 index 00000000000..aaa8f8714b0 --- /dev/null +++ b/src/com/android/settings/notification/PoliteNotifVibrateUnlockedToggleController.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 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.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; + +import android.content.Context; +import android.os.Vibrator; +import android.provider.Settings; + +import com.android.server.notification.Flags; +import com.android.settings.R; +import com.android.settings.core.TogglePreferenceController; + +/** + * Controls the toggle that determines whether notifications + * should only vibrate (no sound) when the device is unlocked. + */ +public class PoliteNotifVibrateUnlockedToggleController extends TogglePreferenceController { + + public PoliteNotifVibrateUnlockedToggleController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + // TODO: b/291897570 - remove this when the feature flag is removed! + if (!Flags.politeNotifications()) { + return CONDITIONALLY_UNAVAILABLE; + } + return mContext.getSystemService(Vibrator.class).hasVibrator() ? AVAILABLE + : UNSUPPORTED_ON_DEVICE; + } + + @Override + public boolean isChecked() { + return Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, OFF) != OFF; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, (isChecked ? ON : OFF)); + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_accessibility; + } +} diff --git a/src/com/android/settings/notification/PoliteNotifWorkProfileToggleController.java b/src/com/android/settings/notification/PoliteNotifWorkProfileToggleController.java new file mode 100644 index 00000000000..65b4fb89b4f --- /dev/null +++ b/src/com/android/settings/notification/PoliteNotifWorkProfileToggleController.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 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.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; + +import android.content.Context; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; + +import androidx.annotation.VisibleForTesting; + +import com.android.server.notification.Flags; +import com.android.settings.R; +import com.android.settings.core.TogglePreferenceController; + +/** + * Controls the toggle that determines whether notification cooldown + * should apply to work profiles. + */ +public class PoliteNotifWorkProfileToggleController extends TogglePreferenceController { + + private final int mManagedProfileId; + + public PoliteNotifWorkProfileToggleController(Context context, String preferenceKey) { + this(context, preferenceKey, new AudioHelper(context)); + } + + @VisibleForTesting + PoliteNotifWorkProfileToggleController(Context context, String preferenceKey, + AudioHelper helper) { + super(context, preferenceKey); + mManagedProfileId = helper.getManagedProfileId(UserManager.get(mContext)); + } + + @Override + public int getAvailabilityStatus() { + // TODO: b/291897570 - remove this when the feature flag is removed! + if (!Flags.politeNotifications()) { + return CONDITIONALLY_UNAVAILABLE; + } + + return (mManagedProfileId != UserHandle.USER_NULL) ? AVAILABLE : DISABLED_FOR_USER; + } + + @Override + public boolean isChecked() { + return Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, OFF, mManagedProfileId) != OFF; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, (isChecked ? ON : OFF), + mManagedProfileId); + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_accessibility; + } +} diff --git a/src/com/android/settings/notification/PoliteNotificationFilterController.java b/src/com/android/settings/notification/PoliteNotificationFilterController.java new file mode 100644 index 00000000000..8093f55acd5 --- /dev/null +++ b/src/com/android/settings/notification/PoliteNotificationFilterController.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023 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.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; + +import android.content.Context; +import android.provider.Settings; +import android.util.Log; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; + +import com.android.server.notification.Flags; +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.PreferenceControllerMixin; + + +/** + * Controls whether polite notifications are enabled and apply to all apps or just to conversations. + */ +public class PoliteNotificationFilterController extends BasePreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + static final String TAG = "PoliteNotificationFilterController"; + + private static final int POLITE_NOTIFICATIONS_ALL = 0; + private static final int POLITE_NOTIFICATIONS_CONVERSATIONS = 1; + private static final int POLITE_NOTIFICATIONS_DISABLED = 2; + + public PoliteNotificationFilterController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + // TODO: b/291897570 - remove this when the feature flag is removed! + return Flags.politeNotifications() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + final ListPreference pref = (ListPreference) preference; + + if (isPoliteNotifDisabled()) { + pref.setValue(Integer.toString(POLITE_NOTIFICATIONS_DISABLED)); + } else if (shouldApplyForAllApps()) { + pref.setValue(Integer.toString(POLITE_NOTIFICATIONS_ALL)); + } else { + pref.setValue(Integer.toString(POLITE_NOTIFICATIONS_CONVERSATIONS)); + } + } + + @Override + public CharSequence getSummary() { + if (isPoliteNotifDisabled()) { + return mContext.getString(R.string.notification_polite_disabled_summary); + } + if (shouldApplyForAllApps()) { + return mContext.getString(R.string.notification_polite_all_apps_summary); + } else { + return mContext.getString(R.string.notification_polite_conversations_summary); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final int prefValue = Integer.parseInt((String) newValue); + if (prefValue == POLITE_NOTIFICATIONS_ALL) { + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, ON); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, ON); + } else if (prefValue == POLITE_NOTIFICATIONS_CONVERSATIONS) { + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, ON); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, OFF); + } else if (prefValue == POLITE_NOTIFICATIONS_DISABLED) { + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, OFF); + } else { + Log.e(TAG, "Unexpected preference value: " + prefValue); + } + refreshSummary(preference); + return true; + } + + private boolean isPoliteNotifDisabled() { + return Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, ON) == OFF; + } + + private boolean shouldApplyForAllApps() { + return Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, ON) != OFF; + } +} diff --git a/src/com/android/settings/notification/PoliteNotificationsPreferenceController.java b/src/com/android/settings/notification/PoliteNotificationsPreferenceController.java new file mode 100644 index 00000000000..e6e0947149a --- /dev/null +++ b/src/com/android/settings/notification/PoliteNotificationsPreferenceController.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 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 com.android.server.notification.Flags; +import com.android.settings.core.BasePreferenceController; + +// TODO: b/291897570 - remove controller when the feature flag is removed! +/** + * Controller for polite notifications settings page. + */ +public class PoliteNotificationsPreferenceController extends BasePreferenceController { + + public PoliteNotificationsPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return Flags.politeNotifications() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + +} diff --git a/src/com/android/settings/notification/PoliteNotificationsPreferenceFragment.java b/src/com/android/settings/notification/PoliteNotificationsPreferenceFragment.java new file mode 100644 index 00000000000..449e678ccd3 --- /dev/null +++ b/src/com/android/settings/notification/PoliteNotificationsPreferenceFragment.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 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.settings.SettingsEnums; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +/** + * Fragment for polite notifications. + */ +@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) +public class PoliteNotificationsPreferenceFragment extends DashboardFragment { + + private static final String POLITE_NOTIF_PREFERENCE_KEY = "polite_notifications_pref_dlg"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.SETTINGS_POLITE_NOTIFICATIONS; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.polite_notifications_settings; + } + @Override + protected String getLogTag() { + return "PoliteNotificationsPreferenceFragment"; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.polite_notifications_settings); +} diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp index fbfd888ad51..6b56d93436f 100644 --- a/tests/robotests/Android.bp +++ b/tests/robotests/Android.bp @@ -62,6 +62,7 @@ android_robolectric_test { "aconfig_settings_flags_lib", "platform-test-annotations", "Settings-testutils2", + "notification_flags_lib", ], libs: [ diff --git a/tests/robotests/src/com/android/settings/notification/PoliteNotifVibrateUnlockedToggleControllerTest.java b/tests/robotests/src/com/android/settings/notification/PoliteNotifVibrateUnlockedToggleControllerTest.java new file mode 100644 index 00000000000..f92ed252116 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/PoliteNotifVibrateUnlockedToggleControllerTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2023 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.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Vibrator; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; + +import com.android.server.notification.Flags; +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class PoliteNotifVibrateUnlockedToggleControllerTest { + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final String PREFERENCE_KEY = "preference_key"; + + private PoliteNotifVibrateUnlockedToggleController mController; + private Context mContext; + @Mock + private Vibrator mVibrator; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mController = new PoliteNotifVibrateUnlockedToggleController(mContext, PREFERENCE_KEY); + when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator); + } + + @Test + public void isAvailable_flagEnabled_vibrationSupported_shouldReturnTrue() { + // TODO: b/291907312 - remove feature flags + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + when(mVibrator.hasVibrator()).thenReturn(true); + assertThat(mController.isAvailable()).isTrue(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void isAvailable_flagEnabled_vibrationNotSupported_shouldReturnFalse() { + // TODO: b/291907312 - remove feature flags + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + when(mVibrator.hasVibrator()).thenReturn(false); + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.UNSUPPORTED_ON_DEVICE); + } + + @Test + public void isAvailable_flagDisabled_shouldReturnFalse() { + // TODO: b/291907312 - remove feature flags + mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void isChecked_vibrateEnabled_shouldReturnTrue() { + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, ON); + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void isChecked_vibrateDisabled_shouldReturnFalse() { + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, OFF); + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void setChecked_setTrue_shouldEnableVibrateSetting() { + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, OFF); + mController.setChecked(true); + assertThat(Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, OFF)).isEqualTo(ON); + } + + @Test + public void setChecked_setFalse_shouldDisableVibrateSetting() { + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, ON); + mController.setChecked(false); + assertThat(Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, ON)).isEqualTo(OFF); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/PoliteNotifWorkProfileToggleControllerTest.java b/tests/robotests/src/com/android/settings/notification/PoliteNotifWorkProfileToggleControllerTest.java new file mode 100644 index 00000000000..5cda1eaab0a --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/PoliteNotifWorkProfileToggleControllerTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2023 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.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.UserHandle; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; + +import com.android.server.notification.Flags; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.testutils.shadow.ShadowSystemSettings; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +public class PoliteNotifWorkProfileToggleControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final String PREFERENCE_KEY = "preference_key"; + + private Context mContext; + PoliteNotifWorkProfileToggleController mController; + @Mock + private AudioHelper mAudioHelper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + when(mAudioHelper.getManagedProfileId(any())).thenReturn(UserHandle.MIN_SECONDARY_USER_ID); + mController = new PoliteNotifWorkProfileToggleController(mContext, PREFERENCE_KEY, + mAudioHelper); + } + + @Test + public void isAvailable_flagEnabled_workProfileExists_shouldReturnTrue() { + // TODO: b/291907312 - remove feature flags + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + assertThat(mController.isAvailable()).isTrue(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void isAvailable_flagEnabled_workProfileMissing_shouldReturnFalse() { + // TODO: b/291907312 - remove feature flags + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + when(mAudioHelper.getManagedProfileId(any())).thenReturn(UserHandle.USER_NULL); + mController = new PoliteNotifWorkProfileToggleController(mContext, PREFERENCE_KEY, + mAudioHelper); + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.DISABLED_FOR_USER); + } + + @Test + public void isAvailable_flagDisabled_shouldReturnFalse() { + // TODO: b/291907312 - remove feature flags + mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + @Config(shadows = ShadowSystemSettings.class) + public void isChecked_enabledForWorkProfile_shouldReturnTrue() { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, ON, + UserHandle.MIN_SECONDARY_USER_ID); + assertThat(mController.isChecked()).isTrue(); + } + + @Test + @Config(shadows = ShadowSystemSettings.class) + public void isChecked_disabledForWorkProfile_shouldReturnFalse() { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, OFF, + UserHandle.MIN_SECONDARY_USER_ID); + assertThat(mController.isChecked()).isFalse(); + } + + @Test + @Config(shadows = ShadowSystemSettings.class) + public void setChecked_setTrue_shouldEnablePoliteNotifForWorkProfile() { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, OFF, + UserHandle.MIN_SECONDARY_USER_ID); + mController.setChecked(true); + assertThat(Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, ON, + UserHandle.MIN_SECONDARY_USER_ID)).isEqualTo(ON); + } + + @Test + @Config(shadows = ShadowSystemSettings.class) + public void setChecked_setFalse_shouldDisablePoliteNotifForWorkProfile() { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, ON, + UserHandle.MIN_SECONDARY_USER_ID); + mController.setChecked(false); + assertThat(Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, ON, + UserHandle.MIN_SECONDARY_USER_ID)).isEqualTo(OFF); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/PoliteNotificationFilterControllerTest.java b/tests/robotests/src/com/android/settings/notification/PoliteNotificationFilterControllerTest.java new file mode 100644 index 00000000000..646e927c02f --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/PoliteNotificationFilterControllerTest.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2023 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.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; +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.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.server.notification.Flags; +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class PoliteNotificationFilterControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final String PREFERENCE_KEY = "preference_key"; + private static final int POLITE_NOTIFICATIONS_ALL = 0; + private static final int POLITE_NOTIFICATIONS_CONVERSATIONS = 1; + private static final int POLITE_NOTIFICATIONS_DISABLED = 2; + + @Mock + private PreferenceScreen mScreen; + + private PoliteNotificationFilterController mController; + private Preference mPreference; + private Context mContext; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + + mController = new PoliteNotificationFilterController(mContext, PREFERENCE_KEY); + mPreference = new Preference(mContext); + mPreference.setKey(mController.getPreferenceKey()); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + } + + @Test + public void isAvailable_flagEnabled_shouldReturnTrue() { + // TODO: b/291907312 - remove feature flags + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + assertThat(mController.isAvailable()).isTrue(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void isAvailable_flagDisabled_shouldReturnFalse() { + // TODO: b/291907312 - remove feature flags + mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void updateState_politeNotificationDisabled() { + final ListPreference preference = mock(ListPreference.class); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, OFF); + mController.updateState(preference); + + verify(preference).setValue(Integer.toString(POLITE_NOTIFICATIONS_DISABLED)); + assertThat(mController.getSummary().toString()).isEqualTo( + mContext.getString(R.string.notification_polite_disabled_summary)); + } + + @Test + public void updateState_politeNotificationEnabled_applyAllApps() { + final ListPreference preference = mock(ListPreference.class); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, ON); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, ON); + mController.updateState(preference); + + verify(preference).setValue(Integer.toString(POLITE_NOTIFICATIONS_ALL)); + assertThat(mController.getSummary().toString()).isEqualTo( + mContext.getString(R.string.notification_polite_all_apps_summary)); + } + + @Test + public void updateState_politeNotificationEnabled_applyOnlyConversations() { + final ListPreference preference = mock(ListPreference.class); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, ON); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, OFF); + mController.updateState(preference); + + verify(preference).setValue(Integer.toString(POLITE_NOTIFICATIONS_CONVERSATIONS)); + assertThat(mController.getSummary().toString()).isEqualTo( + mContext.getString(R.string.notification_polite_conversations_summary)); + } + + @Test + public void onPreferenceChanged_firstItemSelected_shouldEnableForAll() { + mController.displayPreference(mScreen); + mController.onPreferenceChange(mPreference, "0"); + + assertThat(Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, OFF)).isEqualTo(ON); + assertThat(Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, OFF)).isEqualTo(ON); + } + + @Test + public void onPreferenceChanged_secondItemSelected_shouldEnableForConversationsOnly() { + mController.displayPreference(mScreen); + mController.onPreferenceChange(mPreference, "1"); + + assertThat(Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, OFF)).isEqualTo(ON); + assertThat(Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, OFF)).isEqualTo(OFF); + } + + @Test + public void onPreferenceChanged_thirdItemSelected_shouldDisable() { + mController.displayPreference(mScreen); + mController.onPreferenceChange(mPreference, "2"); + + assertThat(Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, OFF)).isEqualTo(OFF); + } + +} diff --git a/tests/robotests/src/com/android/settings/notification/PoliteNotificationsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/PoliteNotificationsPreferenceControllerTest.java new file mode 100644 index 00000000000..f6c5e16818f --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/PoliteNotificationsPreferenceControllerTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 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.google.common.truth.Truth.assertThat; + +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.server.notification.Flags; +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class PoliteNotificationsPreferenceControllerTest { + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final String PREFERENCE_KEY = "preference_key"; + + private PoliteNotificationsPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mController = new PoliteNotificationsPreferenceController(RuntimeEnvironment.application, + PREFERENCE_KEY); + } + + @Test + public void isAvailable_flagEnabled_shouldReturnTrue() { + // TODO: b/291907312 - remove feature flags + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + assertThat(mController.isAvailable()).isTrue(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void isAvailable_flagDisabled_shouldReturnFalse() { + // TODO: b/291907312 - remove feature flags + mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + +}