diff --git a/res/layout/notification_ls_minimalism_selector.xml b/res/layout/notification_ls_minimalism_selector.xml
new file mode 100644
index 00000000000..d37ad5401a2
--- /dev/null
+++ b/res/layout/notification_ls_minimalism_selector.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1ad507a26f8..847e27cb09b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8789,6 +8789,18 @@
Automatically remove previously viewed notifications from the lock screen
+
+ Full list
+
+
+ The current default placement is a full shelf and notification stack.
+
+
+ Compact
+
+
+ New notifications are collapsed into a shelf on your lockscreen.
+
Notifications on lock screen
@@ -8851,6 +8863,18 @@
When your device is locked, how do you want profile notifications to show?
+
+ Hide seen notifications
+
+
+ Seen notifications are removed from the lock screen.
+
+
+ Hide silent notifications
+
+
+ Silent notifications and conversations are removed from the lock screen.
+
Profile notifications
diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml
index aedf0b413aa..e5fddc4e58e 100644
--- a/res/xml/configure_notification_settings.xml
+++ b/res/xml/configure_notification_settings.xml
@@ -92,23 +92,31 @@
android:singleLineTitle="false"
android:summary="@string/summary_placeholder" />
+
+
diff --git a/res/xml/lock_screen_notifications_settings.xml b/res/xml/lock_screen_notifications_settings.xml
new file mode 100644
index 00000000000..b5fb12869e0
--- /dev/null
+++ b/res/xml/lock_screen_notifications_settings.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/more_security_privacy_settings.xml b/res/xml/more_security_privacy_settings.xml
index f2004937f60..56706c3f91a 100644
--- a/res/xml/more_security_privacy_settings.xml
+++ b/res/xml/more_security_privacy_settings.xml
@@ -83,6 +83,12 @@
android:summary="@string/summary_placeholder"
settings:searchable="false"/>
+
+
+
+
+
+
profiles = mUserManager.getProfiles(UserHandle.myUserId());
+
+ for (UserInfo profile: profiles) {
+ if (profile.isManagedProfile()
+ && profile.getUserHandle().getIdentifier() != UserHandle.myUserId()) {
+ mWorkProfileUserId = profile.getUserHandle().getIdentifier();
+ }
+ }
+ }
+
+ @Override
+ public void onStateChanged(@NonNull LifecycleOwner lifecycleOwner,
+ @NonNull Lifecycle.Event event) {
+ if (event == Lifecycle.Event.ON_RESUME) {
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS),
+ /* notifyForDescendants= */ false, mContentObserver);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(
+ Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+ /* notifyForDescendants= */ false,
+ mContentObserver
+ );
+ } else if (event == Lifecycle.Event.ON_PAUSE) {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+ }
+
+ @Override
+ public void displayPreference(@NonNull PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ int userId = getUserId();
+
+ if (mPreference != null && userId != UserHandle.USER_NULL) {
+ mPreference.setDisabledByAdmin(getEnforcedAdmin(userId));
+ }
+ }
+
+ private RestrictedLockUtils.EnforcedAdmin getEnforcedAdmin(int userId) {
+ RestrictedLockUtils.EnforcedAdmin admin =
+ RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
+ mContext, KEYGUARD_DISABLE_SECURE_NOTIFICATIONS, userId);
+ if (admin != null) {
+ return admin;
+ }
+ admin = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
+ mContext, KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS, userId);
+ return admin;
+ }
+
+ private int getUserId() {
+ return KEY_SHOW_SENSITIVE.equals(getPreferenceKey())
+ ? UserHandle.myUserId() : mWorkProfileUserId;
+ }
+
+ @Override
+ public void updateState(@Nullable Preference preference) {
+ if (preference == null) return;
+ setChecked(showSensitiveContentOnlyWhenUnlocked());
+ preference.setVisible(isAvailable());
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ // hide setting if no lock screen notification
+ if (!lockScreenShowNotification()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
+ // hide setting if no screen lock
+ if (!isLockScreenSecure()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
+ // For the work profile toggle
+ if (KEY_SHOW_SENSITIVE_WORK_PROFILE.equals(getPreferenceKey())) {
+ // hide work profile setting if no work profile
+ if (mWorkProfileUserId == UserHandle.myUserId()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
+ // specifically the work profile setting requires the work profile to be unlocked
+ if (mKeyguardManager.isDeviceLocked(mWorkProfileUserId)) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+ }
+
+ return AVAILABLE;
+ }
+
+ /**
+ * @return Whether showing notifications on the lockscreen is enabled.
+ */
+ private boolean lockScreenShowNotification() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, ON) != OFF;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return showSensitiveContentOnlyWhenUnlocked();
+ }
+
+ private boolean showSensitiveContentOnlyWhenUnlocked() {
+ int userId = getUserId();
+ if (!isLockScreenSecure()) return false;
+ if (getEnforcedAdmin(userId) != null) return true;
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, ON, userId) == OFF;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ return Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ (isChecked ? OFF : ON), getUserId()
+ );
+ }
+
+ private boolean isLockScreenSecure() {
+ return FeatureFactory.getFeatureFactory()
+ .getSecurityFeatureProvider()
+ .getLockPatternUtils(mContext)
+ .isSecure(UserHandle.myUserId());
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ // not needed since it's not sliceable
+ return NO_RES;
+ }
+}
diff --git a/src/com/android/settings/notification/LockScreenNotificationsGlobalPreferenceController.java b/src/com/android/settings/notification/LockScreenNotificationsGlobalPreferenceController.java
new file mode 100644
index 00000000000..7c7664e5da9
--- /dev/null
+++ b/src/com/android/settings/notification/LockScreenNotificationsGlobalPreferenceController.java
@@ -0,0 +1,117 @@
+/*
+ * 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;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleEventObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.server.notification.Flags;
+import com.android.settings.widget.SettingsMainSwitchPreferenceController;
+
+public class LockScreenNotificationsGlobalPreferenceController
+ extends SettingsMainSwitchPreferenceController
+ implements LifecycleEventObserver {
+
+ public static final int ON = 1;
+ public static final int OFF = 0;
+ private final ContentResolver mContentResolver;
+ private final ContentObserver mContentObserver;
+ @Nullable private Preference mPreference;
+
+
+ public LockScreenNotificationsGlobalPreferenceController(
+ @NonNull Context context,
+ @NonNull String preferenceKey
+ ) {
+ super(context, preferenceKey);
+ mContentResolver = context.getContentResolver();
+ mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange, @Nullable Uri uri) {
+ if (mPreference == null) return;
+ updateState(mPreference);
+ }
+ };
+ }
+
+ @Override
+ public void displayPreference(@NonNull PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public void updateState(@NonNull Preference preference) {
+ super.updateState(preference);
+ setChecked(lockScreenShowNotifications());
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ // TODO: b/367455695 - remove this when the feature flag is removed!
+ return Flags.notificationLockScreenSettings() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return lockScreenShowNotifications();
+ }
+
+ private boolean lockScreenShowNotifications() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, OFF) == ON;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ return Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, (isChecked ? ON : OFF));
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ // not needed since it's not sliceable
+ return NO_RES;
+ }
+
+ @Override
+ public void onStateChanged(@NonNull LifecycleOwner lifecycleOwner,
+ @NonNull Lifecycle.Event event) {
+ if (event == Lifecycle.Event.ON_RESUME) {
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS),
+ /* notifyForDescendants= */ false,
+ mContentObserver
+ );
+ } else {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/LockScreenNotificationsPreferencePageController.java b/src/com/android/settings/notification/LockScreenNotificationsPreferencePageController.java
new file mode 100644
index 00000000000..61ac27485db
--- /dev/null
+++ b/src/com/android/settings/notification/LockScreenNotificationsPreferencePageController.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import android.content.Context;
+
+import com.android.server.notification.Flags;
+import com.android.settings.core.BasePreferenceController;
+
+// TODO(b/367455695): remove controller when the feature flag is removed!
+
+/**
+ * Controller for lock screen notifications settings page.
+ */
+public class LockScreenNotificationsPreferencePageController extends BasePreferenceController {
+
+ public LockScreenNotificationsPreferencePageController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return Flags.notificationLockScreenSettings() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+}
diff --git a/src/com/android/settings/notification/LockScreenNotificationsPreferencePageFragment.java b/src/com/android/settings/notification/LockScreenNotificationsPreferencePageFragment.java
new file mode 100644
index 00000000000..ef53e2da829
--- /dev/null
+++ b/src/com/android/settings/notification/LockScreenNotificationsPreferencePageFragment.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import com.android.server.notification.Flags;
+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 notifications on lock screen preference page.
+ */
+@SearchIndexable
+public class LockScreenNotificationsPreferencePageFragment extends DashboardFragment {
+
+ @Override
+ public int getMetricsCategory() {
+ //TODO(b/367455695): create a new metrics category
+ return SettingsEnums.SETTINGS_LOCK_SCREEN_PREFERENCES;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.lock_screen_notifications_settings;
+ }
+ @Override
+ protected String getLogTag() {
+ return "LockScreenNotificationsPreferenceFragment";
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.lock_screen_notifications_settings) {
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ return Flags.notificationLockScreenSettings();
+ }
+ };
+}
diff --git a/src/com/android/settings/notification/LockscreenNotificationMinimalismPreferenceController.java b/src/com/android/settings/notification/LockscreenNotificationMinimalismPreferenceController.java
index 7b48ba7ff5a..03875566621 100644
--- a/src/com/android/settings/notification/LockscreenNotificationMinimalismPreferenceController.java
+++ b/src/com/android/settings/notification/LockscreenNotificationMinimalismPreferenceController.java
@@ -56,6 +56,10 @@ public class LockscreenNotificationMinimalismPreferenceController
@Override
public int getAvailabilityStatus() {
+ // Hide when the notifications on lock screen settings page flag is enabled.
+ if (Flags.notificationLockScreenSettings()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
if (!Flags.notificationMinimalism()) {
return CONDITIONALLY_UNAVAILABLE;
}
diff --git a/src/com/android/settings/notification/PoliteNotificationsPreferenceController.java b/src/com/android/settings/notification/PoliteNotificationsPreferenceController.java
index e6e0947149a..49edcb8b769 100644
--- a/src/com/android/settings/notification/PoliteNotificationsPreferenceController.java
+++ b/src/com/android/settings/notification/PoliteNotificationsPreferenceController.java
@@ -18,16 +18,19 @@ package com.android.settings.notification;
import android.content.Context;
+import androidx.annotation.NonNull;
+
import com.android.server.notification.Flags;
import com.android.settings.core.BasePreferenceController;
-// TODO: b/291897570 - remove controller when the feature flag is removed!
+// TODO(b/330606963): 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) {
+ public PoliteNotificationsPreferenceController(@NonNull Context context,
+ @NonNull String preferenceKey) {
super(context, preferenceKey);
}
diff --git a/src/com/android/settings/notification/RedactNotificationPreferenceController.java b/src/com/android/settings/notification/RedactNotificationPreferenceController.java
index 4ebf08e0a43..ea52be6d88f 100644
--- a/src/com/android/settings/notification/RedactNotificationPreferenceController.java
+++ b/src/com/android/settings/notification/RedactNotificationPreferenceController.java
@@ -33,6 +33,7 @@ import android.provider.Settings;
import androidx.preference.PreferenceScreen;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.notification.Flags;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.overlay.FeatureFactory;
@@ -120,6 +121,11 @@ public class RedactNotificationPreferenceController extends TogglePreferenceCont
@Override
public int getAvailabilityStatus() {
+ // Hide when the notifications on lock screen settings page flag is enabled.
+ if (Flags.notificationLockScreenSettings()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
// hide work profile setting if no work profile
if (KEY_LOCKSCREEN_WORK_PROFILE_REDACT.equals(getPreferenceKey())
&& mProfileUserId == UserHandle.myUserId()) {
diff --git a/src/com/android/settings/notification/ShowOnLockScreenNotificationPreferenceController.java b/src/com/android/settings/notification/ShowOnLockScreenNotificationPreferenceController.java
index 1addd8220db..fc5fa740359 100644
--- a/src/com/android/settings/notification/ShowOnLockScreenNotificationPreferenceController.java
+++ b/src/com/android/settings/notification/ShowOnLockScreenNotificationPreferenceController.java
@@ -26,6 +26,7 @@ import android.provider.Settings;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
+import com.android.server.notification.Flags;
import com.android.settings.R;
import com.android.settings.RestrictedListPreference;
import com.android.settings.core.PreferenceControllerMixin;
@@ -63,7 +64,8 @@ public class ShowOnLockScreenNotificationPreferenceController extends AbstractPr
@Override
public boolean isAvailable() {
- return true;
+ // When notificationLockScreenSettings is enabled, show the lock screen notif settings page
+ return !Flags.notificationLockScreenSettings();
}
@Override
diff --git a/src/com/android/settings/notification/ShowOnlyUnseenNotificationsOnLockscreenPreferenceController.java b/src/com/android/settings/notification/ShowOnlyUnseenNotificationsOnLockscreenPreferenceController.java
index 9534483d25f..225fc7c8946 100644
--- a/src/com/android/settings/notification/ShowOnlyUnseenNotificationsOnLockscreenPreferenceController.java
+++ b/src/com/android/settings/notification/ShowOnlyUnseenNotificationsOnLockscreenPreferenceController.java
@@ -57,6 +57,10 @@ public class ShowOnlyUnseenNotificationsOnLockscreenPreferenceController
@Override
public int getAvailabilityStatus() {
+ // Hide when the notifications on lock screen page flag is enabled.
+ if (Flags.notificationLockScreenSettings()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
if (Flags.notificationMinimalism()) {
if (!isNotifOnLockScreenEnabled()) {
return DISABLED_DEPENDENT_SETTING;
diff --git a/src/com/android/settings/notification/lockscreen/MinimalismPreferenceController.java b/src/com/android/settings/notification/lockscreen/MinimalismPreferenceController.java
new file mode 100644
index 00000000000..78be16a1d71
--- /dev/null
+++ b/src/com/android/settings/notification/lockscreen/MinimalismPreferenceController.java
@@ -0,0 +1,196 @@
+/*
+ * 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.lockscreen;
+
+import static android.provider.Settings.Secure.LOCK_SCREEN_NOTIFICATION_MINIMALISM;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleEventObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceScreen;
+
+import com.android.server.notification.Flags;
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.IllustrationPreference;
+import com.android.settingslib.widget.LayoutPreference;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MinimalismPreferenceController
+ extends BasePreferenceController
+ implements LifecycleEventObserver {
+
+ private static final int LS_SHOW_NOTIF_ON = 1;
+ private static final int LS_SHOW_NOTIF_OFF = 0;
+ private static final int LS_MINIMALISM_OFF = 0;
+ private static final int LS_MINIMALISM_ON = 1;
+ private static final String KEY_MINIMALISM_PREFERENCE = "ls_minimalism";
+ private static final String KEY_FULL_LIST_ILLUSTRATION = "full_list_illustration";
+ private static final String KEY_COMPACT_ILLUSTRATION = "compact_illustration";
+ private static final Uri URI_LOCK_SCREEN_NOTIFICATION_MINIMALISM =
+ Settings.Secure.getUriFor(LOCK_SCREEN_NOTIFICATION_MINIMALISM);
+ private static final Uri URI_LOCK_SCREEN_SHOW_NOTIFICATIONS =
+ Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ @Nullable private LayoutPreference mPreference;
+ @Nullable private TextView mDescView;
+ private Map mButtons = new HashMap<>();
+ private Map mIllustrations = new HashMap<>();
+ private final Map mDescriptionTexts = Map.ofEntries(
+ Map.entry(LS_MINIMALISM_OFF, R.string.lock_screen_notifs_full_list_desc),
+ Map.entry(LS_MINIMALISM_ON, R.string.lock_screen_notifs_compact_desc)
+ );
+
+ private final ContentResolver mContentResolver;
+
+ final ContentObserver mContentObserver = new ContentObserver(
+ new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange, @Nullable Uri uri) {
+ refreshState(uri);
+ }
+ };
+
+ public MinimalismPreferenceController(@NonNull Context context, @NonNull String preferenceKey) {
+ super(context, preferenceKey);
+ mContentResolver = context.getContentResolver();
+ }
+
+ @Override
+ public void onStateChanged(@NonNull LifecycleOwner lifecycleOwner,
+ @NonNull Lifecycle.Event event) {
+ if (event == Lifecycle.Event.ON_RESUME) {
+ mContentResolver.registerContentObserver(
+ URI_LOCK_SCREEN_NOTIFICATION_MINIMALISM,
+ /* notifyForDescendants= */ false,
+ mContentObserver
+ );
+ mContentResolver.registerContentObserver(
+ URI_LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ /* notifyForDescendants= */ false,
+ mContentObserver
+ );
+ } else if (event == Lifecycle.Event.ON_PAUSE) {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (!Flags.notificationMinimalism()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+ if (!lockScreenShowNotification()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+ return AVAILABLE;
+ }
+
+ /**
+ * @return Whether showing notifications on the lockscreen is enabled.
+ */
+ private boolean lockScreenShowNotification() {
+ return Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ LS_SHOW_NOTIF_OFF
+ ) == LS_SHOW_NOTIF_ON;
+ }
+
+ @Override
+ public void displayPreference(@NonNull PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(KEY_MINIMALISM_PREFERENCE);
+ mDescView = mPreference.findViewById(R.id.notif_ls_style_desc);
+
+ mButtons = Map.ofEntries(
+ Map.entry(LS_MINIMALISM_OFF,
+ mPreference.findViewById(R.id.button_full)),
+ Map.entry(LS_MINIMALISM_ON,
+ mPreference.findViewById(R.id.button_compact))
+ );
+
+ mIllustrations = Map.ofEntries(
+ Map.entry(LS_MINIMALISM_OFF,
+ screen.findPreference(KEY_FULL_LIST_ILLUSTRATION)),
+ Map.entry(LS_MINIMALISM_ON,
+ screen.findPreference(KEY_COMPACT_ILLUSTRATION))
+ );
+ mButtons.forEach((value, button) -> button.setOnClickListener(v ->
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_NOTIFICATION_MINIMALISM,
+ value
+ )
+ ));
+
+ refreshState(URI_LOCK_SCREEN_NOTIFICATION_MINIMALISM);
+ }
+
+ private void highlightButton(int currentValue) {
+ mButtons.forEach((value, button) -> button.setSelected(currentValue == value));
+ }
+
+ private void highlightIllustration(int currentValue) {
+ mIllustrations.forEach((value, preference)
+ -> preference.setVisible(currentValue == value));
+ }
+
+ private void highlightDescription(int value) {
+ if (mDescView == null) return;
+ Integer descStringId = mDescriptionTexts.get(value);
+ if (descStringId != null) {
+ mDescView.setText(descStringId);
+ }
+ }
+
+ private int getCurrentMinimalismValue() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ LOCK_SCREEN_NOTIFICATION_MINIMALISM, LS_MINIMALISM_ON);
+ }
+
+ private void refreshState(@Nullable Uri uri) {
+ if (mPreference == null) return;
+ if (URI_LOCK_SCREEN_SHOW_NOTIFICATIONS.equals(uri) && !lockScreenShowNotification()) {
+ // hide all preferences when showing notifications on lock screen is disabled
+ mIllustrations.forEach((value, preference)
+ -> preference.setVisible(false));
+ mPreference.setVisible(false);
+ } else {
+ mPreference.setVisible(isAvailable());
+ int currentValue = getCurrentMinimalismValue();
+ highlightButton(currentValue);
+ highlightIllustration(currentValue);
+ highlightDescription(currentValue);
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/LockScreenNotificationShowSensitiveToggleControllerTest.java b/tests/robotests/src/com/android/settings/notification/LockScreenNotificationShowSensitiveToggleControllerTest.java
new file mode 100644
index 00000000000..ea556a592d8
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/LockScreenNotificationShowSensitiveToggleControllerTest.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal;
+import com.android.settings.testutils.shadow.ShadowUtils;
+import com.android.settingslib.RestrictedSwitchPreference;
+
+import org.junit.After;
+import org.junit.Before;
+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;
+
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+ ShadowUtils.class,
+ ShadowRestrictedLockUtilsInternal.class,
+})
+public class LockScreenNotificationShowSensitiveToggleControllerTest {
+
+ @Mock
+ private DevicePolicyManager mDpm;
+ @Mock
+ UserManager mUm;
+ @Mock
+ KeyguardManager mKm;
+ @Mock
+ private PreferenceScreen mScreen;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
+ @Mock
+ private Context mMockContext;
+
+ private Context mContext;
+ private LockScreenNotificationShowSensitiveToggleController mController;
+ private LockScreenNotificationShowSensitiveToggleController mWorkController;
+ private RestrictedSwitchPreference mPreference;
+ private RestrictedSwitchPreference mWorkPreference;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest();
+ when(featureFactory.securityFeatureProvider.getLockPatternUtils(mMockContext))
+ .thenReturn(mLockPatternUtils);
+ when(mMockContext.getContentResolver()).thenReturn(mContext.getContentResolver());
+ when(mMockContext.getSystemService(UserManager.class)).thenReturn(mUm);
+ when(mMockContext.getSystemService(DevicePolicyManager.class)).thenReturn(mDpm);
+ when(mMockContext.getSystemService(KeyguardManager.class)).thenReturn(mKm);
+ when(mUm.getProfiles(anyInt())).thenReturn(Arrays.asList(new UserInfo(0, "", 0)));
+
+ mController = new LockScreenNotificationShowSensitiveToggleController(
+ mMockContext,
+ LockScreenNotificationShowSensitiveToggleController.KEY_SHOW_SENSITIVE
+ );
+ mPreference = new RestrictedSwitchPreference(mContext);
+ mPreference.setKey(mController.getPreferenceKey());
+ when(mScreen.findPreference(
+ mController.getPreferenceKey())).thenReturn(mPreference);
+
+ when(mUm.getProfiles(anyInt())).thenReturn(Arrays.asList(
+ new UserInfo(5, "", 0),
+ new UserInfo(10, "", UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_PROFILE)));
+ mWorkController = new LockScreenNotificationShowSensitiveToggleController(
+ mMockContext,
+ LockScreenNotificationShowSensitiveToggleController.KEY_SHOW_SENSITIVE_WORK_PROFILE
+ );
+ mWorkPreference = new RestrictedSwitchPreference(mContext);
+ mWorkPreference.setKey(mWorkController.getPreferenceKey());
+ when(mScreen.findPreference(
+ mWorkController.getPreferenceKey())).thenReturn(mWorkPreference);
+ }
+
+ @After
+ public void tearDown() {
+ ShadowRestrictedLockUtilsInternal.reset();
+ }
+
+ @Test
+ public void profileUserIds() {
+ assertThat(mController.mWorkProfileUserId).isEqualTo(0);
+ assertThat(mWorkController.mWorkProfileUserId).isEqualTo(10);
+ }
+
+ @Test
+ public void getAvailabilityStatus_noSecureLockscreen() {
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(false);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1, 0);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1, 10);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ assertThat(mWorkController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_noWorkProfile() {
+ // reset controllers with no work profile
+ when(mUm.getProfiles(anyInt())).thenReturn(Arrays.asList(
+ new UserInfo(UserHandle.myUserId(), "", 0)));
+ mWorkController = new LockScreenNotificationShowSensitiveToggleController(
+ mMockContext,
+ LockScreenNotificationShowSensitiveToggleController.KEY_SHOW_SENSITIVE_WORK_PROFILE
+ );
+ mController = new LockScreenNotificationShowSensitiveToggleController(mMockContext,
+ LockScreenNotificationShowSensitiveToggleController.KEY_SHOW_SENSITIVE);
+
+ // should otherwise show
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1, 0);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ assertThat(mWorkController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void displayPreference_adminSaysNoRedaction() {
+ ShadowRestrictedLockUtilsInternal.setKeyguardDisabledFeatures(
+ KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ mController.displayPreference(mScreen);
+ assertThat(mPreference.isDisabledByAdmin()).isTrue();
+ mWorkController.displayPreference(mScreen);
+ assertThat(mWorkPreference.isDisabledByAdmin()).isTrue();
+ }
+
+ @Test
+ public void displayPreference_adminSaysNoSecure() {
+ ShadowRestrictedLockUtilsInternal.setKeyguardDisabledFeatures(
+ KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+ mController.displayPreference(mScreen);
+ assertThat(mPreference.isDisabledByAdmin()).isTrue();
+ mWorkController.displayPreference(mScreen);
+ assertThat(mWorkPreference.isDisabledByAdmin()).isTrue();
+ }
+
+ @Test
+ public void displayPreference() {
+ ShadowRestrictedLockUtilsInternal.setKeyguardDisabledFeatures(0);
+
+ mController.displayPreference(mScreen);
+ assertThat(mPreference.isDisabledByAdmin()).isFalse();
+ mWorkController.displayPreference(mScreen);
+ assertThat(mWorkPreference.isDisabledByAdmin()).isFalse();
+ }
+
+ @Test
+ public void getAvailabilityStatus_adminSaysNoNotifications() {
+ when(mDpm.getKeyguardDisabledFeatures(eq(null), anyInt())).thenReturn(
+ KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+ // should show
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1, 0);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1, 10);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ assertThat(mWorkController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_noNotifications() {
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 0, 0);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 0, 10);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ assertThat(mWorkController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_workProfileLocked() {
+ // should otherwise show
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1, 0);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1, 10);
+
+ when(mKm.isDeviceLocked(10)).thenReturn(true);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ assertThat(mWorkController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_show() {
+ // should show
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1, 0);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1, 10);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ assertThat(mWorkController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void isChecked() {
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ 0, 0);
+
+ assertThat(mController.isChecked()).isTrue();
+
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ 1, 0);
+
+ assertThat(mController.isChecked()).isFalse();
+ }
+
+ @Test
+ public void isChecked_work() {
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ 0, 10);
+
+ assertThat(mWorkController.isChecked()).isTrue();
+
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ 1, 10);
+
+ assertThat(mWorkController.isChecked()).isFalse();
+ }
+
+ @Test
+ public void isChecked_admin() {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ 1, 0);
+
+ ShadowRestrictedLockUtilsInternal.setKeyguardDisabledFeatures(
+ KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+ assertThat(mController.isChecked()).isFalse();
+ }
+
+ @Test
+ public void setChecked_false() throws Exception {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ 0, 0);
+
+ mController.setChecked(false);
+ assertThat(Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0))
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void setChecked_workProfile_true() throws Exception {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ 1, 10);
+
+ mWorkController.setChecked(true);
+ assertThat(Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 10))
+ .isEqualTo(0);
+ }
+
+ @Test
+ public void setChecked_true() throws Exception {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ 1, 0);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ 1, 10);
+
+ mController.setChecked(true);
+ mWorkController.setChecked(true);
+ assertThat(Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 10))
+ .isEqualTo(0);
+ assertThat(Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0))
+ .isEqualTo(0);
+ }
+}
+
diff --git a/tests/robotests/src/com/android/settings/notification/LockScreenNotificationsPreferencePageControllerTest.java b/tests/robotests/src/com/android/settings/notification/LockScreenNotificationsPreferencePageControllerTest.java
new file mode 100644
index 00000000000..472d9cb6f65
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/LockScreenNotificationsPreferencePageControllerTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+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;
+
+// TODO(b/367455695): remove test when feature flag is cleaned
+@RunWith(RobolectricTestRunner.class)
+public class LockScreenNotificationsPreferencePageControllerTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private static final String PREFERENCE_KEY = "lock_screen_notifications_page";
+
+ private LockScreenNotificationsPreferencePageController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mController = new LockScreenNotificationsPreferencePageController(
+ RuntimeEnvironment.application,
+ PREFERENCE_KEY);
+ }
+
+ @Test
+ public void isAvailable_flagEnabled_shouldReturnTrue() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_LOCK_SCREEN_SETTINGS);
+ assertThat(mController.isAvailable()).isTrue();
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE);
+ }
+
+ @Test
+ public void isAvailable_flagDisabled_shouldReturnFalse() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_NOTIFICATION_LOCK_SCREEN_SETTINGS);
+ assertThat(mController.isAvailable()).isFalse();
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+ }
+
+}