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

Commit 04a6b7ef authored by Nate Myren's avatar Nate Myren
Browse files

Add additional logic to redacting sensitive content

Only show the redacted sensitive content view if:
1. the device is locked
2. it has been locked for over 10 minutes
3. the device hasn't been unlocked since the notification came in

Test: atest NotificationLockscreenUserManagerTest
Bug: 358403414
Fixes: 383905441
Flag: android.app.redact_sensitive_content_notifications_on_lockscreen
Change-Id: I89d4c2781d60d5e11fc7e7b94d904bea00cc7cd5
parent 6b4d2a3c
Loading
Loading
Loading
Loading
+90 −2
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.log.LogWtfHandlerRule;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
@@ -90,6 +91,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -115,6 +117,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;

import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
@@ -169,6 +172,10 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
    @Mock
    private DeviceUnlockedInteractor mDeviceUnlockedInteractor;
    @Mock
    private Lazy<KeyguardInteractor> mKeyguardInteractorLazy;
    @Mock
    private KeyguardInteractor mKeyguardInteractor;
    @Mock
    private StateFlow<DeviceUnlockStatus> mDeviceUnlockStatusStateFlow;

    private UserInfo mCurrentUser;
@@ -181,6 +188,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
    private NotificationEntry mSecondaryUserNotif;
    private NotificationEntry mWorkProfileNotif;
    private NotificationEntry mSensitiveContentNotif;
    private long mSensitiveNotifPostTime;
    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
    private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
    private final FakeExecutor mBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
@@ -246,13 +254,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
        mSensitiveContentNotif = new NotificationEntryBuilder()
                .setNotification(notifWithPrivateVisibility)
                .setUser(new UserHandle(mCurrentUser.id))
                .setPostTime(System.currentTimeMillis())
                .build();
        mSensitiveContentNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
                .setChannel(channel)
                .setSensitiveContent(true)
                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
        mSensitiveNotifPostTime = mSensitiveContentNotif.getSbn().getPostTime();
        when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif);

        when(mKeyguardInteractorLazy.get()).thenReturn(mKeyguardInteractor);
        when(mKeyguardInteractor.isKeyguardDismissible())
                .thenReturn(mock(StateFlow.class));
        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
        mLockscreenUserManager.setUpWithPresenter(mPresenter);

@@ -504,11 +516,85 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
    }

    @Test
    @EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
    public void testHasSensitiveContent_notRedactedIfNotLocked() {
        // Allow private notifications for this user
        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                mCurrentUser.id);
        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
        // Claim the device was last locked 1 day ago
        mLockscreenUserManager.mLastLockTime
                .set(mSensitiveNotifPostTime - TimeUnit.DAYS.toMillis(1));
        // Device is not currently locked
        when(mKeyguardManager.isDeviceLocked()).thenReturn(false);

        // Sensitive Content notifications are always redacted
        assertEquals(REDACTION_TYPE_NONE,
                mLockscreenUserManager.getRedactionType(mSensitiveContentNotif));
    }

    @Test
    @EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
    public void testHasSensitiveContent_notRedactedIfUnlockedSinceReceipt() {
        // Allow private notifications for this user
        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                mCurrentUser.id);
        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
        // Device was locked after this notification arrived
        mLockscreenUserManager.mLastLockTime
                .set(mSensitiveNotifPostTime + TimeUnit.DAYS.toMillis(1));

        // Sensitive Content notifications are always redacted
        assertEquals(REDACTION_TYPE_NONE,
                mLockscreenUserManager.getRedactionType(mSensitiveContentNotif));
    }

    @Test
    @EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
    public void testHasSensitiveContent_notRedactedIfNotLockedForLongEnough() {
        // Allow private notifications for this user
        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                mCurrentUser.id);
        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
        // Device has been locked for 1 second before the notification came in, which is too short
        mLockscreenUserManager.mLastLockTime
                .set(mSensitiveNotifPostTime - TimeUnit.SECONDS.toMillis(1));
        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);

        // Sensitive Content notifications are always redacted
        assertEquals(REDACTION_TYPE_NONE,
                mLockscreenUserManager.getRedactionType(mSensitiveContentNotif));
    }

    @Test
    @DisableFlags(LockscreenOtpRedaction.FLAG_NAME)
    public void testHasSensitiveContent_notRedactedFlagDisabled() {
        // Allow private notifications for this user
        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                mCurrentUser.id);
        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
        // Claim the device was last locked 1 day ago
        mLockscreenUserManager.mLastLockTime
                .set(mSensitiveNotifPostTime - TimeUnit.DAYS.toMillis(1));
        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);

        // Sensitive Content notifications are always redacted
        assertEquals(REDACTION_TYPE_NONE,
                mLockscreenUserManager.getRedactionType(mSensitiveContentNotif));
    }

    @Test
    @EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
    public void testHasSensitiveContent_redacted() {
        // Allow private notifications for this user
        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                mCurrentUser.id);
        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
        // Claim the device was last unlocked 1 day ago
        mLockscreenUserManager.mLastLockTime
                .set(mSensitiveNotifPostTime - TimeUnit.DAYS.toMillis(1));

        // Sensitive Content notifications are always redacted
        assertEquals(REDACTION_TYPE_SENSITIVE_CONTENT,
@@ -1066,7 +1152,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
                    mock(DumpManager.class),
                    mock(LockPatternUtils.class),
                    mFakeFeatureFlags,
                    mDeviceUnlockedInteractorLazy
                    mDeviceUnlockedInteractorLazy,
                    mKeyguardInteractorLazy,
                    null //CoroutineScope
            );
        }

