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

Commit 6d2615b3 authored by Richard MacGregor's avatar Richard MacGregor
Browse files

Support hiding notifications during screenshare

Add controller to assist in hiding notifications during screensharing

Test: atest SystemUITests
Bug: 312784780
Flag: ACONFIG com.android.systemui.screenshare_notification_hiding DISABLED
Change-Id: Id53a55aa4452d840af8c9556a32b23229b4b0521
parent 28209a0c
Loading
Loading
Loading
Loading
+14 −3
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator

import android.os.UserHandle
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags.screenshareNotificationHiding
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
@@ -30,6 +31,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import dagger.Binds
import dagger.Module
@@ -55,6 +57,8 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
    private val statusBarStateController: StatusBarStateController,
    private val keyguardStateController: KeyguardStateController,
    private val selectedUserInteractor: SelectedUserInteractor,
    private val sensitiveNotificationProtectionController:
        SensitiveNotificationProtectionController,
) : Invalidator("SensitiveContentInvalidator"),
        SensitiveContentCoordinator,
        DynamicPrivacyController.Listener,
@@ -82,10 +86,13 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
            return
        }

        val isSensitiveContentProtectionActive = screenshareNotificationHiding() &&
            sensitiveNotificationProtectionController.isSensitiveStateActive
        val currentUserId = lockscreenUserManager.currentUserId
        val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
        val deviceSensitive = devicePublic &&
                !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)
        val deviceSensitive = (devicePublic &&
                !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) ||
                isSensitiveContentProtectionActive
        val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
        for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
            val notifUserId = entry.sbn.user.identifier
@@ -105,9 +112,13 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
                    else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
                }
            }

            val shouldProtectNotification = screenshareNotificationHiding() &&
                sensitiveNotificationProtectionController.shouldProtectNotification(entry)

            val needsRedaction = lockscreenUserManager.needsRedaction(entry)
            val isSensitive = userPublic && needsRedaction
            entry.setSensitive(isSensitive, deviceSensitive)
            entry.setSensitive(isSensitive || shouldProtectNotification, deviceSensitive)
        }
    }
}
+33 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N
import static com.android.app.animation.Interpolators.STANDARD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.Flags.screenshareNotificationHiding;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -130,6 +131,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
@@ -210,6 +212,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
    private final SecureSettings mSecureSettings;
    private final NotificationDismissibilityProvider mDismissibilityProvider;
    private final ActivityStarter mActivityStarter;
    private final SensitiveNotificationProtectionController
            mSensitiveNotificationProtectionController;

    private View mLongPressedView;

@@ -287,6 +291,15 @@ public class NotificationStackScrollLayoutController implements Dumpable {
                }
            };

    private final Runnable mSensitiveStateChangedListener = new Runnable() {
        @Override
        public void run() {
            // Animate false to protect against screen recording capturing content
            // during the animation
            updateSensitivenessWithAnimation(false);
        }
    };

    private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
        if (mView.isExpanded()) {
            // The bottom might change because we're using the final actual height of the view
@@ -391,8 +404,19 @@ public class NotificationStackScrollLayoutController implements Dumpable {
    }

    private void updateSensitivenessWithAnimation(boolean animate) {
        if (screenshareNotificationHiding()) {
            boolean isAnyProfilePublic = mLockscreenUserManager.isAnyProfilePublicMode();
            boolean isSensitiveContentProtectionActive =
                    mSensitiveNotificationProtectionController.isSensitiveStateActive();
            boolean isSensitive = isAnyProfilePublic || isSensitiveContentProtectionActive;

            // Only animate if in a non-sensitive state (not screen sharing)
            boolean shouldAnimate = animate && !isSensitiveContentProtectionActive;
            mView.updateSensitiveness(shouldAnimate, isSensitive);
        } else {
            mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
        }
    }

    /**
     * Set the overexpansion of the panel to be applied to the view.
@@ -697,7 +721,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
            SecureSettings secureSettings,
            NotificationDismissibilityProvider dismissibilityProvider,
            ActivityStarter activityStarter,
            SplitShadeStateController splitShadeStateController) {
            SplitShadeStateController splitShadeStateController,
            SensitiveNotificationProtectionController sensitiveNotificationProtectionController) {
        mView = view;
        mKeyguardTransitionRepo = keyguardTransitionRepo;
        mViewBinder = viewBinder;
@@ -743,6 +768,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
        mSecureSettings = secureSettings;
        mDismissibilityProvider = dismissibilityProvider;
        mActivityStarter = activityStarter;
        mSensitiveNotificationProtectionController = sensitiveNotificationProtectionController;
        mView.passSplitShadeStateController(splitShadeStateController);
        mDumpManager.registerDumpable(this);
        updateResources();
@@ -847,6 +873,11 @@ public class NotificationStackScrollLayoutController implements Dumpable {
        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
        mDeviceProvisionedListener.onDeviceProvisionedChanged();

        if (screenshareNotificationHiding()) {
            mSensitiveNotificationProtectionController
                    .registerSensitiveStateListener(mSensitiveStateChangedListener);
        }

        if (mView.isAttachedToWindow()) {
            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
        }
+41 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.statusbar.policy;

import com.android.systemui.statusbar.notification.collection.NotificationEntry;

/**
 * A controller which provides the current sensitive notification protections status as well as
 * to assist in feature usage and exemptions
 */
public interface SensitiveNotificationProtectionController {
    /**
     * Register a runnable that triggers on changes to protection state
     *
     * <p> onSensitiveStateChanged not invoked on registration
     */
    void registerSensitiveStateListener(Runnable onSensitiveStateChanged);

    /** Unregister a previously registered onSensitiveStateChanged runnable */
    void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged);

    /** Return {@code true} if device in state in which notifications should be protected */
    boolean isSensitiveStateActive();

    /** Return {@code true} when notification should be protected */
    boolean shouldProtectNotification(NotificationEntry entry);
}
+97 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.statusbar.policy;

