Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a9dc130c authored by Yuri Lin's avatar Yuri Lin
Browse files

Save original channel visibility on bundling notifications

If the original channel a notification is posted to is marked to never be shown on the lockscreen, we need to keep that information available after bundling even though the channel on the notification has changed (reset to the bundle channel).

Fixes: 410825977
Test: manual by posting notifications to secret channels & viewing the lockscreen; NotificationManagerServiceTest
Flag: android.app.notification_classification_ui
Change-Id: I01516507c03e3fcc1bde77a41687c5985d623d0d
parent 2723c553
Loading
Loading
Loading
Loading
+3 −11
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.PipelineEntry
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
@@ -201,19 +200,13 @@ constructor(

    private fun shouldHideIfEntrySilent(entry: PipelineEntry): Boolean =
        when {
            // TODO(b/410825977): The bundle classifier clobbers the channel that the notification
            //  was posted to, and so we don't know whether any child of a bundle is SECRET. For now
            //  treat all bundles as SECRET.
            entry !is ListEntry -> true
            // Show if explicitly high priority (not hidden)
            highPriorityProvider.isExplicitlyHighPriority(entry) -> false
            // Ambient notifications are hidden always from lock screen
            entry.representativeEntry?.isAmbient == true -> true
            entry.asListEntry()?.representativeEntry?.isAmbient == true -> true
            // [Now notification is silent]
            // Hide regardless of parent priority if user wants silent notifs hidden
            // Always hide if user wants silent notifs hidden
            hideSilentNotificationsOnLockscreen -> true
            // Parent priority is high enough to be shown on the lockscreen, do not hide.
            entry.parent?.let(::shouldHideIfEntrySilent) == false -> false
            // Show when silent notifications are allowed on lockscreen
            else -> false
        }
@@ -244,8 +237,7 @@ constructor(
        // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
        // info, and NotificationLockscreenUserManagerImpl is already listening for updates
        // to those
        return entry.ranking.channel != null &&
            entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET
        return entry.ranking.channel?.lockscreenVisibility == VISIBILITY_SECRET
    }

    override fun dump(pw: PrintWriter, args: Array<out String>) =
+17 −1
Original line number Diff line number Diff line
@@ -168,6 +168,7 @@ import static android.service.notification.NotificationListenerService.REASON_US
import static android.service.notification.NotificationListenerService.Ranking.RANKING_DEMOTED;
import static android.service.notification.NotificationListenerService.Ranking.RANKING_PROMOTED;
import static android.service.notification.NotificationListenerService.Ranking.RANKING_UNCHANGED;
import static android.service.notification.NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
import static android.service.notification.NotificationListenerService.TRIM_FULL;
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -12114,6 +12115,21 @@ public class NotificationManagerService extends SystemService {
                    smartReplies = null;
                }
            }
            NotificationChannel effectiveChannel = record.getChannel().copy();
            if (notificationClassificationUi()) {
                // special handling for a notification's channel visibility when bundled: if the
                // notification's original channel had a more strict visibility than the current
                // channel, or if the current channel has an unspecified visibility, patch that
                // original visibility into the channel stored in Ranking.
                if (record.getOriginalChannelVisibility() != VISIBILITY_NO_OVERRIDE) {
                    int currentChannelVis = record.getChannel().getLockscreenVisibility();
                    if (currentChannelVis == VISIBILITY_NO_OVERRIDE
                            || record.getOriginalChannelVisibility() < currentChannelVis) {
                        effectiveChannel.setLockscreenVisibility(
                                record.getOriginalChannelVisibility());
                    }
                }
            }
            ranking.populate(
                    key,
                    rankings.size(),
@@ -12123,7 +12139,7 @@ public class NotificationManagerService extends SystemService {
                    record.getImportance(),
                    record.getImportanceExplanation(),
                    record.getSbn().getOverrideGroupKey(),
                    record.getChannel(),
                    effectiveChannel,
                    record.getPeopleOverride(),
                    record.getSnoozeCriteria(),
                    record.canShowBadge(),
+19 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.app.Flags;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Person;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -228,6 +229,12 @@ public final class NotificationRecord {
    // was present at the time of unclassification.
    private boolean mHadGroupSummaryWhenUnclassified = false;

    // If this notification was classified into a bundle, this value stores the visibility of the
    // original channel associated with this notification. If the original channel is set to be
    // secret, that information should override any more generous visibility settings on the newly
    // assigned bundle.
    private int mOriginalChannelVisibility = NotificationManager.VISIBILITY_NO_OVERRIDE;

    public NotificationRecord(Context context, StatusBarNotification sbn,
            NotificationChannel channel) {
        this.sbn = sbn;
@@ -808,6 +815,8 @@ public final class NotificationRecord {
                }
                if (android.service.notification.Flags.notificationClassification()) {
                    if (signals.containsKey(Adjustment.KEY_TYPE)) {
                        // Store original channel visibility before re-assigning channel
                        setOriginalChannelVisibility(mChannel.getLockscreenVisibility());
                        updateNotificationChannel(signals.getParcelable(Adjustment.KEY_TYPE,
                                NotificationChannel.class));
                        EventLogTags.writeNotificationAdjusted(getKey(),
@@ -815,6 +824,8 @@ public final class NotificationRecord {
                                mChannel.getId());
                    }
                    if (signals.containsKey(Adjustment.KEY_UNCLASSIFY)) {
                        // reset original channel visibility as we're returning to the original
                        setOriginalChannelVisibility(NotificationManager.VISIBILITY_NO_OVERRIDE);
                        updateNotificationChannel(signals.getParcelable(Adjustment.KEY_UNCLASSIFY,
                                NotificationChannel.class));
                        EventLogTags.writeNotificationAdjusted(getKey(),
@@ -1674,6 +1685,14 @@ public final class NotificationRecord {
        mHadGroupSummaryWhenUnclassified = exists;
    }

    public int getOriginalChannelVisibility() {
        return mOriginalChannelVisibility;
    }

    public void setOriginalChannelVisibility(int visibility) {
        mOriginalChannelVisibility = visibility;
    }

    /**
     * Whether this notification is a conversation notification.
     */
+4 −2
Original line number Diff line number Diff line
@@ -57,7 +57,9 @@ public class VisibilityExtractor implements NotificationSignalExtractor {
                    mConfig.canShowNotificationsOnLockscreen(userId);
            boolean dpmCanShowNotifications = adminAllowsKeyguardFeature(userId,
                    DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
            boolean channelCanShowNotifications = record.getChannel().getLockscreenVisibility()
            boolean channelCanShowNotifications =
                    record.getChannel().getLockscreenVisibility() != Notification.VISIBILITY_SECRET
                            && record.getOriginalChannelVisibility()
                            != Notification.VISIBILITY_SECRET;

            if (!userCanShowNotifications || !dpmCanShowNotifications
+75 −0
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -18089,6 +18090,80 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
    }
    @Test
    @EnableFlags({android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION,
            android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI,
            FLAG_NOTIFICATION_FORCE_GROUPING,
            FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
    public void testApplyAdjustment_keyType_storesOriginalChannelVisibility() throws Exception {
        NotificationManagerService.WorkerHandler handler = mock(
                NotificationManagerService.WorkerHandler.class);
        mService.setHandler(handler);
        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
        when(mAssistants.isClassificationTypeAllowed(anyInt(), anyInt())).thenReturn(true);
        when(mAssistants.isAdjustmentAllowedForPackage(anyInt(), anyString(),
                anyString())).thenReturn(true);
        NotificationChannel secret = new NotificationChannel("secretChannelId", "secret channel",
                NotificationManager.IMPORTANCE_DEFAULT);
        mBinderService.createNotificationChannels(mPkg, new ParceledListSlice(List.of(secret)));
        // Need to set the visibility as an update since this isn't a field typically set by apps
        secret.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
        mBinderService.updateNotificationChannelForPackage(mPkg, mUid, secret);
        final NotificationRecord r = generateNotificationRecord(secret);
        mService.addNotification(r);
        Bundle signals = new Bundle();
        signals.putInt(KEY_TYPE, TYPE_NEWS);
        Adjustment adjustment = new Adjustment(
                r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
        mBinderService.applyAdjustmentFromAssistant(null, adjustment);
        waitForIdle();
        r.applyAdjustments();
        // The notification should be bundled now
        assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
        // but the original channel visibility is stored
        assertThat(r.getOriginalChannelVisibility()).isEqualTo(Notification.VISIBILITY_SECRET);
        // check that the information made it to the ranking update too, via the stored channel:
        // it's still the "news" channel, but with the stricter visibility applied
        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
        when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
        when(info.isSameUser(anyInt())).thenReturn(true);
        NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
        NotificationListenerService.Ranking ranking =
                nru.getRankingMap().getRawRankingObject(r.getKey());
        assertThat(ranking.getChannel().getId()).isEqualTo(NEWS_ID);
        assertThat(ranking.getChannel().getLockscreenVisibility()).isEqualTo(
                Notification.VISIBILITY_SECRET);
        // Now un-classify
        doAnswer(invocationOnMock -> {
            ((NotificationRecord) invocationOnMock.getArguments()[0]).applyAdjustments();
            ((NotificationRecord) invocationOnMock.getArguments()[0]).calculateImportance();
            return null;
        }).when(mRankingHelper).extractSignals(any(NotificationRecord.class));
        mService.unclassifyNotification(r.getKey());
        mService.handleRankingSort();
        // confirm it's unclassified
        assertThat(r.getChannel().getId()).isEqualTo(secret.getId());
        // and that the original channel visibility is reset
        assertThat(r.getOriginalChannelVisibility()).isEqualTo(VISIBILITY_NO_OVERRIDE);
        // and the ranking objects will be updated accordingly (the ranking's channel should be the
        // notification's original channel)
        NotificationRankingUpdate nru2 = mService.makeRankingUpdateLocked(info);
        NotificationListenerService.Ranking ranking2 =
                nru2.getRankingMap().getRawRankingObject(r.getKey());
        assertThat(ranking2.getChannel()).isEqualTo(secret);
    }
    @Test
    @EnableFlags({FLAG_API_RICH_ONGOING,
            android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION,
Loading