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

Commit 6f0a62a4 authored by Selim Cinek's avatar Selim Cinek
Browse files

Implemented dynamic privacy for redacted lockscreens

Users with redacted lock screens now dynamically are able
to see the contents of their notifications whenever
they are authenticated.

Bug: 128037042
Test: atest SystemUITests
Change-Id: If289a8a9ddb300e1398f9e715511beb5a5fd5c5d
parent 211ddf70
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -33,6 +33,14 @@ public interface NotificationLockscreenUserManager {
     */
    boolean isLockscreenPublicMode(int userId);

    /**
     * Does a user require a separate work challenge? If so, the unlock mechanism is decoupled from
     * the current user and has to be solved separately.
     */
    default boolean needsSeparateWorkChallenge(int userId) {
        return false;
    }

    void setUpWithPresenter(NotificationPresenter presenter);

    int getCurrentUserId();
+11 −2
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ public class NotificationLockscreenUserManagerImpl implements

    private final DevicePolicyManager mDevicePolicyManager;
    private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
    private final SparseBooleanArray mUsersWithSeperateWorkChallenge = new SparseBooleanArray();
    private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
    private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
    private final UserManager mUserManager;
@@ -394,6 +395,11 @@ public class NotificationLockscreenUserManagerImpl implements
        return mLockscreenPublicMode.get(userId, false);
    }

    @Override
    public boolean needsSeparateWorkChallenge(int userId) {
        return mUsersWithSeperateWorkChallenge.get(userId, false);
    }

    /**
     * Has the given user chosen to allow notifications to be shown even when the lockscreen is in
     * "public" (secure & locked) mode?
@@ -493,20 +499,23 @@ public class NotificationLockscreenUserManagerImpl implements
        //   - device keyguard is shown in secure mode;
        //   - profile is locked with a work challenge.
        SparseArray<UserInfo> currentProfiles = getCurrentProfiles();
        mUsersWithSeperateWorkChallenge.clear();
        for (int i = currentProfiles.size() - 1; i >= 0; i--) {
            final int userId = currentProfiles.valueAt(i).id;
            boolean isProfilePublic = devicePublic;
            boolean needsSeparateChallenge = mLockPatternUtils.isSeparateProfileChallengeEnabled(
                    userId);
            if (!devicePublic && userId != getCurrentUserId()) {
                // We can't rely on KeyguardManager#isDeviceLocked() for unified profile challenge
                // due to a race condition where this code could be called before
                // TrustManagerService updates its internal records, resulting in an incorrect
                // state being cached in mLockscreenPublicMode. (b/35951989)
                if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
                        && isSecure(userId)) {
                if (needsSeparateChallenge && isSecure(userId)) {
                    isProfilePublic = mKeyguardManager.isDeviceLocked(userId);
                }
            }
            setLockscreenPublicMode(isProfilePublic, userId);
            mUsersWithSeperateWorkChallenge.put(userId, needsSeparateChallenge);
        }
    }

+22 −6
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.content.res.Resources;
import android.os.Trace;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -26,6 +27,7 @@ import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -33,6 +35,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.UnlockMethodCache;

import java.util.ArrayList;
import java.util.HashMap;
@@ -52,7 +55,7 @@ import dagger.Lazy;
 * notifications that might affect their display.
 */
@Singleton
public class NotificationViewHierarchyManager {
public class NotificationViewHierarchyManager implements DynamicPrivacyController.Listener {
    private static final String TAG = "NotificationViewHierarchyManager";

