diff --git a/res/values/strings.xml b/res/values/strings.xml
index c4c7fec5497..8c97b0403e1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8774,6 +8774,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));
+ }
+}