From d5c0851c0bec3c932753358129bd45aab24355ec Mon Sep 17 00:00:00 2001 From: Yuri Lin Date: Mon, 23 Dec 2024 16:51:20 -0500 Subject: [PATCH] Add setting for promoted (live) notifications Add a switch to control whether live notifications are permitted for an app, at the top of the app's notification settings page. This controls canPostPromotedNotifications() for that app. Bug: 369150427 Flag: android.app.ui_rich_ongoing Test: manual, PromotedNotificationsPreferenceControllerTest Change-Id: I3a7c05721f1e879fb5fb0f5fb5f283dedbd195da --- res/values/strings.xml | 7 + res/xml/app_notification_settings.xml | 17 +++ .../notification/NotificationBackend.java | 36 ++++- .../app/AppNotificationSettings.java | 1 + ...otedNotificationsPreferenceController.java | 83 +++++++++++ ...NotificationsPreferenceControllerTest.java | 135 ++++++++++++++++++ 6 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 src/com/android/settings/notification/app/PromotedNotificationsPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/app/PromotedNotificationsPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index cbcf51dfbf9..80d869d71c9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8764,6 +8764,13 @@ Notification bundles + + Live notifications + + Show live info + + Pinned notifications display live info from apps, and always appear on the status bar and lock screen + Bubbles diff --git a/res/xml/app_notification_settings.xml b/res/xml/app_notification_settings.xml index 30a57a24bb7..06d74f608fa 100644 --- a/res/xml/app_notification_settings.xml +++ b/res/xml/app_notification_settings.xml @@ -30,6 +30,23 @@ + + + + + + + + + sentByChannel; public NotificationsSentState sentByApp; public boolean showAllChannels = true; + public boolean canBePromoted; } } diff --git a/src/com/android/settings/notification/app/AppNotificationSettings.java b/src/com/android/settings/notification/app/AppNotificationSettings.java index 046f0ce8c0a..59faa55aa7b 100644 --- a/src/com/android/settings/notification/app/AppNotificationSettings.java +++ b/src/com/android/settings/notification/app/AppNotificationSettings.java @@ -104,6 +104,7 @@ public class AppNotificationSettings extends NotificationSettings { mControllers.add(new ShowMorePreferenceController( context, mDependentFieldListener, mBackend)); mControllers.add(new BundleListPreferenceController(context, mBackend)); + mControllers.add(new PromotedNotificationsPreferenceController(context, mBackend)); return new ArrayList<>(mControllers); } } diff --git a/src/com/android/settings/notification/app/PromotedNotificationsPreferenceController.java b/src/com/android/settings/notification/app/PromotedNotificationsPreferenceController.java new file mode 100644 index 00000000000..7d38c86dee6 --- /dev/null +++ b/src/com/android/settings/notification/app/PromotedNotificationsPreferenceController.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024 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.app; + +import android.app.Flags; +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + +import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.RestrictedSwitchPreference; + +public class PromotedNotificationsPreferenceController extends + NotificationPreferenceController implements Preference.OnPreferenceChangeListener { + private static final String KEY_PROMOTED_CATEGORY = "promoted_category"; + private static final String KEY_PROMOTED_SWITCH = "promoted_switch"; + + public PromotedNotificationsPreferenceController(@NonNull Context context, + @NonNull NotificationBackend backend) { + super(context, backend); + } + + @Override + @NonNull + public String getPreferenceKey() { + return KEY_PROMOTED_CATEGORY; + } + + @Override + public boolean isAvailable() { + if (!Flags.uiRichOngoing()) { + return false; + } + return super.isAvailable(); + } + + @Override + boolean isIncludedInFilter() { + // not a channel-specific preference; only at the app level + return false; + } + + /** + * Updates the state of the promoted notifications switch. Because this controller governs + * the full PreferenceCategory, we must find the switch preference within the category first. + */ + public void updateState(@NonNull Preference preference) { + PreferenceCategory category = (PreferenceCategory) preference; + RestrictedSwitchPreference pref = category.findPreference(KEY_PROMOTED_SWITCH); + + if (pref != null && mAppRow != null) { + pref.setDisabledByAdmin(mAdmin); + pref.setEnabled(!pref.isDisabledByAdmin()); + pref.setChecked(mAppRow.canBePromoted); + pref.setOnPreferenceChangeListener(this); + } + } + + @Override + public boolean onPreferenceChange(@NonNull Preference preference, @NonNull Object newValue) { + final boolean canBePromoted = (Boolean) newValue; + if (mAppRow != null && mAppRow.canBePromoted != canBePromoted) { + mAppRow.canBePromoted = canBePromoted; + mBackend.setCanBePromoted(mAppRow.pkg, mAppRow.uid, canBePromoted); + } + return true; + } +} diff --git a/tests/robotests/src/com/android/settings/notification/app/PromotedNotificationsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/PromotedNotificationsPreferenceControllerTest.java new file mode 100644 index 00000000000..917d4694ca3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/app/PromotedNotificationsPreferenceControllerTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 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.app; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Flags; +import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.preference.PreferenceCategory; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.RestrictedSwitchPreference; + +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; + +@RunWith(RobolectricTestRunner.class) +public class PromotedNotificationsPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private Context mContext; + private NotificationBackend.AppRow mAppRow; + @Mock + private NotificationBackend mBackend; + @Mock + private PreferenceCategory mPrefCategory; + private RestrictedSwitchPreference mSwitch; + + private PromotedNotificationsPreferenceController mPrefController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + mSwitch = new RestrictedSwitchPreference(mContext); + when(mPrefCategory.findPreference("promoted_switch")).thenReturn(mSwitch); + mPrefController = new PromotedNotificationsPreferenceController(mContext, mBackend); + + mAppRow = new NotificationBackend.AppRow(); + mAppRow.pkg = "pkg.name"; + mAppRow.uid = 12345; + mPrefController.onResume(mAppRow, null, null, null, null, null, null); + } + + @Test + @DisableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testIsAvailable_flagOff() { + assertThat(mPrefController.isAvailable()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testIsAvailable_flagOn() { + assertThat(mPrefController.isAvailable()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testChecked_canBePromoted() { + mAppRow.canBePromoted = true; + mPrefController.onResume(mAppRow, null, null, null, null, null, null); + + mPrefController.updateState(mPrefCategory); + assertThat(mSwitch.isChecked()).isTrue(); + + mAppRow.canBePromoted = false; + mPrefController.onResume(mAppRow, null, null, null, null, null, null); + mPrefController.updateState(mPrefCategory); + assertThat(mSwitch.isChecked()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testOnPreferenceChange_noChange() { + mAppRow.canBePromoted = true; + mPrefController.onResume(mAppRow, null, null, null, null, null, null); + + // No change means no backend call + mPrefController.onPreferenceChange(mSwitch, true); + verify(mBackend, never()).setCanBePromoted(any(), anyInt(), anyBoolean()); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testOnPreferenceChange_changeOnAndOff() { + mAppRow.canBePromoted = true; + mPrefController.onResume(mAppRow, null, null, null, null, null, null); + + // when the switch value changes to false + mPrefController.onPreferenceChange(mSwitch, false); + + // then updates the app row data in the preference controller + assertThat(mPrefController.mAppRow.canBePromoted).isFalse(); + // and also updates the backend + verify(mBackend, times(1)).setCanBePromoted(eq(mAppRow.pkg), eq(mAppRow.uid), eq(false)); + + // same as above but now from false -> true + mPrefController.onPreferenceChange(mSwitch, true); + assertThat(mPrefController.mAppRow.canBePromoted).isTrue(); + verify(mBackend, times(1)).setCanBePromoted(eq(mAppRow.pkg), eq(mAppRow.uid), eq(true)); + } +}