    //TODO: change this top <Entry, List<Entry>>?
@@ -76,6 +79,7 @@ public class NotificationViewHierarchyManager {
     */
    private final boolean mAlwaysExpandNonGroupedNotification;
    private final BubbleData mBubbleData;
    private final DynamicPrivacyController mDynamicPrivacyController;

    private NotificationPresenter mPresenter;
    private NotificationListContainer mListContainer;
@@ -88,7 +92,8 @@ public class NotificationViewHierarchyManager {
            StatusBarStateController statusBarStateController,
            NotificationEntryManager notificationEntryManager,
            Lazy<ShadeController> shadeController,
            BubbleData bubbleData) {
            BubbleData bubbleData,
            DynamicPrivacyController privacyController) {
        mLockscreenUserManager = notificationLockscreenUserManager;
        mGroupManager = groupManager;
        mVisualStabilityManager = visualStabilityManager;
@@ -99,6 +104,8 @@ public class NotificationViewHierarchyManager {
        mAlwaysExpandNonGroupedNotification =
                res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
        mBubbleData = bubbleData;
        mDynamicPrivacyController = privacyController;
        privacyController.addListener(this);
    }

    public void setUpWithPresenter(NotificationPresenter presenter,
@@ -130,15 +137,20 @@ public class NotificationViewHierarchyManager {
            // Display public version of the notification if we need to redact.
            // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
            // We can probably move some of this code there.
            boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
                    mLockscreenUserManager.getCurrentUserId());
            int currentUserId = mLockscreenUserManager.getCurrentUserId();
            boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(currentUserId);
            boolean userPublic = devicePublic
                    || mLockscreenUserManager.isLockscreenPublicMode(userId);
            if (userPublic && mDynamicPrivacyController.isDynamicallyUnlocked()
                    && (userId == currentUserId || userId == UserHandle.USER_ALL
                    || !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) {
                userPublic = false;
            }
            boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
            boolean sensitive = userPublic && needsRedaction;
            boolean deviceSensitive = devicePublic
                    && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
                    mLockscreenUserManager.getCurrentUserId());
                    currentUserId);
            ent.getRow().setSensitive(sensitive, deviceSensitive);
            ent.getRow().setNeedsRedaction(needsRedaction);
            if (mGroupManager.isChildInGroupWithSummary(ent.notification)) {
@@ -153,7 +165,6 @@ public class NotificationViewHierarchyManager {
            } else {
                toShow.add(ent.getRow());
            }

        }

        ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>();
@@ -406,4 +417,9 @@ public class NotificationViewHierarchyManager {
        Trace.endSection();
        Trace.endSection();
    }

    @Override
    public void onDynamicPrivacyChanged() {
        updateNotificationViews();
    }
}
+90 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.statusbar.notification;

import android.content.Context;
import android.util.ArraySet;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.phone.UnlockMethodCache;

import javax.inject.Inject;
import javax.inject.Singleton;

/**
 * A controller which dynamically controls the visibility of Notification content
 */
@Singleton
public class DynamicPrivacyController implements UnlockMethodCache.OnUnlockMethodChangedListener {

    private final UnlockMethodCache mUnlockMethodCache;
    private final NotificationLockscreenUserManager mLockscreenUserManager;
    private ArraySet<Listener> mListeners = new ArraySet<>();

    private boolean mLastDynamicUnlocked;
    private boolean mCacheInvalid;

    @Inject
    DynamicPrivacyController(Context context,
            NotificationLockscreenUserManager notificationLockscreenUserManager) {
        this(notificationLockscreenUserManager, UnlockMethodCache.getInstance(context));
    }

    @VisibleForTesting
    DynamicPrivacyController(NotificationLockscreenUserManager notificationLockscreenUserManager,
            UnlockMethodCache unlockMethodCache) {
        mLockscreenUserManager = notificationLockscreenUserManager;
        mUnlockMethodCache = unlockMethodCache;
        mUnlockMethodCache.addListener(this);
        mLastDynamicUnlocked = isDynamicallyUnlocked();
    }

    @Override
    public void onUnlockMethodStateChanged() {
        if (isDynamicPrivacyEnabled()) {
            // We only want to notify our listeners if dynamic privacy is actually active
            boolean dynamicallyUnlocked = isDynamicallyUnlocked();
            if (dynamicallyUnlocked != mLastDynamicUnlocked || mCacheInvalid) {
                mLastDynamicUnlocked = dynamicallyUnlocked;
                for (Listener listener : mListeners) {
                    listener.onDynamicPrivacyChanged();
                }
            }
            mCacheInvalid = false;
        } else {
            mCacheInvalid = true;
        }
    }

    private boolean isDynamicPrivacyEnabled() {
        return !mLockscreenUserManager.shouldHideNotifications(
                mLockscreenUserManager.getCurrentUserId());
    }

    public boolean isDynamicallyUnlocked() {
        return mUnlockMethodCache.canSkipBouncer() && isDynamicPrivacyEnabled();
    }

    public void addListener(Listener listener) {
        mListeners.add(listener);
    }

    public interface Listener {
        void onDynamicPrivacyChanged();
    }
}
 No newline at end of file
+17 −3
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -153,7 +154,8 @@ import javax.inject.Named;
 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
 */
public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter,
        NotificationListContainer, ConfigurationListener, Dumpable {
        NotificationListContainer, ConfigurationListener, Dumpable,
        DynamicPrivacyController.Listener {

    public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
    private static final String TAG = "StackScroller";
@@ -498,6 +500,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
     * If the {@link NotificationShelf} should be visible when dark.
     */
    private boolean mShowDarkShelf;
    private boolean mAnimateBottomOnLayout;

    @Inject
    public NotificationStackScrollLayout(
@@ -505,7 +508,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
            AttributeSet attrs,
            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
            NotificationRoundnessManager notificationRoundnessManager,
            AmbientPulseManager ambientPulseManager) {
            AmbientPulseManager ambientPulseManager,
            DynamicPrivacyController dynamicPrivacyController) {
        super(context, attrs, 0, 0);
        Resources res = getResources();

@@ -576,6 +580,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
                }
            }
        });
        dynamicPrivacyController.addListener(this);
    }

    private void updateDismissRtlSetting(boolean dismissRtl) {
@@ -3165,7 +3170,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd

        if (mAnimationsEnabled && mIsExpanded) {
            mAnimateNextBackgroundTop = firstChild != previousFirstChild;
            mAnimateNextBackgroundBottom = lastChild != previousLastChild;
            mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout;
            mAnimateNextSectionBoundsChange = sectionViewsChanged;
        } else {
            mAnimateNextBackgroundTop = false;
@@ -3174,6 +3179,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
        }
        mAmbientState.setLastVisibleBackgroundChild(lastChild);
        mRoundnessManager.updateRoundedChildren(mSections);
        mAnimateBottomOnLayout = false;
        invalidate();
    }

@@ -5714,6 +5720,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
        }
    }

    @Override
    public void onDynamicPrivacyChanged() {
        if (mIsExpanded) {
            // The bottom might change because we're using the final actual height of the view
            mAnimateBottomOnLayout = true;
        }
    }

    /**
     * A listener that is notified when the empty space below the notifications is clicked on
     */
Loading