import static com.android.systemui.Flags.screenshareNotificationHiding;

import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.util.ListenerSet;

import javax.inject.Inject;

/** Implementation of SensitiveNotificationProtectionController. **/
@SysUISingleton
public class SensitiveNotificationProtectionControllerImpl
        implements SensitiveNotificationProtectionController {
    private final MediaProjectionManager mMediaProjectionManager;
    private final ListenerSet<Runnable> mListeners = new ListenerSet<>();
    private volatile MediaProjectionInfo mProjection;

    @VisibleForTesting
    final MediaProjectionManager.Callback mMediaProjectionCallback =
            new MediaProjectionManager.Callback() {
                @Override
                public void onStart(MediaProjectionInfo info) {
                    mProjection = info;
                    mListeners.forEach(Runnable::run);
                }

                @Override
                public void onStop(MediaProjectionInfo info) {
                    mProjection = null;
                    mListeners.forEach(Runnable::run);
                }
            };

    @Inject
    public SensitiveNotificationProtectionControllerImpl(
            MediaProjectionManager mediaProjectionManager,
            @Main Handler mainHandler) {
        mMediaProjectionManager = mediaProjectionManager;

        if (screenshareNotificationHiding()) {
            mMediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler);
        }
    }

    @Override
    public void registerSensitiveStateListener(Runnable onSensitiveStateChanged) {
        mListeners.addIfAbsent(onSensitiveStateChanged);
    }

    @Override
    public void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged) {
        mListeners.remove(onSensitiveStateChanged);
    }

    @Override
    public boolean isSensitiveStateActive() {
        // TODO(b/316955558): Add disabled by developer option
        // TODO(b/316955306): Add feature exemption for sysui and bug handlers
        // TODO(b/316955346): Add feature exemption for single app screen sharing
        return mProjection != null;
    }

    @Override
    public boolean shouldProtectNotification(NotificationEntry entry) {
        if (!isSensitiveStateActive()) {
            return false;
        }

        // Exempt foreground service notifications from protection in effort to keep screen share
        // stop actions easily accessible
        // TODO(b/316955208): Exempt FGS notifications only for app that started projection
        return !entry.getSbn().getNotification().isFgsOrUij();
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -60,6 +60,8 @@ import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionControllerImpl;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.statusbar.policy.SplitShadeStateControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -144,6 +146,11 @@ public interface StatusBarPolicyModule {
    @Binds
    SecurityController provideSecurityController(SecurityControllerImpl controllerImpl);

    /** */
    @Binds
    SensitiveNotificationProtectionController provideSensitiveNotificationProtectionController(
            SensitiveNotificationProtectionControllerImpl controllerImpl);

    /** */
    @Binds
    UserInfoController provideUserInfoContrller(UserInfoControllerImpl controllerImpl);
Loading