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

Commit 1d37ad40 authored by Nate Myren's avatar Nate Myren
Browse files

Redact smart replies and actions from untrusted listeners

These actions/replies may include sensitive material, if the
notificaiton has sensitive content.

Bug: 313709930
Bug: 301960090
Test: atest SensitiveNotificationRedactionTest
Change-Id: If837c5b404b0c66240c10cdd65aa5f15aa4a3d2e
parent a338097c
Loading
Loading
Loading
Loading
+24 −18
Original line number Diff line number Diff line
@@ -10733,6 +10733,14 @@ public class NotificationManagerService extends SystemService {
            final String key = record.getSbn().getKey();
            final NotificationListenerService.Ranking ranking =
                    new NotificationListenerService.Ranking();
            ArrayList<Notification.Action> smartActions = record.getSystemGeneratedSmartActions();
            ArrayList<CharSequence> smartReplies = record.getSmartReplies();
            if (redactSensitiveNotificationsFromUntrustedListeners()
                    && !mListeners.isUidTrusted(info.uid)
                    && mListeners.hasSensitiveContent(record)) {
                smartActions = null;
                smartReplies = null;
            }
            ranking.populate(
                    key,
                    rankings.size(),
@@ -10750,8 +10758,8 @@ public class NotificationManagerService extends SystemService {
                    record.isHidden(),
                    record.getLastAudiblyAlertedMs(),
                    record.getSound() != null || record.getVibration() != null,
                    record.getSystemGeneratedSmartActions(),
                    record.getSmartReplies(),
                    smartActions,
                    smartReplies,
                    record.canBubble(),
                    record.isTextChanged(),
                    record.isConversation(),
@@ -11501,21 +11509,17 @@ public class NotificationManagerService extends SystemService {
            super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
            String pkgName = getPackageName(pkgOrComponent);
            if (redactSensitiveNotificationsFromUntrustedListeners()) {
                try {
                    int uid = mPackageManagerClient.getPackageUidAsUser(pkgName, userId);
                    if (!enabled) {
                int uid = mPackageManagerInternal.getPackageUid(pkgName, 0, userId);
                if (!enabled && uid >= 0) {
                    synchronized (mTrustedListenerUids) {
                        mTrustedListenerUids.remove(uid);
                    }
                }
                    if (enabled && isAppTrustedNotificationListenerService(uid, pkgName)) {
                if (enabled && uid >= 0 && isAppTrustedNotificationListenerService(uid, pkgName)) {
                    synchronized (mTrustedListenerUids) {
                        mTrustedListenerUids.add(uid);
                    }
                }
                } catch (NameNotFoundException e) {
                    Slog.e(TAG, "PackageManager could not find package " + pkgName, e);
                }
            }
            mContext.sendBroadcastAsUser(
@@ -11934,8 +11938,10 @@ public class NotificationManagerService extends SystemService {
                for (final ManagedServiceInfo info : getServices()) {
                    boolean isTrusted = isUidTrusted(info.uid);
                    boolean sendRedacted = isNewSensitive && !isTrusted;
                    boolean sendOldRedacted = isOldSensitive && !isTrusted;
                    boolean sendRedacted = redactSensitiveNotificationsFromUntrustedListeners()
                            && isNewSensitive && !isTrusted;
                    boolean sendOldRedacted = redactSensitiveNotificationsFromUntrustedListeners()
                            && isOldSensitive && !isTrusted;
                    boolean sbnVisible = isVisibleToListener(sbn, r.getNotificationType(), info);
                    boolean oldSbnVisible = (oldSbn != null)
                            && isVisibleToListener(oldSbn, old.getNotificationType(), info);
@@ -12034,7 +12040,7 @@ public class NotificationManagerService extends SystemService {
        StatusBarNotification redactStatusBarNotification(StatusBarNotification sbn) {
            if (!redactSensitiveNotificationsFromUntrustedListeners()) {
                return sbn;
                throw new RuntimeException("redactStatusBarNotification called while flag is off");
            }
            ApplicationInfo appInfo = sbn.getNotification().extras.getParcelable(
@@ -12206,6 +12212,7 @@ public class NotificationManagerService extends SystemService {
        public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
            boolean isHiddenRankingUpdate = changedHiddenNotifications != null
                    && changedHiddenNotifications.size() > 0;
            // TODO (b/73052211): if the ranking update changed the notification type,
            // cancel notifications for NLSes that can't see them anymore
            for (final ManagedServiceInfo serviceInfo : getServices()) {
@@ -12229,7 +12236,6 @@ public class NotificationManagerService extends SystemService {
                if (notifyThisListener || !isHiddenRankingUpdate) {
                    final NotificationRankingUpdate update = makeRankingUpdateLocked(
                            serviceInfo);
                    mHandler.post(() -> notifyRankingUpdate(serviceInfo, update));
                }
            }
+10 −15
Original line number Diff line number Diff line
@@ -68,10 +68,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.INotificationListener;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerService;
@@ -111,7 +108,7 @@ import java.util.concurrent.CountDownLatch;
public class NotificationListenersTest extends UiServiceTestCase {

    @Rule
    public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
    public SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Mock
    private PackageManager mPm;
@@ -696,8 +693,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
    }

    @Test
    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
    public void testListenerTrusted_withPermission() throws RemoteException {
        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        when(mNm.mPackageManager.checkUidPermission(RECEIVE_SENSITIVE_NOTIFICATIONS, mUid1))
                .thenReturn(PERMISSION_GRANTED);
        ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
@@ -706,8 +703,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
    }

    @Test
    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
    public void testListenerTrusted_withSystemSignature() {
        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        when(mNm.mPackageManagerInternal.isPlatformSigned(mCn1.getPackageName())).thenReturn(true);
        ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
        mListeners.onServiceAdded(info);
@@ -715,8 +712,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
    }

    @Test
    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
    public void testListenerTrusted_withCdmAssociation() throws Exception {
        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        mNm.mCompanionManager = mock(ICompanionDeviceManager.class);
        AssociationInfo assocInfo = mock(AssociationInfo.class);
        when(assocInfo.isRevoked()).thenReturn(false);
@@ -731,16 +728,16 @@ public class NotificationListenersTest extends UiServiceTestCase {
    }

    @Test
    @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
    public void testListenerTrusted_ifFlagDisabled() {
        mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
        mListeners.onServiceAdded(info);
        assertTrue(mListeners.isUidTrusted(mUid1));
    }

    @Test
    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
    public void testRedaction_whenPosted() {
        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
        infos.add(getMockServiceInfo());
        doReturn(infos).when(mListeners).getServices();
@@ -762,13 +759,11 @@ public class NotificationListenersTest extends UiServiceTestCase {
        mListeners.notifyPostedLocked(r, old);
        verify(mListeners, atLeast(1)).redactStatusBarNotification(eq(sbn));
        verify(mListeners, never()).redactStatusBarNotification(eq(oldSbn));


    }

    @Test
    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
    public void testRedaction_whenPosted_oldRemoved() {
        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
        infos.add(getMockServiceInfo());
        doReturn(infos).when(mListeners).getServices();
@@ -795,8 +790,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
    }

    @Test
    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
    public void testRedaction_whenRemoved() {
        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        doReturn(mock(StatusBarNotification.class))
                .when(mListeners).redactStatusBarNotification(any());
        ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
@@ -816,8 +811,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
    }

    @Test
    @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
    public void testRedaction_noneIfFlagDisabled() {
        mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
        infos.add(getMockServiceInfo());
        doReturn(infos).when(mListeners).getServices();
+96 −10
Original line number Diff line number Diff line
@@ -77,7 +77,9 @@ import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -213,9 +215,6 @@ import android.os.UserManager;
import android.os.WorkSource;
import android.permission.PermissionManager;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.rule.DeniedDevices;
import android.platform.test.rule.DeviceProduct;
@@ -351,9 +350,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    @Rule
    public TestRule compatChangeRule = new PlatformCompatChangeRule();
    @Rule
    public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
    private TestableNotificationManagerService mService;
    private INotificationManager mBinderService;
    private NotificationManagerInternal mInternalService;
@@ -11632,8 +11628,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    }
    @Test
    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
    public void testGetActiveNotificationsFromListener_redactNotification() throws Exception {
        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        NotificationRecord r =
                generateNotificationRecord(mTestNotificationChannel, 0, 0);
        mService.addNotification(r);
@@ -11662,12 +11658,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    }
    @Test
    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
    public void testGetSnoozedNotificationsFromListener_redactNotification() throws Exception {
        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        NotificationRecord r =
                generateNotificationRecord(mTestNotificationChannel, 0, 0);
        mService.addNotification(r);
        mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
        when(mSnoozeHelper.getSnoozed()).thenReturn(List.of(r));
        when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
        when(mListeners.hasSensitiveContent(any())).thenReturn(true);
        StatusBarNotification redacted = generateRedactedSbn(mTestNotificationChannel, 1, 1);
@@ -11846,6 +11841,97 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertEquals(2, nru.getRankingMap().getOrderedKeys().length);
    }
    @Test
    public void testMakeRankingUpdate_redactsIfRecordSensitiveAndServiceUntrusted() {
        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
        when(mListeners.hasSensitiveContent(any())).thenReturn(true);
        NotificationRecord pkgA = new NotificationRecord(mContext,
                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
        addSmartActionsAndReplies(pkgA);
        mService.addNotification(pkgA);
        NotificationRecord pkgB = new NotificationRecord(mContext,
                generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
        addSmartActionsAndReplies(pkgB);
        mService.addNotification(pkgB);
        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(pkgA.getSbn().getKey());
        assertEquals(0, ranking.getSmartActions().size());
        assertEquals(0, ranking.getSmartReplies().size());
        NotificationListenerService.Ranking ranking2 =
                nru.getRankingMap().getRawRankingObject(pkgB.getSbn().getKey());
        assertEquals(0, ranking2.getSmartActions().size());
        assertEquals(0, ranking2.getSmartReplies().size());
    }
    @Test
    public void testMakeRankingUpdate_doestntRedactIfFlagDisabled() {
        mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
        when(mListeners.hasSensitiveContent(any())).thenReturn(true);
        NotificationRecord pkgA = new NotificationRecord(mContext,
                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
        addSmartActionsAndReplies(pkgA);
        mService.addNotification(pkgA);
        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(pkgA.getSbn().getKey());
        assertEquals(1, ranking.getSmartActions().size());
        assertEquals(1, ranking.getSmartReplies().size());
    }
    @Test
    public void testMakeRankingUpdate_doesntRedactIfNotSensitiveOrServiceTrusted() {
        mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
        NotificationRecord pkgA = new NotificationRecord(mContext,
                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
        addSmartActionsAndReplies(pkgA);
        mService.addNotification(pkgA);
        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
        when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
        when(info.isSameUser(anyInt())).thenReturn(true);
        // No sensitive content, no redaction
        when(mListeners.isUidTrusted(eq(1000))).thenReturn(false);
        when(mListeners.hasSensitiveContent(any())).thenReturn(false);
        NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
        NotificationListenerService.Ranking ranking =
                nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
        assertEquals(1, ranking.getSmartActions().size());
        assertEquals(1, ranking.getSmartReplies().size());
        // trusted listener, no redaction
        when(mListeners.isUidTrusted(eq(1000))).thenReturn(true);
        when(mListeners.hasSensitiveContent(any())).thenReturn(true);
        nru = mService.makeRankingUpdateLocked(info);
        ranking = nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
        assertEquals(1, ranking.getSmartActions().size());
        assertEquals(1, ranking.getSmartReplies().size());
    }
    private void addSmartActionsAndReplies(NotificationRecord record) {
        Bundle b = new Bundle();
        ArrayList<Notification.Action> actions = new ArrayList<>();
        actions.add(new Notification.Action(0, "", null));
        b.putParcelableArrayList(KEY_CONTEXTUAL_ACTIONS, actions);
        ArrayList<CharSequence> replies = new ArrayList<>(List.of("test"));
        b.putCharSequenceArrayList(KEY_TEXT_REPLIES, replies);
        Adjustment a = new Adjustment(record.getSbn().getPackageName(), record.getSbn().getKey(),
                b, "", record.getUserId());
        record.addAdjustment(a);
        record.applyAdjustments();
    }
    @Test
    public void testMaybeShowReviewPermissionsNotification_flagOff() {
        mService.setShowReviewPermissionsNotification(false);