Merge "Update deprecated MutableTextureState Vulkan constructor call" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 0e413c4..1091706 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -25478,34 +25478,34 @@
field public short preset;
}
- public class Virtualizer extends android.media.audiofx.AudioEffect {
- ctor public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
- method public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public boolean getStrengthSupported();
- method public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener);
- method public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- field public static final int PARAM_STRENGTH = 1; // 0x1
- field public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0
- field public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1
- field public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2
- field public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0
- field public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3
+ @Deprecated public class Virtualizer extends android.media.audiofx.AudioEffect {
+ ctor @Deprecated public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
+ method @Deprecated public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public boolean getStrengthSupported();
+ method @Deprecated public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener);
+ method @Deprecated public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ field @Deprecated public static final int PARAM_STRENGTH = 1; // 0x1
+ field @Deprecated public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0
+ field @Deprecated public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1
+ field @Deprecated public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2
+ field @Deprecated public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0
+ field @Deprecated public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3
}
- public static interface Virtualizer.OnParameterChangeListener {
- method public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short);
+ @Deprecated public static interface Virtualizer.OnParameterChangeListener {
+ method @Deprecated public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short);
}
- public static class Virtualizer.Settings {
- ctor public Virtualizer.Settings();
- ctor public Virtualizer.Settings(String);
- field public short strength;
+ @Deprecated public static class Virtualizer.Settings {
+ ctor @Deprecated public Virtualizer.Settings();
+ ctor @Deprecated public Virtualizer.Settings(String);
+ field @Deprecated public short strength;
}
public class Visualizer {
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 39b6aeb..8c70501 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -86,3 +86,11 @@
description: "This flag is used to enabled the Wallet Role for all users on the device"
bug: "283989236"
}
+
+flag {
+ name: "signature_permission_allowlist_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Enable signature permission allowlist"
+ bug: "308573169"
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e0bda91..3c36227 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -26,7 +26,6 @@
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
-import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -1012,10 +1011,8 @@
// Used to check if there were any view invalidations in
// the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
private boolean mHasInvalidation = false;
- // Used to check if it is in the frame rate boosting period.
+ // Used to check if it is in the touch boosting period.
private boolean mIsFrameRateBoosting = false;
- // Used to check if it is in touch boosting period.
- private boolean mIsTouchBoosting = false;
// Used to check if there is a message in the message queue
// for idleness handling.
private boolean mHasIdledMessage = false;
@@ -6424,12 +6421,11 @@
* Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
*/
mIsFrameRateBoosting = false;
- mIsTouchBoosting = false;
setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
mLastPreferredFrameRateCategory));
break;
case MSG_CHECK_INVALIDATION_IDLE:
- if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
+ if (!mHasInvalidation && !mIsFrameRateBoosting) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mHasIdledMessage = false;
@@ -7452,7 +7448,7 @@
// For the variable refresh rate project
if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
// set the frame rate to the maximum value.
- mIsTouchBoosting = true;
+ mIsFrameRateBoosting = true;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
}
/**
@@ -12204,16 +12200,8 @@
return;
}
- int frameRateCategory = mIsTouchBoosting
- ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory;
-
- // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
- // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
- // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
- // (e.g., Window Initialization).
- if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
- frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
- }
+ int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning
+ ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
try {
if (mLastPreferredFrameRateCategory != frameRateCategory) {
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cfbda84..cf3eb12 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -19,7 +19,6 @@
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
-import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -576,13 +575,8 @@
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH_HINT);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
diff --git a/libs/hwui/jni/PathMeasure.cpp b/libs/hwui/jni/PathMeasure.cpp
index acf893e..79acb6c 100644
--- a/libs/hwui/jni/PathMeasure.cpp
+++ b/libs/hwui/jni/PathMeasure.cpp
@@ -17,7 +17,11 @@
#include "GraphicsJNI.h"
+#include "SkMatrix.h"
+#include "SkPath.h"
#include "SkPathMeasure.h"
+#include "SkPoint.h"
+#include "SkScalar.h"
/* We declare an explicit pair, so that we don't have to rely on the java
client to be sure not to edit the path while we have an active measure
diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java
index 74b6fc1..71147f4 100644
--- a/media/java/android/media/audiofx/Virtualizer.java
+++ b/media/java/android/media/audiofx/Virtualizer.java
@@ -46,6 +46,11 @@
* <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
* <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling
* audio effects.
+ *
+ * @deprecated use the {@link android.media.Spatializer} class to query the capabilities of the
+ * platform with regards to spatialization, a different name for audio channel virtualization,
+ * and the {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)} to
+ * characterize how you want your content to be played when spatialization is supported.
*/
public class Virtualizer extends AudioEffect {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3236130..2c35c77 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -89,6 +89,13 @@
}
flag {
+ name: "notification_avalanche_suppression"
+ namespace: "systemui"
+ description: "After notification avalanche floodgate event, suppress HUNs completely."
+ bug: "321089634"
+}
+
+flag {
name: "notification_background_tint_optimization"
namespace: "systemui"
description: "Re-enable the codepath that removed tinting of notifications when the"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 6f62afc..dc8b97a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -139,6 +139,18 @@
}
@Test
+ fun topClippingBounds() =
+ testScope.runTest {
+ assertThat(underTest.topClippingBounds.value).isNull()
+
+ underTest.topClippingBounds.value = 50
+ assertThat(underTest.topClippingBounds.value).isEqualTo(50)
+
+ underTest.topClippingBounds.value = 500
+ assertThat(underTest.topClippingBounds.value).isEqualTo(500)
+ }
+
+ @Test
fun clockPosition() =
testScope.runTest {
assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 1eaa060..cceb767 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -54,13 +55,12 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
private val screenOffAnimationController = kosmos.screenOffAnimationController
private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
private val dozeParameters = kosmos.dozeParameters
- private val underTest by lazy {
- kosmos.keyguardRootViewModel
- }
+ private val underTest by lazy { kosmos.keyguardRootViewModel }
@Before
fun setUp() {
@@ -207,6 +207,19 @@
}
@Test
+ fun topClippingBounds() =
+ testScope.runTest {
+ val topClippingBounds by collectLastValue(underTest.topClippingBounds)
+ assertThat(topClippingBounds).isNull()
+
+ keyguardRepository.topClippingBounds.value = 50
+ assertThat(topClippingBounds).isEqualTo(50)
+
+ keyguardRepository.topClippingBounds.value = 1000
+ assertThat(topClippingBounds).isEqualTo(1000)
+ }
+
+ @Test
fun alpha_glanceableHubOpen_isZero() =
testScope.runTest {
val alpha by collectLastValue(underTest.alpha)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 033f93b..ad30317 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -477,9 +477,13 @@
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardClockSwitch:");
pw.println(" mSmallClockFrame = " + mSmallClockFrame);
- pw.println(" mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha());
+ if (mSmallClockFrame != null) {
+ pw.println(" mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha());
+ }
pw.println(" mLargeClockFrame = " + mLargeClockFrame);
- pw.println(" mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha());
+ if (mLargeClockFrame != null) {
+ pw.println(" mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha());
+ }
pw.println(" mStatusArea = " + mStatusArea);
pw.println(" mDisplayedClockSize = " + mDisplayedClockSize);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index d012d24..1437194 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -132,6 +132,9 @@
*/
val isDozing: StateFlow<Boolean>
+ /** Keyguard can be clipped at the top as the shade is dragged */
+ val topClippingBounds: MutableStateFlow<Int?>
+
/**
* Observable for whether the device is dreaming.
*
@@ -326,6 +329,8 @@
private val _clockShouldBeCentered = MutableStateFlow(true)
override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
+ override val topClippingBounds = MutableStateFlow<Int?>(null)
+
override val isKeyguardShowing: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6170356..91747e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -171,6 +171,14 @@
/** Whether the keyguard is going away. */
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
+ /** Keyguard can be clipped at the top as the shade is dragged */
+ val topClippingBounds: Flow<Int?> =
+ combine(configurationInteractor.onAnyConfigurationChange, repository.topClippingBounds) {
+ _,
+ topClippingBounds ->
+ topClippingBounds
+ }
+
/** Last point that [KeyguardRootView] view was tapped */
val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
@@ -328,6 +336,10 @@
repository.keyguardDoneAnimationsFinished()
}
+ fun setTopClippingBounds(top: Int?) {
+ repository.topClippingBounds.value = top
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 2aebd99..48092c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -21,6 +21,7 @@
import android.annotation.DrawableRes
import android.annotation.SuppressLint
import android.graphics.Point
+import android.graphics.Rect
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.View.OnLayoutChangeListener
@@ -158,6 +159,23 @@
}
launch {
+ val clipBounds = Rect()
+ viewModel.topClippingBounds.collect { clipTop ->
+ if (clipTop == null) {
+ view.setClipBounds(null)
+ } else {
+ clipBounds.apply {
+ top = clipTop
+ left = view.getLeft()
+ right = view.getRight()
+ bottom = view.getBottom()
+ }
+ view.setClipBounds(clipBounds)
+ }
+ }
+ }
+
+ launch {
viewModel.lockscreenStateAlpha.collect { alpha ->
childViews[statusViewId]?.alpha = alpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 5d36da9..ea66702 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -80,6 +80,12 @@
val notificationBounds: StateFlow<NotificationContainerBounds> =
keyguardInteractor.notificationContainerBounds
+ /**
+ * The keyguard root view can be clipped as the shade is pulled down, typically only for
+ * non-split shade cases.
+ */
+ val topClippingBounds: Flow<Int?> = keyguardInteractor.topClippingBounds
+
/** An observable for the alpha level for the entire keyguard root view. */
val alpha: Flow<Float> =
merge(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 46806e6..54b6ad7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -790,7 +790,7 @@
/** Mutates the HeadsUp state of notifications. */
private interface HunMutator {
- fun updateNotification(key: String, alert: Boolean)
+ fun updateNotification(key: String, shouldHeadsUpAgain: Boolean)
fun removeNotification(key: String, releaseImmediately: Boolean)
}
@@ -801,8 +801,8 @@
private class HunMutatorImpl(private val headsUpManager: HeadsUpManager) : HunMutator {
private val deferred = mutableListOf<Pair<String, Boolean>>()
- override fun updateNotification(key: String, alert: Boolean) {
- headsUpManager.updateNotification(key, alert)
+ override fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) {
+ headsUpManager.updateNotification(key, shouldHeadsUpAgain)
}
override fun removeNotification(key: String, releaseImmediately: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index 380cdad..ae4ba27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -18,6 +18,7 @@
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.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 @@
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 @@
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 @@
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)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt
new file mode 100644
index 0000000..a21dd9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification avalanche suppression flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationAvalancheSuppression {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationAvalancheSuppression()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 6a66bb7..9a8cc0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -22,6 +22,7 @@
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;
@@ -135,6 +136,7 @@
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;
@@ -218,6 +220,8 @@
private final SecureSettings mSecureSettings;
private final NotificationDismissibilityProvider mDismissibilityProvider;
private final ActivityStarter mActivityStarter;
+ private final SensitiveNotificationProtectionController
+ mSensitiveNotificationProtectionController;
private View mLongPressedView;
@@ -295,6 +299,15 @@
}
};
+ 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
@@ -399,7 +412,20 @@
}
private void updateSensitivenessWithAnimation(boolean animate) {
- mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
+ Trace.beginSection("NSSLC.updateSensitivenessWithAnimation");
+ 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());
+ }
+ Trace.endSection();
}
/**
@@ -708,7 +734,8 @@
SecureSettings secureSettings,
NotificationDismissibilityProvider dismissibilityProvider,
ActivityStarter activityStarter,
- SplitShadeStateController splitShadeStateController) {
+ SplitShadeStateController splitShadeStateController,
+ SensitiveNotificationProtectionController sensitiveNotificationProtectionController) {
mView = view;
mKeyguardTransitionRepo = keyguardTransitionRepo;
mViewBinder = viewBinder;
@@ -756,6 +783,7 @@
mSecureSettings = secureSettings;
mDismissibilityProvider = dismissibilityProvider;
mActivityStarter = activityStarter;
+ mSensitiveNotificationProtectionController = sensitiveNotificationProtectionController;
mView.passSplitShadeStateController(splitShadeStateController);
mDumpManager.registerDumpable(this);
updateResources();
@@ -860,6 +888,11 @@
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
mDeviceProvisionedListener.onDeviceProvisionedChanged();
+ if (screenshareNotificationHiding()) {
+ mSensitiveNotificationProtectionController
+ .registerSensitiveStateListener(mSensitiveStateChangedListener);
+ }
+
if (mView.isAttachedToWindow()) {
mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index ae04eaf..459b368 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -60,6 +60,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.ScrimAlpha;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -217,6 +218,7 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final KeyguardInteractor mKeyguardInteractor;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
@@ -311,6 +313,7 @@
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel,
KeyguardTransitionInteractor keyguardTransitionInteractor,
+ KeyguardInteractor keyguardInteractor,
WallpaperRepository wallpaperRepository,
@Main CoroutineDispatcher mainDispatcher,
LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
@@ -357,6 +360,7 @@
mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mKeyguardInteractor = keyguardInteractor;
mWallpaperRepository = wallpaperRepository;
mMainDispatcher = mainDispatcher;
}
@@ -759,7 +763,9 @@
// see: b/186644628
mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom);
mScrimBehind.setBottomEdgePosition((int) top);
+ mKeyguardInteractor.setTopClippingBounds((int) top);
} else {
+ mKeyguardInteractor.setTopClippingBounds(null);
mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 1528c9b..1414150 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -159,7 +159,7 @@
public void showNotification(@NonNull NotificationEntry entry) {
mLogger.logShowNotification(entry);
addEntry(entry);
- updateNotification(entry.getKey(), true /* show */);
+ updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
entry.setInterruption();
}
@@ -190,12 +190,12 @@
/**
* Called when the notification state has been updated.
* @param key the key of the entry that was updated
- * @param show whether the notification should show again and force reevaluation of
- * removal time
+ * @param shouldHeadsUpAgain whether the notification should show again and force reevaluation
+ * of removal time
*/
- public void updateNotification(@NonNull String key, boolean show) {
+ public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
- mLogger.logUpdateNotification(key, show, headsUpEntry != null);
+ mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
if (headsUpEntry == null) {
// the entry was released before this update (i.e by a listener) This can happen
// with the groupmanager
@@ -204,7 +204,7 @@
headsUpEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- if (show) {
+ if (shouldHeadsUpAgain) {
headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
if (headsUpEntry != null) {
setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
index b8c7e20..a7352be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
@@ -182,7 +182,7 @@
*/
fun unpinAll(userUnPinned: Boolean)
- fun updateNotification(key: String, alert: Boolean)
+ fun updateNotification(key: String, shouldHeadsUpAgain: Boolean)
}
/** Sets the animation state of the HeadsUpManager. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java
new file mode 100644
index 0000000..970cc75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java
@@ -0,0 +1,41 @@
+/*
+ * 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);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
new file mode 100644
index 0000000..3c4ca44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -0,0 +1,104 @@
+/*
+ * 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 android.os.Trace;
+
+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) {
+ Trace.beginSection(
+ "SNPC.onProjectionStart");
+ mProjection = info;
+ mListeners.forEach(Runnable::run);
+ Trace.endSection();
+ }
+
+ @Override
+ public void onStop(MediaProjectionInfo info) {
+ Trace.beginSection(
+ "SNPC.onProjectionStop");
+ mProjection = null;
+ mListeners.forEach(Runnable::run);
+ Trace.endSection();
+ }
+ };
+
+ @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();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 3304b98..15200bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -60,6 +60,8 @@
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;
@@ -146,6 +148,11 @@
/** */
@Binds
+ SensitiveNotificationProtectionController provideSensitiveNotificationProtectionController(
+ SensitiveNotificationProtectionControllerImpl controllerImpl);
+
+ /** */
+ @Binds
UserInfoController provideUserInfoContrller(UserInfoControllerImpl controllerImpl);
/** */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index df547ae..350ed2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -17,9 +17,11 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -33,6 +35,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -55,28 +58,31 @@
val statusBarStateController: StatusBarStateController = mock()
val keyguardStateController: KeyguardStateController = mock()
val mSelectedUserInteractor: SelectedUserInteractor = mock()
+ val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController =
+ mock()
val coordinator: SensitiveContentCoordinator =
- DaggerTestSensitiveContentCoordinatorComponent
- .factory()
- .create(
- dynamicPrivacyController,
- lockscreenUserManager,
- keyguardUpdateMonitor,
- statusBarStateController,
- keyguardStateController,
- mSelectedUserInteractor)
- .coordinator
+ DaggerTestSensitiveContentCoordinatorComponent.factory()
+ .create(
+ dynamicPrivacyController,
+ lockscreenUserManager,
+ keyguardUpdateMonitor,
+ statusBarStateController,
+ keyguardStateController,
+ mSelectedUserInteractor,
+ sensitiveNotificationProtectionController
+ )
+ .coordinator
@Test
fun onDynamicPrivacyChanged_invokeInvalidationListener() {
coordinator.attach(pipeline)
- val invalidator = withArgCaptor<Invalidator> {
- verify(pipeline).addPreRenderInvalidator(capture())
- }
- val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> {
- verify(dynamicPrivacyController).addListener(capture())
- }
+ val invalidator =
+ withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) }
+ val dynamicPrivacyListener =
+ withArgCaptor<DynamicPrivacyController.Listener> {
+ verify(dynamicPrivacyController).addListener(capture())
+ }
val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>()
invalidator.setInvalidationListener(invalidationListener)
@@ -89,9 +95,10 @@
@Test
fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
@@ -105,11 +112,59 @@
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, false)
+ }
+
+ @Test
fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
@@ -123,11 +178,59 @@
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, true)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, true)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, false)
+ }
+
+ @Test
fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -141,11 +244,59 @@
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, false)
+ }
+
+ @Test
fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -159,11 +310,61 @@
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -177,11 +378,59 @@
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, true)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, true)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -195,18 +444,66 @@
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ val entry = fakeNotification(1, true)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ val entry = fakeNotification(1, true)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
-
val entry = fakeNotification(2, true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -215,11 +512,62 @@
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
+ val entry = fakeNotification(2, true)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
+ val entry = fakeNotification(2, true)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -227,9 +575,11 @@
whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
- .thenReturn(true)
-
+ .thenReturn(true)
val entry = fakeNotification(2, true)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+ whenever(sensitiveNotificationProtectionController.shouldProtectNotification(any()))
+ .thenReturn(true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -237,15 +587,11 @@
}
private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
- val mockUserHandle = mock<UserHandle>().apply {
- whenever(identifier).thenReturn(notifUserId)
- }
- val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply {
- whenever(user).thenReturn(mockUserHandle)
- }
- val mockEntry = mock<NotificationEntry>().apply {
- whenever(sbn).thenReturn(mockSbn)
- }
+ val mockUserHandle =
+ mock<UserHandle>().apply { whenever(identifier).thenReturn(notifUserId) }
+ val mockSbn: StatusBarNotification =
+ mock<StatusBarNotification>().apply { whenever(user).thenReturn(mockUserHandle) }
+ val mockEntry = mock<NotificationEntry>().apply { whenever(sbn).thenReturn(mockSbn) }
whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction)
whenever(mockEntry.rowExists()).thenReturn(true)
return object : ListEntry("key", 0) {
@@ -268,6 +614,8 @@
@BindsInstance statusBarStateController: StatusBarStateController,
@BindsInstance keyguardStateController: KeyguardStateController,
@BindsInstance selectedUserInteractor: SelectedUserInteractor,
+ @BindsInstance
+ sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
): TestSensitiveContentCoordinatorComponent
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 89f826b..1ab4c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
+import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -32,6 +33,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
@@ -39,6 +41,7 @@
import android.metrics.LogMaker;
import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -101,6 +104,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.settings.SecureSettings;
@@ -172,10 +176,16 @@
@Mock private ActivityStarter mActivityStarter;
@Mock private KeyguardTransitionRepository mKeyguardTransitionRepo;
@Mock private NotificationListViewBinder mViewBinder;
+ @Mock
+ private SensitiveNotificationProtectionController mSensitiveNotificationProtectionController;
+
+ @Captor
+ private ArgumentCaptor<Runnable> mSensitiveStateListenerArgumentCaptor;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
+
private final ActiveNotificationListRepository mActiveNotificationsRepository =
new ActiveNotificationListRepository();
@@ -386,6 +396,23 @@
}
@Test
+ public void testOnUserChange_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
public void testOnUserChange_verifySensitiveProfile() {
when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
initController(/* viewIsAttached= */ true);
@@ -403,6 +430,80 @@
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnUserChange_verifyNotSensitive_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnUserChange_verifySensitiveProfile_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnUserChange_verifySensitiveActive_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ public void testOnStatePostChange_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
public void testOnStatePostChange_verifyIfProfileIsPublic() {
when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
@@ -418,6 +519,194 @@
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_verifyNotSensitive_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_verifyIfProfileIsPublic_screenshareNotificationHidingEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_verifyIfSensitiveActive_screenshareNotificationHidingEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ public void testOnStatePostChange_goingFullShade_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, false);
+ }
+
+ @Test
+ public void testOnStatePostChange_goingFullShade_verifyIfProfileIsPublic() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_goingFullShade_verifyNotSensitive_screenshareHideEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_goingFullShade_verifyProfileIsPublic_screenshareHideEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_goingFullShade_verifySensitiveActive_screenshareHideEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnProjectionStateChanged_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive())
+ .thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController)
+ .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+ mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnProjectionStateChanged_verifyIfProfileIsPublic() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController)
+ .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+ mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnProjectionStateChanged_verifyIfSensitiveActive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController)
+ .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+ mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
public void testOnMenuShownLogging() {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker(
@@ -666,6 +955,20 @@
verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
}
+ @Test
+ @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void sensitiveNotificationProtectionControllerListenerNotRegistered() {
+ initController(/* viewIsAttached= */ true);
+ verifyZeroInteractions(mSensitiveNotificationProtectionController);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void sensitiveNotificationProtectionControllerListenerRegistered() {
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController).registerSensitiveStateListener(any());
+ }
+
private LogMaker logMatcher(int category, int type) {
return argThat(new LogMatcher(category, type));
}
@@ -744,7 +1047,8 @@
mSecureSettings,
mock(NotificationDismissibilityProvider.class),
mActivityStarter,
- new ResourcesSplitShadeStateController());
+ new ResourcesSplitShadeStateController(),
+ mSensitiveNotificationProtectionController);
}
static class LogMatcher implements ArgumentMatcher<LogMaker> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 4827c92..d9eaea1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -66,6 +66,7 @@
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -145,6 +146,7 @@
@Mock private AlternateBouncerToGoneTransitionViewModel
mAlternateBouncerToGoneTransitionViewModel;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ @Mock private KeyguardInteractor mKeyguardInteractor;
private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
@Mock private CoroutineDispatcher mMainDispatcher;
@Mock private TypedArray mMockTypedArray;
@@ -292,6 +294,7 @@
mPrimaryBouncerToGoneTransitionViewModel,
mAlternateBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
+ mKeyguardInteractor,
mWallpaperRepository,
mMainDispatcher,
mLinearLargeScreenShadeInterpolator);
@@ -1000,6 +1003,7 @@
mPrimaryBouncerToGoneTransitionViewModel,
mAlternateBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
+ mKeyguardInteractor,
mWallpaperRepository,
mMainDispatcher,
mLinearLargeScreenShadeInterpolator);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
new file mode 100644
index 0000000..cd5d5ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -0,0 +1,231 @@
+/*
+ * 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 android.app.Notification
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
+ @Mock private lateinit var handler: Handler
+
+ @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
+
+ @Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
+
+ @Mock private lateinit var listener1: Runnable
+ @Mock private lateinit var listener2: Runnable
+ @Mock private lateinit var listener3: Runnable
+
+ @Captor
+ private lateinit var mediaProjectionCallbackCaptor:
+ ArgumentCaptor<MediaProjectionManager.Callback>
+
+ private lateinit var controller: SensitiveNotificationProtectionControllerImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+
+ controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+
+ // Obtain useful MediaProjectionCallback
+ verify(mediaProjectionManager).addCallback(mediaProjectionCallbackCaptor.capture(), any())
+ }
+
+ @Test
+ fun init_flagEnabled_registerMediaProjectionManagerCallback() {
+ assertNotNull(mediaProjectionCallbackCaptor.value)
+ }
+
+ @Test
+ fun init_flagDisabled_noRegisterMediaProjectionManagerCallback() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ reset(mediaProjectionManager)
+
+ controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+
+ verifyZeroInteractions(mediaProjectionManager)
+ }
+
+ @Test
+ fun registerSensitiveStateListener_singleListener() {
+ controller.registerSensitiveStateListener(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+ }
+
+ @Test
+ fun registerSensitiveStateListener_multipleListeners() {
+ controller.registerSensitiveStateListener(listener1)
+ controller.registerSensitiveStateListener(listener2)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+ verify(listener2, times(2)).run()
+ }
+
+ @Test
+ fun registerSensitiveStateListener_afterProjectionActive() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ controller.registerSensitiveStateListener(listener1)
+ verifyZeroInteractions(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1).run()
+ }
+
+ @Test
+ fun unregisterSensitiveStateListener_singleListener() {
+ controller.registerSensitiveStateListener(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+
+ controller.unregisterSensitiveStateListener(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verifyNoMoreInteractions(listener1)
+ }
+
+ @Test
+ fun unregisterSensitiveStateListener_multipleListeners() {
+ controller.registerSensitiveStateListener(listener1)
+ controller.registerSensitiveStateListener(listener2)
+ controller.registerSensitiveStateListener(listener3)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+ verify(listener2, times(2)).run()
+ verify(listener3, times(2)).run()
+
+ controller.unregisterSensitiveStateListener(listener1)
+ controller.unregisterSensitiveStateListener(listener2)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verifyNoMoreInteractions(listener1)
+ verifyNoMoreInteractions(listener2)
+ verify(listener3, times(4)).run()
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionInactive_false() {
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionActive_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ assertTrue(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionInactiveAfterActive_false() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionActiveAfterInactive_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ assertTrue(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionInactive_false() {
+ val notificationEntry = mock(NotificationEntry::class.java)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_fgsNotification_false() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = mock(NotificationEntry::class.java)
+ val sbn = mock(StatusBarNotification::class.java)
+ val notification = mock(Notification::class.java)
+ `when`(notificationEntry.sbn).thenReturn(sbn)
+ `when`(sbn.notification).thenReturn(notification)
+ `when`(notification.isFgsOrUij).thenReturn(true)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = mock(NotificationEntry::class.java)
+ val sbn = mock(StatusBarNotification::class.java)
+ val notification = mock(Notification::class.java)
+ `when`(notificationEntry.sbn).thenReturn(sbn)
+ `when`(sbn.notification).thenReturn(notification)
+ `when`(notification.isFgsOrUij).thenReturn(false)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 59f56dd..5766f7a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -130,6 +130,8 @@
private val _isEncryptedOrLockdown = MutableStateFlow(true)
override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown
+ override val topClippingBounds = MutableStateFlow<Int?>(null)
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 3483c1a..a493d7a 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -95,6 +95,7 @@
private static final int ALLOW_OVERRIDE_APP_RESTRICTIONS = 0x100;
private static final int ALLOW_IMPLICIT_BROADCASTS = 0x200;
private static final int ALLOW_VENDOR_APEX = 0x400;
+ private static final int ALLOW_SIGNATURE_PERMISSIONS = 0x800;
private static final int ALLOW_ALL = ~0;
// property for runtime configuration differentiation
@@ -597,7 +598,7 @@
// Vendors are only allowed to customize these
int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS
- | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX;
+ | ALLOW_SIGNATURE_PERMISSIONS | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX;
if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.O_MR1) {
// For backward compatibility
vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS);
@@ -649,9 +650,9 @@
// TODO(b/157203468): ALLOW_HIDDENAPI_WHITELISTING must be removed because we prohibited
// the use of hidden APIs from the product partition.
int productPermissionFlag = ALLOW_FEATURES | ALLOW_LIBS | ALLOW_PERMISSIONS
- | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_HIDDENAPI_WHITELISTING
- | ALLOW_ASSOCIATIONS | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS
- | ALLOW_VENDOR_APEX;
+ | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_SIGNATURE_PERMISSIONS
+ | ALLOW_HIDDENAPI_WHITELISTING | ALLOW_ASSOCIATIONS
+ | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS | ALLOW_VENDOR_APEX;
if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.R) {
// TODO(b/157393157): This must check product interface enforcement instead of
// DEVICE_INITIAL_SDK_INT for the devices without product interface enforcement.
@@ -772,6 +773,8 @@
final boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
final boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS)
!= 0;
+ final boolean allowSignaturePermissions = (permissionFlag & ALLOW_SIGNATURE_PERMISSIONS)
+ != 0;
final boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0;
final boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING)
!= 0;
@@ -1246,6 +1249,38 @@
XmlUtils.skipCurrentTag(parser);
}
} break;
+ case "signature-permissions": {
+ if (allowSignaturePermissions) {
+ // signature permissions from system, apex, vendor, product and
+ // system_ext partitions are stored separately. This is to
+ // prevent xml files in the vendor partition from granting
+ // permissions to signature apps in the system partition and vice versa.
+ boolean vendor = permFile.toPath().startsWith(
+ Environment.getVendorDirectory().toPath() + "/")
+ || permFile.toPath().startsWith(
+ Environment.getOdmDirectory().toPath() + "/");
+ boolean product = permFile.toPath().startsWith(
+ Environment.getProductDirectory().toPath() + "/");
+ boolean systemExt = permFile.toPath().startsWith(
+ Environment.getSystemExtDirectory().toPath() + "/");
+ if (vendor) {
+ readSignatureAppPermissions(parser,
+ mPermissionAllowlist.getVendorSignatureAppAllowlist());
+ } else if (product) {
+ readSignatureAppPermissions(parser,
+ mPermissionAllowlist.getProductSignatureAppAllowlist());
+ } else if (systemExt) {
+ readSignatureAppPermissions(parser,
+ mPermissionAllowlist.getSystemExtSignatureAppAllowlist());
+ } else {
+ readSignatureAppPermissions(parser,
+ mPermissionAllowlist.getSignatureAppAllowlist());
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ } break;
case "oem-permissions": {
if (allowOemPermissions) {
readOemPermissions(parser);
@@ -1655,6 +1690,12 @@
readPermissionAllowlist(parser, allowlist, "privapp-permissions");
}
+ private void readSignatureAppPermissions(@NonNull XmlPullParser parser,
+ @NonNull ArrayMap<String, ArrayMap<String, Boolean>> allowlist)
+ throws IOException, XmlPullParserException {
+ readPermissionAllowlist(parser, allowlist, "signature-permissions");
+ }
+
private void readInstallInUserType(XmlPullParser parser,
Map<String, Set<String>> doInstallMap,
Map<String, Set<String>> nonInstallMap)
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index fb4943a..c1d5ebf 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1328,8 +1328,7 @@
mPointerIconDisplayContext = null;
}
- updateAdditionalDisplayInputProperties(displayId,
- AdditionalDisplayInputProperties::reset);
+ updateAdditionalDisplayInputProperties(displayId, AdditionalDisplayInputProperties::reset);
// TODO(b/320763728): Rely on WindowInfosListener to determine when a display has been
// removed in InputDispatcher instead of this callback.
@@ -1812,8 +1811,6 @@
mPointerIconType = icon.getType();
mPointerIcon = mPointerIconType == PointerIcon.TYPE_CUSTOM ? icon : null;
- if (!mCurrentDisplayProperties.pointerIconVisible) return false;
-
return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken);
}
}
@@ -3509,7 +3506,11 @@
properties = new AdditionalDisplayInputProperties();
mAdditionalDisplayInputProperties.put(displayId, properties);
}
+ final boolean oldPointerIconVisible = properties.pointerIconVisible;
updater.accept(properties);
+ if (oldPointerIconVisible != properties.pointerIconVisible) {
+ mNative.setPointerIconVisibility(displayId, properties.pointerIconVisible);
+ }
if (properties.allDefaults()) {
mAdditionalDisplayInputProperties.remove(displayId);
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index c3ef80f..706db3d 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -190,6 +190,8 @@
boolean setPointerIcon(@NonNull PointerIcon icon, int displayId, int deviceId, int pointerId,
@NonNull IBinder inputToken);
+ void setPointerIconVisibility(int displayId, boolean visible);
+
void requestPointerCapture(IBinder windowToken, boolean enabled);
boolean canDispatchToDisplay(int deviceId, int displayId);
@@ -452,6 +454,9 @@
int pointerId, IBinder inputToken);
@Override
+ public native void setPointerIconVisibility(int displayId, boolean visible);
+
+ @Override
public native void requestPointerCapture(IBinder windowToken, boolean enabled);
@Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
index 3efac81..d138606 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
@@ -26,6 +26,7 @@
public final class PermissionAllowlist {
@NonNull
private final ArrayMap<String, ArrayMap<String, Boolean>> mOemAppAllowlist = new ArrayMap<>();
+
@NonNull
private final ArrayMap<String, ArrayMap<String, Boolean>> mPrivilegedAppAllowlist =
new ArrayMap<>();
@@ -43,6 +44,19 @@
mApexPrivilegedAppAllowlists = new ArrayMap<>();
@NonNull
+ private final ArrayMap<String, ArrayMap<String, Boolean>> mSignatureAppAllowlist =
+ new ArrayMap<>();
+ @NonNull
+ private final ArrayMap<String, ArrayMap<String, Boolean>> mVendorSignatureAppAllowlist =
+ new ArrayMap<>();
+ @NonNull
+ private final ArrayMap<String, ArrayMap<String, Boolean>> mProductSignatureAppAllowlist =
+ new ArrayMap<>();
+ @NonNull
+ private final ArrayMap<String, ArrayMap<String, Boolean>> mSystemExtSignatureAppAllowlist =
+ new ArrayMap<>();
+
+ @NonNull
public ArrayMap<String, ArrayMap<String, Boolean>> getOemAppAllowlist() {
return mOemAppAllowlist;
}
@@ -73,6 +87,26 @@
return mApexPrivilegedAppAllowlists;
}
+ @NonNull
+ public ArrayMap<String, ArrayMap<String, Boolean>> getSignatureAppAllowlist() {
+ return mSignatureAppAllowlist;
+ }
+
+ @NonNull
+ public ArrayMap<String, ArrayMap<String, Boolean>> getVendorSignatureAppAllowlist() {
+ return mVendorSignatureAppAllowlist;
+ }
+
+ @NonNull
+ public ArrayMap<String, ArrayMap<String, Boolean>> getProductSignatureAppAllowlist() {
+ return mProductSignatureAppAllowlist;
+ }
+
+ @NonNull
+ public ArrayMap<String, ArrayMap<String, Boolean>> getSystemExtSignatureAppAllowlist() {
+ return mSystemExtSignatureAppAllowlist;
+ }
+
@Nullable
public Boolean getOemAppAllowlistState(@NonNull String packageName,
@NonNull String permissionName) {
@@ -137,4 +171,44 @@
}
return permissions.get(permissionName);
}
+
+ @Nullable
+ public Boolean getSignatureAppAllowlistState(@NonNull String packageName,
+ @NonNull String permissionName) {
+ ArrayMap<String, Boolean> permissions = mSignatureAppAllowlist.get(packageName);
+ if (permissions == null) {
+ return null;
+ }
+ return permissions.get(permissionName);
+ }
+
+ @Nullable
+ public Boolean getVendorSignatureAppAllowlistState(@NonNull String packageName,
+ @NonNull String permissionName) {
+ ArrayMap<String, Boolean> permissions = mVendorSignatureAppAllowlist.get(packageName);
+ if (permissions == null) {
+ return null;
+ }
+ return permissions.get(permissionName);
+ }
+
+ @Nullable
+ public Boolean getProductSignatureAppAllowlistState(@NonNull String packageName,
+ @NonNull String permissionName) {
+ ArrayMap<String, Boolean> permissions = mProductSignatureAppAllowlist.get(packageName);
+ if (permissions == null) {
+ return null;
+ }
+ return permissions.get(permissionName);
+ }
+
+ @Nullable
+ public Boolean getSystemExtSignatureAppAllowlistState(@NonNull String packageName,
+ @NonNull String permissionName) {
+ ArrayMap<String, Boolean> permissions = mSystemExtSignatureAppAllowlist.get(packageName);
+ if (permissions == null) {
+ return null;
+ }
+ return permissions.get(permissionName);
+ }
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 1c90e30..ce0efe1 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -304,6 +304,7 @@
bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
int32_t displayId, DeviceId deviceId, int32_t pointerId,
const sp<IBinder>& inputToken);
+ void setPointerIconVisibility(int32_t displayId, bool visible);
void setMotionClassifierEnabled(bool enabled);
std::optional<std::string> getBluetoothAddress(int32_t deviceId);
void setStylusButtonMotionEventsEnabled(bool enabled);
@@ -1397,6 +1398,13 @@
return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId);
}
+void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) {
+ if (!ENABLE_POINTER_CHOREOGRAPHER) {
+ return;
+ }
+ mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible);
+}
+
TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
JNIEnv *env, jfloatArray matrixArr) {
ATRACE_CALL();
@@ -2550,6 +2558,13 @@
ibinderForJavaObject(env, inputTokenObj));
}
+static void nativeSetPointerIconVisibility(JNIEnv* env, jobject nativeImplObj, jint displayId,
+ jboolean visible) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->setPointerIconVisibility(displayId, visible);
+}
+
static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2828,6 +2843,7 @@
(void*)nativeSetCustomPointerIcon},
{"setPointerIcon", "(Landroid/view/PointerIcon;IIILandroid/os/IBinder;)Z",
(void*)nativeSetPointerIcon},
+ {"setPointerIconVisibility", "(IZ)V", (void*)nativeSetPointerIconVisibility},
{"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
{"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
{"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 62d2d7e..4c74878 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -22,6 +22,7 @@
import android.content.pm.PermissionInfo
import android.content.pm.SigningDetails
import android.os.Build
+import android.permission.flags.Flags
import android.util.Slog
import com.android.internal.os.RoSystemProperties
import com.android.internal.pm.permission.CompatibilityPermissionInfo
@@ -1197,15 +1198,80 @@
newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!!
.androidPackage!!
.signingDetails
- return sourceSigningDetails?.hasCommonSignerWithCapability(
- packageSigningDetails,
- SigningDetails.CertCapabilities.PERMISSION
- ) == true ||
- packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
- platformSigningDetails.checkCapability(
+ val hasCommonSigner =
+ sourceSigningDetails?.hasCommonSignerWithCapability(
packageSigningDetails,
SigningDetails.CertCapabilities.PERMISSION
- )
+ ) == true ||
+ packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
+ platformSigningDetails.checkCapability(
+ packageSigningDetails,
+ SigningDetails.CertCapabilities.PERMISSION
+ )
+ if (!Flags.signaturePermissionAllowlistEnabled()) {
+ return hasCommonSigner;
+ }
+ if (!hasCommonSigner) {
+ return false
+ }
+ // A platform signature permission also needs to be allowlisted on non-debuggable builds.
+ if (permission.packageName == PLATFORM_PACKAGE_NAME) {
+ val isRequestedByFactoryApp =
+ if (packageState.isSystem) {
+ // For updated system applications, a signature permission still needs to be
+ // allowlisted if it wasn't requested by the original application.
+ if (packageState.isUpdatedSystemApp) {
+ val disabledSystemPackage =
+ newState.externalState.disabledSystemPackageStates[
+ packageState.packageName]
+ ?.androidPackage
+ disabledSystemPackage != null &&
+ permission.name in disabledSystemPackage.requestedPermissions
+ } else {
+ true
+ }
+ } else {
+ false
+ }
+ if (
+ !(isRequestedByFactoryApp ||
+ getSignaturePermissionAllowlistState(packageState, permission.name) == true)
+ ) {
+ Slog.w(
+ LOG_TAG,
+ "Signature permission ${permission.name} for package" +
+ " ${packageState.packageName} (${packageState.path}) not in" +
+ " signature permission allowlist"
+ )
+ if (!Build.isDebuggable()) {
+ return false
+ }
+ }
+ }
+ return true
+ }
+
+ private fun MutateStateScope.getSignaturePermissionAllowlistState(
+ packageState: PackageState,
+ permissionName: String
+ ): Boolean? {
+ val permissionAllowlist = newState.externalState.permissionAllowlist
+ val packageName = packageState.packageName
+ return when {
+ packageState.isVendor || packageState.isOdm ->
+ permissionAllowlist.getVendorSignatureAppAllowlistState(packageName, permissionName)
+ packageState.isProduct ->
+ permissionAllowlist.getProductSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
+ packageState.isSystemExt ->
+ permissionAllowlist.getSystemExtSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
+ else -> permissionAllowlist.getSignatureAppAllowlistState(packageName, permissionName)
+ }
}
private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 1c57623..29faed1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -132,8 +132,10 @@
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -563,6 +565,86 @@
return Pair.create(splitPrimaryActivity, splitSecondActivity);
}
+ /**
+ * This test ensures that if the intent is being delivered to a desktop mode unfocused task
+ * while it is already on top, reports it as delivering to top.
+ */
+ @Test
+ public void testDesktopModeDeliverToTop() {
+ final ActivityStarter starter = prepareStarter(
+ FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP,
+ false /* mockGetRootTask */);
+ final List<ActivityRecord> activities = createActivitiesInDesktopMode();
+
+ // Set focus back to the first task.
+ activities.get(0).moveFocusableActivityToTop("testDesktopModeDeliverToTop");
+
+ // Start activity and delivered new intent.
+ starter.getIntent().setComponent(activities.get(3).mActivityComponent);
+ doReturn(activities.get(3)).when(mRootWindowContainer).findTask(any(), any());
+ final int result = starter.setReason("testDesktopModeDeliverToTop").execute();
+
+ // Ensure result is delivering intent to top.
+ assertEquals(START_DELIVERED_TO_TOP, result);
+ }
+
+ /**
+ * This test ensures that if the intent is being delivered to a desktop mode unfocused task
+ * reports it is brought to front instead of delivering to top.
+ */
+ @Test
+ public void testDesktopModeTaskToFront() {
+ final ActivityStarter starter = prepareStarter(
+ FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, false);
+ final List<ActivityRecord> activities = createActivitiesInDesktopMode();
+ final ActivityRecord desktopModeFocusActivity = activities.get(0);
+ final ActivityRecord desktopModeReusableActivity = activities.get(1);
+ final ActivityRecord desktopModeTopActivity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setParentTask(desktopModeReusableActivity.getRootTask()).build();
+ assertTrue(desktopModeTopActivity.inMultiWindowMode());
+
+ // Let first stack has focus.
+ desktopModeFocusActivity.moveFocusableActivityToTop("testDesktopModeTaskToFront");
+
+ // Start activity and delivered new intent.
+ starter.getIntent().setComponent(desktopModeReusableActivity.mActivityComponent);
+ doReturn(desktopModeReusableActivity).when(mRootWindowContainer).findTask(any(), any());
+ final int result = starter.setReason("testDesktopModeMoveToFront").execute();
+
+ // Ensure result is moving task to front.
+ assertEquals(START_TASK_TO_FRONT, result);
+ }
+
+ /** Returns 4 activities. */
+ private List<ActivityRecord> createActivitiesInDesktopMode() {
+ final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+ List<ActivityRecord> activityRecords = new ArrayList<>();
+
+ for (int i = 0; i < 4; i++) {
+ Rect bounds = new Rect(desktopOrganizer.getDefaultDesktopTaskBounds());
+ bounds.offset(20 * i, 20 * i);
+ desktopOrganizer.createTask(bounds);
+ }
+
+ for (int i = 0; i < 4; i++) {
+ activityRecords.add(new TaskBuilder(mSupervisor)
+ .setParentTask(desktopOrganizer.mTasks.get(i))
+ .setCreateActivity(true)
+ .build()
+ .getTopMostActivity());
+ }
+
+ for (int i = 0; i < 4; i++) {
+ activityRecords.get(i).setVisibleRequested(true);
+ }
+
+ for (int i = 0; i < 4; i++) {
+ assertEquals(desktopOrganizer.mTasks.get(i), activityRecords.get(i).getRootTask());
+ }
+
+ return activityRecords;
+ }
+
@Test
public void testMoveVisibleTaskToFront() {
final ActivityRecord activity = new TaskBuilder(mSupervisor)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 114b9c3..c7c7913 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -133,7 +134,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/** Common base class for window manager unit test classes. */
class WindowTestsBase extends SystemServiceTestsBase {
@@ -1892,6 +1895,55 @@
}
}
+ static class TestDesktopOrganizer extends WindowOrganizerTests.StubOrganizer {
+ final int mDesktopModeDefaultWidthDp = 840;
+ final int mDesktopModeDefaultHeightDp = 630;
+ final int mDesktopDensity = 284;
+
+ final ActivityTaskManagerService mService;
+ final TaskDisplayArea mDefaultTDA;
+ List<Task> mTasks;
+ final DisplayContent mDisplay;
+ Rect mStableBounds;
+
+ TestDesktopOrganizer(ActivityTaskManagerService service, DisplayContent display) {
+ mService = service;
+ mDefaultTDA = display.getDefaultTaskDisplayArea();
+ mDisplay = display;
+ mService.mTaskOrganizerController.registerTaskOrganizer(this);
+ mTasks = new ArrayList<>();
+ mStableBounds = display.getBounds();
+ }
+
+ TestDesktopOrganizer(ActivityTaskManagerService service) {
+ this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay());
+ }
+
+ public Task createTask(Rect bounds) {
+ Task task = mService.mTaskOrganizerController.createRootTask(
+ mDisplay, WINDOWING_MODE_FREEFORM, null);
+ task.setBounds(bounds);
+ mTasks.add(task);
+ spyOn(task);
+ return task;
+ }
+
+ public Rect getDefaultDesktopTaskBounds() {
+ int width = (int) (mDesktopModeDefaultWidthDp * mDesktopDensity + 0.5f);
+ int height = (int) (mDesktopModeDefaultHeightDp * mDesktopDensity + 0.5f);
+ Rect outBounds = new Rect();
+
+ outBounds.set(0, 0, width, height);
+ // Center the task in stable bounds
+ outBounds.offset(
+ mStableBounds.centerX() - outBounds.centerX(),
+ mStableBounds.centerY() - outBounds.centerY()
+ );
+ return outBounds;
+ }
+
+ }
+
static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
return createTestWindowToken(type, dc, false /* persistOnEmpty */);
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9ec5f7a..67bb1f0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6888,6 +6888,7 @@
}
}
+ // TODO(b/316183370): replace all @code with @link in javadoc after feature is released
/**
* @return true if the current device is "voice capable".
* <p>
@@ -6901,7 +6902,10 @@
* PackageManager.FEATURE_TELEPHONY system feature, which is available
* on any device with a telephony radio, even if the device is
* data-only.
- * @deprecated Replaced by {@link #isDeviceVoiceCapable()}
+ * @deprecated Replaced by {@code #isDeviceVoiceCapable()}. Starting from Android 15, voice
+ * capability may also be overridden by carriers for a given subscription. For voice capable
+ * device (when {@code #isDeviceVoiceCapable} return {@code true}), caller should check for
+ * subscription-level voice capability as well. See {@code #isDeviceVoiceCapable} for details.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
@Deprecated
@@ -6923,9 +6927,10 @@
* .FEATURE_TELEPHONY system feature, which is available on any device with a telephony
* radio, even if the device is data-only.
* <p>
- * To check if a subscription is "voice capable", call method
- * {@link SubscriptionInfo#getServiceCapabilities()} and compare the result with
- * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE}.
+ * Starting from Android 15, voice capability may also be overridden by carrier for a given
+ * subscription on a voice capable device. To check if a subscription is "voice capable",
+ * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if
+ * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included.
*
* @see SubscriptionInfo#getServiceCapabilities()
*/
@@ -6943,7 +6948,10 @@
* <p>
* Note: Voicemail waiting sms, cell broadcasting sms, and MMS are
* disabled when device doesn't support sms.
- * @deprecated Replaced by {@link #isDeviceSmsCapable()}
+ * @deprecated Replaced by {@code #isDeviceSmsCapable()}. Starting from Android 15, SMS
+ * capability may also be overridden by carriers for a given subscription. For SMS capable
+ * device (when {@code #isDeviceSmsCapable} return {@code true}), caller should check for
+ * subscription-level SMS capability as well. See {@code #isDeviceSmsCapable} for details.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
public boolean isSmsCapable() {
@@ -6961,9 +6969,10 @@
* Note: Voicemail waiting SMS, cell broadcasting SMS, and MMS are
* disabled when device doesn't support SMS.
* <p>
- * To check if a subscription is "SMS capable", call method
- * {@link SubscriptionInfo#getServiceCapabilities()} and compare result with
- * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_SMS}.
+ * Starting from Android 15, SMS capability may also be overridden by carriers for a given
+ * subscription on an SMS capable device. To check if a subscription is "SMS capable",
+ * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if
+ * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS} is included.
*
* @see SubscriptionInfo#getServiceCapabilities()
*/
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index b058631..60a9f0b 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -292,11 +292,13 @@
setVirtualMousePointerDisplayIdAndVerify(10)
localService.setPointerIconVisible(false, 10)
+ verify(native).setPointerIconVisibility(10, false)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
localService.setMousePointerAccelerationEnabled(false, 10)
verify(native).setMousePointerAccelerationEnabled(eq(false))
service.onDisplayRemoved(10)
+ verify(native).setPointerIconVisibility(10, true)
verify(native).displayRemoved(eq(10))
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
verify(native).setMousePointerAccelerationEnabled(true)
@@ -315,17 +317,20 @@
localService.setPointerIconVisible(false, 10)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
+ verify(native).setPointerIconVisibility(10, false)
localService.setMousePointerAccelerationEnabled(false, 10)
verify(native).setMousePointerAccelerationEnabled(eq(false))
localService.setPointerIconVisible(true, 10)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
+ verify(native).setPointerIconVisibility(10, true)
localService.setMousePointerAccelerationEnabled(true, 10)
verify(native).setMousePointerAccelerationEnabled(eq(true))
// Verify that setting properties on a different display is not propagated until the
// pointer is moved to that display.
localService.setPointerIconVisible(false, 20)
+ verify(native).setPointerIconVisibility(20, false)
localService.setMousePointerAccelerationEnabled(false, 20)
verifyNoMoreInteractions(native)
@@ -341,6 +346,7 @@
localService.setPointerIconVisible(false, 10)
localService.setMousePointerAccelerationEnabled(false, 10)
+ verify(native).setPointerIconVisibility(10, false)
verifyNoMoreInteractions(native)
setVirtualMousePointerDisplayIdAndVerify(10)