+66 −5
Original line number Diff line number Diff line
@@ -61,11 +61,13 @@ import com.android.systemui.Dumpable;
import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
@@ -78,6 +80,7 @@ import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedac
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.kotlin.JavaAdapterKt;
import com.android.systemui.util.settings.SecureSettings;

import dagger.Lazy;
@@ -88,9 +91,13 @@ import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import javax.inject.Inject;

import kotlinx.coroutines.CoroutineScope;

/**
 * Handles keeping track of the current user, profiles, and various things related to hiding
 * contents, redacting notifications, and the lockscreen.
@@ -111,6 +118,9 @@ public class NotificationLockscreenUserManagerImpl implements
    private static final Uri SHOW_PRIVATE_LOCKSCREEN =
            Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);

    private static final long LOCK_TIME_FOR_SENSITIVE_REDACTION_MS =
            TimeUnit.MINUTES.toMillis(10);

    private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;
    private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;
    private final DevicePolicyManager mDevicePolicyManager;
@@ -284,7 +294,12 @@ public class NotificationLockscreenUserManagerImpl implements
    protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
    protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>();

    // The last lock time. Uses currentTimeMillis
    @VisibleForTesting
    protected final AtomicLong mLastLockTime = new AtomicLong(-1);

    protected int mCurrentUserId = 0;

    protected NotificationPresenter mPresenter;
    protected ContentObserver mLockscreenSettingsObserver;
    protected ContentObserver mSettingsObserver;
@@ -311,7 +326,10 @@ public class NotificationLockscreenUserManagerImpl implements
            DumpManager dumpManager,
            LockPatternUtils lockPatternUtils,
            FeatureFlagsClassic featureFlags,
            Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy) {
            Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
            Lazy<KeyguardInteractor> keyguardInteractor,
            @Application CoroutineScope coroutineScope
    ) {
        mContext = context;
        mMainExecutor = mainExecutor;
        mBackgroundExecutor = backgroundExecutor;
@@ -341,6 +359,18 @@ public class NotificationLockscreenUserManagerImpl implements
        if (keyguardPrivateNotifications()) {
            init();
        }

        // To avoid dependency injection cycle, finish constructing this object before using the
        // KeyguardInteractor. The CoroutineScope will only be null in tests.
        if (LockscreenOtpRedaction.isEnabled() && coroutineScope != null) {
            mMainExecutor.execute(() -> JavaAdapterKt.collectFlow(coroutineScope,
                    keyguardInteractor.get().isKeyguardDismissible(),
                    unlocked -> {
                        if (!unlocked) {
                            mLastLockTime.set(System.currentTimeMillis());
                        }
                    }));
        }
    }

    public void setUpWithPresenter(NotificationPresenter presenter) {
@@ -667,8 +697,6 @@ public class NotificationLockscreenUserManagerImpl implements
                !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
        boolean isNotifForManagedProfile = mCurrentManagedProfiles.contains(userId);
        boolean isNotifUserRedacted = !userAllowsPrivateNotificationsInPublic(userId);
        boolean isNotifSensitive = LockscreenOtpRedaction.isEnabled()
                && ent.getRanking() != null && ent.getRanking().hasSensitiveContent();

        // redact notifications if the current user is redacting notifications or the notification
        // contains sensitive content. However if the notification is associated with a managed
@@ -689,12 +717,45 @@ public class NotificationLockscreenUserManagerImpl implements
        if (keyguardPrivateNotifications() && !mKeyguardAllowingNotifications) {
            return REDACTION_TYPE_PUBLIC;
        }
        if (isNotifSensitive) {

        if (shouldShowSensitiveContentRedactedView(ent)) {
            return REDACTION_TYPE_SENSITIVE_CONTENT;
        }
        return REDACTION_TYPE_NONE;
    }

    /*
     * We show the sensitive content redaction view if
     * 1. The feature is enabled
     * 2. The device is locked
     * 3. The notification has the `hasSensitiveContent` ranking variable set to true
     * 4. The device has been locked for at least LOCK_TIME_FOR_SENSITIVE_REDACTION_MS
     * 5. The notification arrived since the last lock time
     */
    private boolean shouldShowSensitiveContentRedactedView(NotificationEntry ent) {
        if (!LockscreenOtpRedaction.isEnabled()) {
            return false;
        }

        if (!mKeyguardManager.isDeviceLocked()) {
            return false;
        }

        if (ent.getRanking() == null || !ent.getRanking().hasSensitiveContent()) {
            return false;
        }

        long lastLockedTime = mLastLockTime.get();
        if (ent.getSbn().getPostTime() < lastLockedTime) {
            return false;
        }

        if ((System.currentTimeMillis() - lastLockedTime) < LOCK_TIME_FOR_SENSITIVE_REDACTION_MS) {
            return false;
        }
        return true;
    }

    private boolean packageHasVisibilityOverride(String key) {
        if (mCommonNotifCollectionLazy.get() == null) {
            Log.wtf(TAG, "mEntryManager was null!", new Throwable());