Merge "Settings for Polite Notifications" into main

This commit is contained in:
Iavor-Valentin Iftime
2023-10-11 12:04:37 +00:00
committed by Android (Google) Code Review
15 changed files with 925 additions and 3 deletions

View File

@@ -117,11 +117,10 @@ android_library {
"factory_reset_flags_lib",
"android.content.pm.flags-aconfig-java",
"FingerprintManagerInteractor",
"notification_flags_lib",
],
plugins: [
"androidx.room_room-compiler-plugin",
],
plugins: ["androidx.room_room-compiler-plugin"],
errorprone: {
extra_check_modules: ["//external/nullaway:nullaway_plugin"],

View File

@@ -1474,4 +1474,19 @@
<string-array name="terms_of_address_supported_languages">
<item>fr</item> <!-- French -->
</string-array>
<!-- Values for notification cooldown -->
<string-array name="notification_polite_options">
<item>@string/notification_polite_all_apps</item>
<item>@string/notification_polite_conversations</item>
<item>@string/notification_polite_disabled</item>
</string-array>
<!-- Values for notification cooldown -->
<string-array name="notification_polite_options_values" translatable="false">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
</resources>

View File

@@ -8210,6 +8210,20 @@
<string name="notif_listener_more_settings">More settings</string>
<string name="notif_listener_more_settings_desc">More settings are available inside this app</string>
<!-- Title for Polite Notifications setting [CHAR LIMIT=30]-->
<string name="notification_polite_title">Notification cooldown</string>
<string name="notification_polite_all_apps">Apply cooldown to all notifications</string>
<string name="notification_polite_all_apps_summary">Gradually lower the notification volume when you get many successive notifications from the same app</string>
<string name="notification_polite_conversations">Apply cooldown to conversations</string>
<string name="notification_polite_conversations_summary">Gradually lower the notification volume when you get many messages from the same chat within a short period of time</string>
<string name="notification_polite_disabled">Don\'t use notification cooldown</string>
<string name="notification_polite_disabled_summary">Never lower notification volume, regardless of the amount of successive notifications from the same app</string>
<string name="notification_polite_vibrate_unlocked">Vibrate when unlocked</string>
<string name="notification_polite_vibrate_unlocked_summary">Only vibrate when screen is unlocked</string>
<string name="notification_polite_work">Apply to work profiles</string>
<string name="notification_polite_work_summary">Apply the notification cooldown settings from your personal profile to your work profile</string>
<!-- Title for managing VR (virtual reality) helper services. [CHAR LIMIT=50] -->
<string name="vr_listeners_title">VR helper services</string>

View File

@@ -180,5 +180,13 @@
android:title="@string/notification_assistant_title"
android:summary="@string/notification_assistant_summary"
settings:controller="com.android.settings.notification.NotificationAssistantPreferenceController"/>
<Preference
android:fragment="com.android.settings.notification.PoliteNotificationsPreferenceFragment"
android:key="polite_notifications_preference"
android:order="26"
android:persistent="false"
android:title="@string/notification_polite_title"
settings:controller="com.android.settings.notification.PoliteNotificationsPreferenceController" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/notification_polite_title">
<com.android.settings.RestrictedListPreference
android:key="polite_notifications_pref_dlg"
android:persistent="false"
android:title="@string/notification_polite_title"
android:entries="@array/notification_polite_options"
android:entryValues="@array/notification_polite_options_values"
settings:controller="com.android.settings.notification.PoliteNotificationFilterController"/>
<SwitchPreference
android:key="notification_polite_vibrate_unlocked"
android:persistent="false"
android:title="@string/notification_polite_vibrate_unlocked"
android:summary="@string/notification_polite_vibrate_unlocked_summary"
settings:controller="com.android.settings.notification.PoliteNotifVibrateUnlockedToggleController" />
<SwitchPreference
android:key="notification_polite_work_profile"
android:persistent="false"
android:title="@string/notification_polite_work"
android:summary="@string/notification_polite_work_summary"
settings:controller="com.android.settings.notification.PoliteNotifWorkProfileToggleController" />
</PreferenceScreen>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -62,6 +62,7 @@ android_robolectric_test {
"aconfig_settings_flags_lib",
"platform-test-annotations",
"Settings-testutils2",
"notification_flags_lib",
],
libs: [

View File

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

View File

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

View File

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

View File

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