Merge "Prevent default blueprint changes from triggering refreshes" into main
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index e3c367f8..63ffaa0 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -39,6 +39,13 @@
}
flag {
+ name: "check_autogroup_before_post"
+ namespace: "systemui"
+ description: "Does a check to see if notification should be autogrouped before posting, and if so groups before post."
+ bug: "330214226"
+}
+
+flag {
name: "visit_risky_uris"
namespace: "systemui"
description: "Guards the security fix that ensures all URIs in intents and Person.java are valid"
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 1cdcd20..0e7de48 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2463,22 +2463,22 @@
public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) {
final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused();
synchronized (mH) {
- if (!isFocusedAndWindowFocused && !hasServedByInputMethodLocked(view)) {
+ final boolean hasServedByInputMethod = hasServedByInputMethodLocked(view);
+ if (!isFocusedAndWindowFocused && !hasServedByInputMethod) {
// Fail early if the view is not focused and not served
// to avoid logging many erroneous calls.
return false;
}
- }
- final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
- final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
- ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view));
- ImeTracker.forLatency().onRequestHide(statsToken,
- ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication);
- ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromView",
- this, null /* icProto */);
- synchronized (mH) {
- if (!hasServedByInputMethodLocked(view)) {
+ final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
+ final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+ ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view));
+ ImeTracker.forLatency().onRequestHide(statsToken,
+ ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication);
+ ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromView",
+ this, null /* icProto */);
+
+ if (!hasServedByInputMethod) {
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
ImeTracker.forLatency().onShowFailed(statsToken,
ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java
index 6cf241e..ddbb586 100644
--- a/core/java/com/android/internal/util/ProcFileReader.java
+++ b/core/java/com/android/internal/util/ProcFileReader.java
@@ -89,6 +89,12 @@
mTail -= count;
if (mTail == 0) {
fillBuf();
+
+ if (mTail > 0 && mBuffer[0] == ' ') {
+ // After filling the buffer, it contains more consecutive
+ // delimiters that need to be skipped.
+ consumeBuf(0);
+ }
}
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 77a9912..bfbfb3a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -178,6 +178,7 @@
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REPLY" />
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" />
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" />
+ <protected-broadcast android:name="android.bluetooth.device.action.KEY_MISSING" />
<protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" />
<protected-broadcast android:name="android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.REMOTE_ISSUE_OCCURRED" />
diff --git a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
index 4c00c16..9785ca7 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
@@ -216,6 +216,46 @@
}
@Test
+ public void testBufferSizeWithConsecutiveDelimiters() throws Exception {
+ // Read numbers using very small buffer size, exercising fillBuf()
+ // Include more consecutive delimiters than the buffer size.
+ final ProcFileReader reader =
+ buildReader("1 21 3 41 5 61 7 81 9 10\n", 3);
+
+ assertEquals(1, reader.nextInt());
+ assertEquals(21, reader.nextInt());
+ assertEquals(3, reader.nextInt());
+ assertEquals(41, reader.nextInt());
+ assertEquals(5, reader.nextInt());
+ assertEquals(61, reader.nextInt());
+ assertEquals(7, reader.nextInt());
+ assertEquals(81, reader.nextInt());
+ assertEquals(9, reader.nextInt());
+ assertEquals(10, reader.nextInt());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+ }
+
+ @Test
+ public void testBufferSizeWithConsecutiveDelimitersAndMultipleLines() throws Exception {
+ final ProcFileReader reader =
+ buildReader("1 21 41 \n 5 7 81 \n 9 10 \n", 3);
+
+ assertEquals(1, reader.nextInt());
+ assertEquals(21, reader.nextInt());
+ assertEquals(41, reader.nextInt());
+ reader.finishLine();
+ assertEquals(5, reader.nextInt());
+ assertEquals(7, reader.nextInt());
+ assertEquals(81, reader.nextInt());
+ reader.finishLine();
+ assertEquals(9, reader.nextInt());
+ assertEquals(10, reader.nextInt());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+ }
+
+ @Test
public void testIgnore() throws Exception {
final ProcFileReader reader = buildReader("a b c\n");
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 9d1e507..65615e6 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -322,11 +322,11 @@
<library name="android.hidl.manager-V1.0-java"
file="/system/framework/android.hidl.manager-V1.0-java.jar" />
- <!-- These are the standard packages that are white-listed to always have internet
+ <!-- These are the standard packages that are allowed to always have internet
access while in power save mode, even if they aren't in the foreground. -->
<allow-in-power-save package="com.android.providers.downloads" />
- <!-- These are the standard packages that are white-listed to always have internet
+ <!-- These are the standard packages that are allowed to always have internet
access while in data mode, even if they aren't in the foreground. -->
<allow-in-data-usage-save package="com.android.providers.downloads" />
@@ -338,7 +338,7 @@
<!-- Emergency app needs to run in the background to reliably provide safety features -->
<allow-in-power-save package="com.android.emergency" />
- <!-- Whitelist system providers -->
+ <!-- Allow system providers -->
<!-- Calendar provider needs alarms while in idle -->
<allow-in-power-save package="com.android.providers.calendar" />
<allow-in-power-save-except-idle package="com.android.providers.contacts" />
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index 2aff2c3..3fc6154 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -46,6 +46,7 @@
android:paddingEnd="@dimen/autofill_view_right_padding"
android:paddingTop="@dimen/autofill_view_top_padding"
android:paddingBottom="@dimen/autofill_view_bottom_padding"
+ android:textDirection="locale"
android:orientation="vertical">
<TextView
@@ -53,6 +54,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textDirection="locale"
style="@style/autofill.TextTitle"/>
<TextView
@@ -60,6 +62,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ android:textDirection="locale"
style="@style/autofill.TextSubtitle"/>
</LinearLayout>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index c8f9135..991ce12 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -31,7 +31,6 @@
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
@@ -90,6 +89,7 @@
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
private static UiDevice sUiDevice;
+ private static String sLockSettings;
private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false);
@@ -108,6 +108,11 @@
sUiAutomation.adoptShellPermissionIdentity(
UiAutomation.ALL_PERMISSIONS.toArray(new String[0]));
sUiDevice = UiDevice.getInstance(sInstrumentation);
+ sLockSettings = sUiDevice.executeShellCommand("locksettings get-disabled");
+ Log.i(TAG, "locksettings get-disabled returns " + sLockSettings);
+ // Some test in the test class requires the device to be in lock screen
+ // ensure we have locksettings enabled before running the tests
+ sUiDevice.executeShellCommand("locksettings set-disabled false");
final Context context = sInstrumentation.getTargetContext();
sAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -157,9 +162,10 @@
}
@AfterClass
- public static void classTeardown() {
+ public static void classTeardown() throws IOException {
Settings.Secure.putString(sInstrumentation.getTargetContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
+ sUiDevice.executeShellCommand("locksettings set-disabled " + sLockSettings);
}
@Before
@@ -184,17 +190,17 @@
return root != null && root.getPackageName().toString().equals(PACKAGE_NAME);
}
- private static void wakeUpScreen() throws IOException {
+ private static void wakeUpScreen() {
sUiDevice.pressKeyCode(KeyEvent.KEYCODE_WAKEUP);
WaitUtils.waitForValueToSettle("Screen On", AccessibilityMenuServiceTest::isScreenOn);
- assertWithMessage("Screen is on").that(isScreenOn()).isTrue();
+ WaitUtils.ensureThat("Screen is on", AccessibilityMenuServiceTest::isScreenOn);
}
- private static void closeScreen() throws Throwable {
+ private static void closeScreen() {
// go/adb-cheats#lock-screen
sUiDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP);
WaitUtils.waitForValueToSettle("Screen Off", AccessibilityMenuServiceTest::isScreenOff);
- assertWithMessage("Screen is off").that(isScreenOff()).isTrue();
+ WaitUtils.ensureThat("Screen is off", AccessibilityMenuServiceTest::isScreenOff);
WaitUtils.ensureThat(
"Screen is locked", () -> sKeyguardManager.isKeyguardLocked());
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index eedff89..5f84dd4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -17,6 +17,11 @@
package com.android.systemui.qs.footer.ui.compose
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.LocalIndication
@@ -87,10 +92,24 @@
fun SceneScope.FooterActionsWithAnimatedVisibility(
viewModel: FooterActionsViewModel,
isCustomizing: Boolean,
+ customizingAnimationDuration: Int,
lifecycleOwner: LifecycleOwner,
modifier: Modifier = Modifier,
) {
- AnimatedVisibility(visible = !isCustomizing, modifier = modifier.fillMaxWidth()) {
+ AnimatedVisibility(
+ visible = !isCustomizing,
+ enter =
+ expandVertically(
+ animationSpec = tween(customizingAnimationDuration),
+ initialHeight = { 0 },
+ ) + fadeIn(tween(customizingAnimationDuration)),
+ exit =
+ shrinkVertically(
+ animationSpec = tween(customizingAnimationDuration),
+ targetHeight = { 0 },
+ ) + fadeOut(tween(customizingAnimationDuration)),
+ modifier = modifier.fillMaxWidth()
+ ) {
QuickSettingsTheme {
// This view has its own horizontal padding
// TODO(b/321716470) This should use a lifecycle tied to the scene.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index a87a8df..46be6b8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -162,7 +162,8 @@
modifier: Modifier = Modifier,
) {
val qsView by qsSceneAdapter.qsView.collectAsState(null)
- val isCustomizing by qsSceneAdapter.isCustomizing.collectAsState()
+ val isCustomizing by
+ qsSceneAdapter.isCustomizerShowing.collectAsState(qsSceneAdapter.isCustomizerShowing.value)
QuickSettingsTheme {
val context = LocalContext.current
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index a32cc04..4c0f2e1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -19,12 +19,15 @@
import android.view.ViewGroup
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clipScrollableContainer
import androidx.compose.foundation.gestures.Orientation
@@ -178,6 +181,9 @@
}
) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+ val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
+ val customizingAnimationDuration by
+ viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState()
val screenHeight = LocalRawScreenHeight.current
BackHandler(
@@ -217,6 +223,18 @@
val navBarBottomHeight =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
val density = LocalDensity.current
+ val bottomPadding by
+ animateDpAsState(
+ targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
+ animationSpec = tween(customizingAnimationDuration),
+ label = "animateQSSceneBottomPaddingAsState"
+ )
+ val topPadding by
+ animateDpAsState(
+ targetValue = if (isCustomizing) ShadeHeader.Dimensions.CollapsedHeight else 0.dp,
+ animationSpec = tween(customizingAnimationDuration),
+ label = "animateQSSceneTopPaddingAsState"
+ )
LaunchedEffect(navBarBottomHeight, density) {
with(density) {
@@ -236,17 +254,14 @@
horizontalAlignment = Alignment.CenterHorizontally,
modifier =
Modifier.fillMaxSize()
- .then(
- if (isCustomizing) {
- Modifier.padding(top = 48.dp)
- } else {
- Modifier.padding(bottom = navBarBottomHeight)
- }
+ .padding(
+ top = topPadding.coerceAtLeast(0.dp),
+ bottom = bottomPadding.coerceAtLeast(0.dp)
)
) {
Box(modifier = Modifier.fillMaxSize().weight(1f)) {
val shadeHeaderAndQuickSettingsModifier =
- if (isCustomizing) {
+ if (isCustomizerShowing) {
Modifier.fillMaxHeight().align(Alignment.TopCenter)
} else {
Modifier.verticalNestedScrollToScene()
@@ -269,15 +284,22 @@
visible = !isCustomizing,
enter =
expandVertically(
- animationSpec = tween(100),
- initialHeight = { collapsedHeaderHeight },
- ) + fadeIn(tween(100)),
+ animationSpec = tween(customizingAnimationDuration),
+ expandFrom = Alignment.Top,
+ ) +
+ slideInVertically(
+ animationSpec = tween(customizingAnimationDuration),
+ ) +
+ fadeIn(tween(customizingAnimationDuration)),
exit =
shrinkVertically(
- animationSpec = tween(100),
- targetHeight = { collapsedHeaderHeight },
+ animationSpec = tween(customizingAnimationDuration),
shrinkTowards = Alignment.Top,
- ) + fadeOut(tween(100)),
+ ) +
+ slideOutVertically(
+ animationSpec = tween(customizingAnimationDuration),
+ ) +
+ fadeOut(tween(customizingAnimationDuration)),
) {
ExpandedShadeHeader(
viewModel = viewModel.shadeHeaderViewModel,
@@ -303,7 +325,7 @@
viewModel.qsSceneAdapter,
{ viewModel.qsSceneAdapter.qsHeight },
isSplitShade = false,
- modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
+ modifier = Modifier.sysuiResTag("expanded_qs_scroll_view")
)
MediaCarousel(
@@ -318,6 +340,7 @@
FooterActionsWithAnimatedVisibility(
viewModel = footerActionsViewModel,
isCustomizing = isCustomizing,
+ customizingAnimationDuration = customizingAnimationDuration,
lifecycleOwner = lifecycleOwner,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 72b8026..ef5d4e1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -17,7 +17,9 @@
package com.android.systemui.shade.ui.composable
import android.view.ViewGroup
+import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.clipScrollableContainer
@@ -301,6 +303,9 @@
modifier: Modifier = Modifier,
) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+ val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
+ val customizingAnimationDuration by
+ viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState()
val lifecycleOwner = LocalLifecycleOwner.current
val footerActionsViewModel =
remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
@@ -320,6 +325,12 @@
.collectAsState(0f)
val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
+ val bottomPadding by
+ animateDpAsState(
+ targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
+ animationSpec = tween(customizingAnimationDuration),
+ label = "animateQSSceneBottomPaddingAsState"
+ )
val density = LocalDensity.current
LaunchedEffect(navBarBottomHeight, density) {
with(density) {
@@ -390,16 +401,13 @@
)
Column(
verticalArrangement = Arrangement.Top,
- modifier =
- Modifier.fillMaxSize().thenIf(!isCustomizing) {
- Modifier.padding(bottom = navBarBottomHeight)
- },
+ modifier = Modifier.fillMaxSize().padding(bottom = bottomPadding),
) {
Column(
modifier =
Modifier.fillMaxSize()
.weight(1f)
- .thenIf(!isCustomizing) {
+ .thenIf(!isCustomizerShowing) {
Modifier.verticalNestedScrollToScene()
.verticalScroll(
quickSettingsScrollState,
@@ -432,6 +440,7 @@
FooterActionsWithAnimatedVisibility(
viewModel = footerActionsViewModel,
isCustomizing = isCustomizing,
+ customizingAnimationDuration = customizingAnimationDuration,
lifecycleOwner = lifecycleOwner,
modifier =
Modifier.align(Alignment.CenterHorizontally)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index dfc04ff..456fb79 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -57,6 +57,8 @@
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
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.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.activityStarter
@@ -1090,6 +1092,78 @@
.isEqualTo(USER_INFO_WORK.id)
}
+ @Test
+ fun showCommunalFromOccluded_enteredOccludedFromHub() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
+ assertThat(showCommunalFromOccluded).isFalse()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+
+ assertThat(showCommunalFromOccluded).isTrue()
+ }
+
+ @Test
+ fun showCommunalFromOccluded_enteredOccludedFromLockscreen() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
+ assertThat(showCommunalFromOccluded).isFalse()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+
+ assertThat(showCommunalFromOccluded).isFalse()
+ }
+
+ @Test
+ fun showCommunalFromOccluded_communalBecomesUnavailableWhileOccluded() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
+ assertThat(showCommunalFromOccluded).isFalse()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ runCurrent()
+ kosmos.setCommunalAvailable(false)
+
+ assertThat(showCommunalFromOccluded).isFalse()
+ }
+
+ @Test
+ fun showCommunalFromOccluded_showBouncerWhileOccluded() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
+ assertThat(showCommunalFromOccluded).isFalse()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ runCurrent()
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+
+ assertThat(showCommunalFromOccluded).isTrue()
+ }
+
private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
val timer = mock(SmartspaceTarget::class.java)
whenever(timer.smartspaceTargetId).thenReturn(id)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index f2eb7f4..c660ff3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -273,21 +273,56 @@
}
@Test
- fun customizing_QS() =
+ fun customizing_QS_noAnimations() =
testScope.runTest {
- val customizing by collectLastValue(underTest.isCustomizing)
+ val customizerState by collectLastValue(underTest.customizerState)
underTest.inflate(context)
runCurrent()
underTest.setState(QSSceneAdapter.State.QS)
- assertThat(customizing).isFalse()
+ assertThat(customizerState).isEqualTo(CustomizerState.Hidden)
underTest.setCustomizerShowing(true)
- assertThat(customizing).isTrue()
+ assertThat(customizerState).isEqualTo(CustomizerState.Showing)
underTest.setCustomizerShowing(false)
- assertThat(customizing).isFalse()
+ assertThat(customizerState).isEqualTo(CustomizerState.Hidden)
+ }
+
+ // This matches the calls made by QSCustomizer
+ @Test
+ fun customizing_QS_animations_correctStates() =
+ testScope.runTest {
+ val customizerState by collectLastValue(underTest.customizerState)
+ val animatingInDuration = 100L
+ val animatingOutDuration = 50L
+
+ underTest.inflate(context)
+ runCurrent()
+ underTest.setState(QSSceneAdapter.State.QS)
+
+ assertThat(customizerState).isEqualTo(CustomizerState.Hidden)
+
+ // Start showing customizer with animation
+ underTest.setCustomizerAnimating(true)
+ underTest.setCustomizerShowing(true, animatingInDuration)
+ assertThat(customizerState)
+ .isEqualTo(CustomizerState.AnimatingIntoCustomizer(animatingInDuration))
+
+ // Finish animation
+ underTest.setCustomizerAnimating(false)
+ assertThat(customizerState).isEqualTo(CustomizerState.Showing)
+
+ // Start closing customizer with animation
+ underTest.setCustomizerAnimating(true)
+ underTest.setCustomizerShowing(false, animatingOutDuration)
+ assertThat(customizerState)
+ .isEqualTo(CustomizerState.AnimatingOutOfCustomizer(animatingOutDuration))
+
+ // Finish animation
+ underTest.setCustomizerAnimating(false)
+ assertThat(customizerState).isEqualTo(CustomizerState.Hidden)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
index 55e46dc..e1be6b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
@@ -24,16 +24,17 @@
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
import com.android.systemui.communal.util.fakeCommunalColors
import com.android.systemui.concurrency.fakeExecutor
-import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle
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.wakefulnessLifecycle
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.SysUiState
@@ -63,7 +64,6 @@
import java.util.Optional
import java.util.concurrent.Executor
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -186,29 +186,18 @@
verify(mRecentTasks).setTransitionBackgroundColor(null)
verify(mRecentTasks, never()).setTransitionBackgroundColor(black)
- setDocked(true)
- // Make communal available
- kosmos.fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
- kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
- kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
-
+ // Transition to occluded from the glanceable hub.
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ kosmos.setCommunalAvailable(true)
runCurrent()
verify(mRecentTasks).setTransitionBackgroundColor(black)
}
- private fun TestScope.setDocked(docked: Boolean) {
- kosmos.fakeDockManager.setIsDocked(docked)
- val event =
- if (docked) {
- DockManager.STATE_DOCKED
- } else {
- DockManager.STATE_NONE
- }
- kosmos.fakeDockManager.setDockEvent(event)
- runCurrent()
- }
-
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index 0534824..f1c3f94 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -323,6 +323,9 @@
alternateBouncerUIAvailable
.logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false)
.launchIn(applicationScope)
+ alternateBouncerVisible
+ .logDiffsForTable(buffer, "", "AlternateBouncerVisible", false)
+ .launchIn(applicationScope)
lastShownSecurityMode
.map { it.name }
.logDiffsForTable(buffer, "", "lastShownSecurityMode", null)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 5cf68b7..0042915 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -44,9 +44,10 @@
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.retrieveIsDocked
+import com.android.systemui.dagger.qualifiers.Background
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.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -64,6 +65,7 @@
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
@@ -77,9 +79,11 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
@@ -92,6 +96,7 @@
@Inject
constructor(
@Application val applicationScope: CoroutineScope,
+ @Background val bgDispatcher: CoroutineDispatcher,
broadcastDispatcher: BroadcastDispatcher,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
@@ -99,13 +104,13 @@
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
keyguardInteractor: KeyguardInteractor,
- private val communalSettingsInteractor: CommunalSettingsInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ communalSettingsInteractor: CommunalSettingsInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
private val userTracker: UserTracker,
private val activityStarter: ActivityStarter,
private val userManager: UserManager,
- private val dockManager: DockManager,
sceneInteractor: SceneInteractor,
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
@@ -145,8 +150,18 @@
replay = 1,
)
- /** Whether to show communal by default */
- val showByDefault: Flow<Boolean> = and(isCommunalAvailable, dockManager.retrieveIsDocked())
+ /** Whether to show communal when exiting the occluded state. */
+ val showCommunalFromOccluded: Flow<Boolean> =
+ keyguardTransitionInteractor.startedKeyguardTransitionStep
+ .filter { step -> step.to == KeyguardState.OCCLUDED }
+ .combine(isCommunalAvailable, ::Pair)
+ .map { (step, available) -> available && step.from == KeyguardState.GLANCEABLE_HUB }
+ .flowOn(bgDispatcher)
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index 1bee83b..337d873 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -63,8 +63,8 @@
)
.distinctUntilChanged()
- /** Whether to show communal by default */
- val showByDefault: Flow<Boolean> = communalInteractor.showByDefault
+ /** Whether to show communal when exiting the occluded state. */
+ val showCommunalFromOccluded: Flow<Boolean> = communalInteractor.showCommunalFromOccluded
val transitionFromOccludedEnded =
keyguardTransitionInteractor.transitionStepsFromState(KeyguardState.OCCLUDED).filter { step
@@ -74,8 +74,11 @@
}
val recentsBackgroundColor: Flow<Color?> =
- combine(showByDefault, communalColors.backgroundColor) { showByDefault, backgroundColor ->
- if (showByDefault) {
+ combine(showCommunalFromOccluded, communalColors.backgroundColor) {
+ showCommunalFromOccluded,
+ backgroundColor,
+ ->
+ if (showCommunalFromOccluded) {
backgroundColor
} else {
null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6ef39f3..d6fd354 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -177,10 +177,6 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
-import dagger.Lazy;
-
-import kotlinx.coroutines.CoroutineDispatcher;
-
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -190,6 +186,9 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
/**
* Mediates requests related to the keyguard. This includes queries about the
* state of the keyguard, power management events that effect whether the keyguard
@@ -1234,7 +1233,7 @@
mUnoccludeAnimator.cancel();
}
- if (isDream || mShowCommunalByDefault) {
+ if (isDream || mShowCommunalWhenUnoccluding) {
initAlphaForAnimationTargets(wallpapers);
if (isDream) {
mDreamViewModel.get().startTransitionFromDream();
@@ -1372,7 +1371,7 @@
private final Lazy<DreamViewModel> mDreamViewModel;
private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
- private boolean mShowCommunalByDefault = false;
+ private boolean mShowCommunalWhenUnoccluding = false;
private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
@@ -1630,8 +1629,10 @@
getRemoteSurfaceAlphaApplier());
mJavaAdapter.alwaysCollectFlow(dreamViewModel.getTransitionEnded(),
getFinishedCallbackConsumer());
- mJavaAdapter.alwaysCollectFlow(communalViewModel.getShowByDefault(),
- (showByDefault) -> mShowCommunalByDefault = showByDefault);
+ mJavaAdapter.alwaysCollectFlow(communalViewModel.getShowCommunalFromOccluded(),
+ (showCommunalFromOccluded) -> {
+ mShowCommunalWhenUnoccluding = showCommunalFromOccluded;
+ });
mJavaAdapter.alwaysCollectFlow(communalViewModel.getTransitionFromOccludedEnded(),
getFinishedCallbackConsumer());
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index ee589f4..e51ba83 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -87,12 +87,15 @@
scope.launch {
keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
.filterRelevantKeyguardStateAnd { onTop -> !onTop }
- .sample(communalInteractor.isIdleOnCommunal, communalInteractor.showByDefault)
- .collect { (_, isIdleOnCommunal, showCommunalByDefault) ->
+ .sample(
+ communalInteractor.isIdleOnCommunal,
+ communalInteractor.showCommunalFromOccluded,
+ )
+ .collect { (_, isIdleOnCommunal, showCommunalFromOccluded) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
val to =
- if (isIdleOnCommunal || showCommunalByDefault) {
+ if (isIdleOnCommunal || showCommunalFromOccluded) {
KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
@@ -106,16 +109,16 @@
.sample(
keyguardInteractor.isKeyguardShowing,
communalInteractor.isIdleOnCommunal,
- communalInteractor.showByDefault,
+ communalInteractor.showCommunalFromOccluded,
)
.filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _) ->
!isOccluded && isShowing
}
- .collect { (_, _, isIdleOnCommunal, showCommunalByDefault) ->
+ .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
val to =
- if (isIdleOnCommunal || showCommunalByDefault) {
+ if (isIdleOnCommunal || showCommunalFromOccluded) {
KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index 53f0132..18022a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.binder
import android.graphics.PixelFormat
+import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
@@ -95,11 +96,11 @@
applicationScope.launch("$TAG#alternateBouncerWindowViewModel") {
alternateBouncerWindowViewModel.get().alternateBouncerWindowRequired.collect {
addAlternateBouncerWindowView ->
+ Log.d(TAG, "alternateBouncerWindowRequired=$addAlternateBouncerWindowView")
if (addAlternateBouncerWindowView) {
addViewToWindowManager()
- val scrim =
+ val scrim: ScrimView =
alternateBouncerView!!.requireViewById(R.id.alternate_bouncer_scrim)
- as ScrimView
scrim.viewAlpha = 0f
bind(alternateBouncerView!!, alternateBouncerDependencies.get())
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 3d86e3c..63acbb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -49,21 +49,44 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
// TODO(307945185) Split View concerns into a ViewBinder
/** Adapter to use between Scene system and [QSImpl] */
interface QSSceneAdapter {
- /** Whether [QSImpl] is currently customizing */
+
+ /**
+ * Whether we are currently customizing or entering the customizer.
+ *
+ * @see CustomizerState.isCustomizing
+ */
val isCustomizing: StateFlow<Boolean>
/**
+ * Whether the customizer is showing. This includes animating into and out of it.
+ *
+ * @see CustomizerState.isShowing
+ */
+ val isCustomizerShowing: StateFlow<Boolean>
+
+ /**
+ * The duration of the current animation in/out of customizer. If not in an animating state,
+ * this duration is 0 (to match show/hide immediately).
+ *
+ * @see CustomizerState.Animating.animationDuration
+ */
+ val customizerAnimationDuration: StateFlow<Int>
+
+ /**
* A view with the QS content ([QSContainerImpl]), managed by an instance of [QSImpl] tracked by
* the interactor.
*/
@@ -181,8 +204,35 @@
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED)
- private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val isCustomizing = _isCustomizing.asStateFlow()
+ private val _customizingState: MutableStateFlow<CustomizerState> =
+ MutableStateFlow(CustomizerState.Hidden)
+ val customizerState = _customizingState.asStateFlow()
+
+ override val isCustomizing: StateFlow<Boolean> =
+ customizerState
+ .map { it.isCustomizing }
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ customizerState.value.isCustomizing,
+ )
+ override val isCustomizerShowing: StateFlow<Boolean> =
+ customizerState
+ .map { it.isShowing }
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ customizerState.value.isShowing
+ )
+ override val customizerAnimationDuration: StateFlow<Int> =
+ customizerState
+ .map { (it as? CustomizerState.Animating)?.animationDuration?.toInt() ?: 0 }
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ (customizerState.value as? CustomizerState.Animating)?.animationDuration?.toInt()
+ ?: 0,
+ )
private val _qsImpl: MutableStateFlow<QSImpl?> = MutableStateFlow(null)
val qsImpl = _qsImpl.asStateFlow()
@@ -209,9 +259,9 @@
dumpManager.registerDumpable(this)
applicationScope.launch {
launch {
- state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
+ state.sample(_customizingState, ::Pair).collect { (state, customizing) ->
qsImpl.value?.apply {
- if (state != QSSceneAdapter.State.QS && customizing) {
+ if (state != QSSceneAdapter.State.QS && customizing.isShowing) {
this@apply.closeCustomizerImmediately()
}
applyState(state)
@@ -243,14 +293,38 @@
}
}
- override fun setCustomizerAnimating(animating: Boolean) {}
+ override fun setCustomizerAnimating(animating: Boolean) {
+ if (_customizingState.value is CustomizerState.Animating && !animating) {
+ _customizingState.update {
+ if (it is CustomizerState.AnimatingIntoCustomizer) {
+ CustomizerState.Showing
+ } else {
+ CustomizerState.Hidden
+ }
+ }
+ }
+ }
override fun setCustomizerShowing(showing: Boolean) {
- _isCustomizing.value = showing
+ setCustomizerShowing(showing, 0L)
}
override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) {
- setCustomizerShowing(showing)
+ _customizingState.update { _ ->
+ if (showing) {
+ if (animationDuration > 0) {
+ CustomizerState.AnimatingIntoCustomizer(animationDuration)
+ } else {
+ CustomizerState.Showing
+ }
+ } else {
+ if (animationDuration > 0) {
+ CustomizerState.AnimatingOutOfCustomizer(animationDuration)
+ } else {
+ CustomizerState.Hidden
+ }
+ }
+ }
}
override fun setDetailShowing(showing: Boolean) {}
@@ -302,9 +376,50 @@
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.apply {
println("Last state: ${state.value}")
- println("Customizing: ${isCustomizing.value}")
+ println("CustomizerState: ${_customizingState.value}")
println("QQS height: $qqsHeight")
println("QS height: $qsHeight")
}
}
}
+
+/** Current state of the customizer */
+sealed interface CustomizerState {
+
+ /**
+ * This indicates that some part of the customizer is showing. It could be animating in or out.
+ */
+ val isShowing: Boolean
+ get() = true
+
+ /**
+ * This indicates that we are currently customizing or animating into it. In particular, when
+ * animating out, this is false.
+ *
+ * @see QSCustomizer.isCustomizing
+ */
+ val isCustomizing: Boolean
+ get() = false
+
+ sealed interface Animating : CustomizerState {
+ val animationDuration: Long
+ }
+
+ /** Customizer is completely hidden, and not animating */
+ data object Hidden : CustomizerState {
+ override val isShowing = false
+ }
+
+ /** Customizer is completely showing, and not animating */
+ data object Showing : CustomizerState {
+ override val isCustomizing = true
+ }
+
+ /** Animating from [Hidden] into [Showing]. */
+ data class AnimatingIntoCustomizer(override val animationDuration: Long) : Animating {
+ override val isCustomizing = true
+ }
+
+ /** Animating from [Showing] into [Hidden]. */
+ data class AnimatingOutOfCustomizer(override val animationDuration: Long) : Animating
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 257c4d5..17698f9d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -79,13 +79,13 @@
combine(
deviceEntryInteractor.isUnlocked,
deviceEntryInteractor.canSwipeToEnter,
- qsSceneAdapter.isCustomizing,
+ qsSceneAdapter.isCustomizerShowing,
backScene,
- ) { isUnlocked, canSwipeToDismiss, isCustomizing, backScene ->
+ ) { isUnlocked, canSwipeToDismiss, isCustomizerShowing, backScene ->
destinationScenes(
isUnlocked,
canSwipeToDismiss,
- isCustomizing,
+ isCustomizerShowing,
backScene,
)
}
@@ -96,7 +96,7 @@
destinationScenes(
isUnlocked = deviceEntryInteractor.isUnlocked.value,
canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
- isCustomizing = qsSceneAdapter.isCustomizing.value,
+ isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
backScene = backScene.value,
),
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
index 3462993..864e39a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
@@ -33,7 +33,7 @@
get() = shadeInteractor.isQsExpanded.value
override val isCustomizing: Boolean
- get() = qsSceneAdapter.isCustomizing.value
+ get() = qsSceneAdapter.isCustomizerShowing.value
@Deprecated("specific to legacy touch handling")
override fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 5b76acb..ac76bec 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -72,13 +72,13 @@
deviceEntryInteractor.isUnlocked,
deviceEntryInteractor.canSwipeToEnter,
shadeInteractor.shadeMode,
- qsSceneAdapter.isCustomizing
- ) { isUnlocked, canSwipeToDismiss, shadeMode, isCustomizing ->
+ qsSceneAdapter.isCustomizerShowing
+ ) { isUnlocked, canSwipeToDismiss, shadeMode, isCustomizerShowing ->
destinationScenes(
isUnlocked = isUnlocked,
canSwipeToDismiss = canSwipeToDismiss,
shadeMode = shadeMode,
- isCustomizing = isCustomizing
+ isCustomizing = isCustomizerShowing
)
}
.stateIn(
@@ -89,7 +89,7 @@
isUnlocked = deviceEntryInteractor.isUnlocked.value,
canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
shadeMode = shadeInteractor.shadeMode.value,
- isCustomizing = qsSceneAdapter.isCustomizing.value,
+ isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
),
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 7c1101b..d7d3732 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -382,16 +382,15 @@
private void clearCurrentMediaNotificationSession() {
mMediaMetadata = null;
- mBackgroundExecutor.execute(() -> {
- if (mMediaController != null) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
- + mMediaController.getPackageName());
- }
- mMediaController.unregisterCallback(mMediaListener);
- mMediaController = null;
+ if (mMediaController != null) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
+ + mMediaController.getPackageName());
}
- });
+ // TODO(b/336612071): move to background thread
+ mMediaController.unregisterCallback(mMediaListener);
+ }
+ mMediaController = null;
}
public interface MediaListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index f6161c5..be6bef7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2350,6 +2350,7 @@
} else if (mState == StatusBarState.KEYGUARD
&& !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
&& mStatusBarKeyguardViewManager.isSecure()) {
+ Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer");
mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 5deb08a7..cff46ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
+import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
@@ -277,6 +278,15 @@
addView(view, viewIndex, createLayoutParams());
}
+ /** Adds a bindable icon to the demo mode view. */
+ public void addBindableIcon(StatusBarIconHolder.BindableIconHolder holder) {
+ // This doesn't do any correct ordering, and also doesn't check if we already have an
+ // existing icon for the slot. But since we hope to remove this class soon, we won't spend
+ // the time adding that logic.
+ ModernStatusBarView view = holder.getInitializer().createAndBind(mContext);
+ addView(view, createLayoutParams());
+ }
+
public void onRemoveIcon(StatusIconDisplayable view) {
if (view.getSlot().equals("wifi")) {
if (view instanceof ModernStatusBarWifiView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
index bef0b28..08a890d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
@@ -169,16 +169,19 @@
* StatusBarIconController will register all available bindable icons on init (see
* [BindableIconsRepository]), and will ignore any call to setIcon for these.
*
- * [initializer] a view creator that can bind the relevant view models to the created view.
+ * @property initializer a view creator that can bind the relevant view models to the created
+ * view.
+ * @property slot the name of the slot that this holder is used for.
*/
- class BindableIconHolder(val initializer: ModernStatusBarViewCreator) : StatusBarIconHolder() {
+ class BindableIconHolder(val initializer: ModernStatusBarViewCreator, val slot: String) :
+ StatusBarIconHolder() {
override var type: Int = TYPE_BINDABLE
/** This is unused, as bindable icons use their own view binders to control visibility */
override var isVisible: Boolean = true
override fun toString(): String {
- return ("StatusBarIconHolder(type=BINDABLE)")
+ return ("StatusBarIconHolder(type=BINDABLE, slot=$slot)")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7301b87..f0dab3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -744,6 +744,7 @@
public void showBouncer(boolean scrimmed) {
if (DeviceEntryUdfpsRefactor.isEnabled()) {
if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) {
+ Log.d(TAG, "showBouncer:alternateBouncer.forceShow()");
mAlternateBouncerInteractor.forceShow();
updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState());
} else {
@@ -869,6 +870,7 @@
}
if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ Log.d(TAG, "dismissWithAction:alternateBouncer.forceShow()");
mAlternateBouncerInteractor.forceShow();
updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState());
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
index 0ed9420..5ad7376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.DemoStatusIcons;
import com.android.systemui.statusbar.phone.StatusBarIconHolder;
+import com.android.systemui.statusbar.phone.StatusBarIconHolder.BindableIconHolder;
import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
@@ -49,7 +50,9 @@
import com.android.systemui.util.Assert;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Turns info from StatusBarIconController into ImageViews in a ViewGroup.
@@ -60,6 +63,11 @@
private final LocationBasedWifiViewModel mWifiViewModel;
private final MobileIconsViewModel mMobileIconsViewModel;
+ /**
+ * Stores the list of bindable icons that have been added, keyed on slot name. This ensures
+ * we don't accidentally add the same bindable icon twice.
+ */
+ private final Map<String, BindableIconHolder> mBindableIcons = new HashMap<>();
protected final Context mContext;
protected int mIconSize;
// Whether or not these icons show up in dumpsys
@@ -142,7 +150,7 @@
case TYPE_MOBILE_NEW -> addNewMobileIcon(index, slot, holder.getTag());
case TYPE_BINDABLE ->
// Safe cast, since only BindableIconHolders can set this tag on themselves
- addBindableIcon((StatusBarIconHolder.BindableIconHolder) holder, index);
+ addBindableIcon((BindableIconHolder) holder, index);
default -> null;
};
}
@@ -162,10 +170,14 @@
* icon view, we can simply create the icon when requested and allow the
* ViewBinder to control its visual state.
*/
- protected StatusIconDisplayable addBindableIcon(StatusBarIconHolder.BindableIconHolder holder,
+ protected StatusIconDisplayable addBindableIcon(BindableIconHolder holder,
int index) {
+ mBindableIcons.put(holder.getSlot(), holder);
ModernStatusBarView view = holder.getInitializer().createAndBind(mContext);
mGroup.addView(view, index, onCreateLayoutParams());
+ if (mIsInDemoMode) {
+ mDemoStatusIcons.addBindableIcon(holder);
+ }
return view;
}
@@ -278,6 +290,9 @@
if (mDemoStatusIcons == null) {
mDemoStatusIcons = createDemoStatusIcons();
mDemoStatusIcons.addModernWifiView(mWifiViewModel);
+ for (BindableIconHolder holder : mBindableIcons.values()) {
+ mDemoStatusIcons.addBindableIcon(holder);
+ }
}
mDemoStatusIcons.onDemoModeStarted();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
index 92d90af..fabf858d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
@@ -213,7 +213,8 @@
StatusBarIconHolder existingHolder = mStatusBarIconList.getIconHolder(icon.getSlot(), 0);
// Expected to be null
if (existingHolder == null) {
- BindableIconHolder bindableIcon = new BindableIconHolder(icon.getInitializer());
+ BindableIconHolder bindableIcon =
+ new BindableIconHolder(icon.getInitializer(), icon.getSlot());
setIcon(icon.getSlot(), bindableIcon);
} else {
Log.e(TAG, "addBindableIcon called, but icon has already been added. Ignoring");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index b80ff38..226a84a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -41,6 +41,8 @@
import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxyImpl
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepositorySwitcher
+import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl
import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel
import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModelImpl
@@ -83,8 +85,13 @@
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
@Binds
- abstract fun deviceBasedSatelliteRepository(
+ abstract fun realDeviceBasedSatelliteRepository(
impl: DeviceBasedSatelliteRepositoryImpl
+ ): RealDeviceBasedSatelliteRepository
+
+ @Binds
+ abstract fun deviceBasedSatelliteRepository(
+ impl: DeviceBasedSatelliteRepositorySwitcher
): DeviceBasedSatelliteRepository
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
index ad8b810..d38e834 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.pipeline.satellite.data
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/**
* Device-based satellite refers to the capability of a device to connect directly to a satellite
@@ -26,12 +26,22 @@
*/
interface DeviceBasedSatelliteRepository {
/** See [SatelliteConnectionState] for available states */
- val connectionState: Flow<SatelliteConnectionState>
+ val connectionState: StateFlow<SatelliteConnectionState>
/** 0-4 level (similar to wifi and mobile) */
// @IntRange(from = 0, to = 4)
- val signalStrength: Flow<Int>
+ val signalStrength: StateFlow<Int>
/** Clients must observe this property, as device-based satellite is location-dependent */
- val isSatelliteAllowedForCurrentLocation: Flow<Boolean>
+ val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean>
}
+
+/**
+ * A no-op interface used for Dagger bindings.
+ *
+ * [DeviceBasedSatelliteRepositorySwitcher] needs to inject both the real repository and the demo
+ * mode repository, both of which implement the [DeviceBasedSatelliteRepository] interface. To help
+ * distinguish the two for the switcher, [DeviceBasedSatelliteRepositoryImpl] will implement this
+ * [RealDeviceBasedSatelliteRepository] interface.
+ */
+interface RealDeviceBasedSatelliteRepository : DeviceBasedSatelliteRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
new file mode 100644
index 0000000..6b1bc65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.pipeline.satellite.data
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.satellite.data.demo.DemoDeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * A provider for the [DeviceBasedSatelliteRepository] interface that can choose between the Demo
+ * and Prod concrete implementations at runtime. It works by defining a base flow, [activeRepo],
+ * which switches based on the latest information from [DemoModeController], and switches every flow
+ * in the interface to point to the currently-active provider. This allows us to put the demo mode
+ * interface in its own repository, completely separate from the real version, while still using all
+ * of the prod implementations for the rest of the pipeline (interactors and onward). Looks
+ * something like this:
+ * ```
+ * RealRepository
+ * │
+ * ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ * │
+ * DemoRepository
+ * ```
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class DeviceBasedSatelliteRepositorySwitcher
+@Inject
+constructor(
+ private val realImpl: RealDeviceBasedSatelliteRepository,
+ private val demoImpl: DemoDeviceBasedSatelliteRepository,
+ private val demoModeController: DemoModeController,
+ @Application scope: CoroutineScope,
+) : DeviceBasedSatelliteRepository {
+ private val isDemoMode =
+ conflatedCallbackFlow {
+ val callback =
+ object : DemoMode {
+ override fun dispatchDemoCommand(command: String?, args: Bundle?) {
+ // Don't care
+ }
+
+ override fun onDemoModeStarted() {
+ demoImpl.startProcessingCommands()
+ trySend(true)
+ }
+
+ override fun onDemoModeFinished() {
+ demoImpl.stopProcessingCommands()
+ trySend(false)
+ }
+ }
+
+ demoModeController.addCallback(callback)
+ awaitClose { demoModeController.removeCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode)
+
+ @VisibleForTesting
+ val activeRepo: StateFlow<DeviceBasedSatelliteRepository> =
+ isDemoMode
+ .mapLatest { isDemoMode ->
+ if (isDemoMode) {
+ demoImpl
+ } else {
+ realImpl
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
+
+ override val connectionState: StateFlow<SatelliteConnectionState> =
+ activeRepo
+ .flatMapLatest { it.connectionState }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.connectionState.value)
+
+ override val signalStrength: StateFlow<Int> =
+ activeRepo
+ .flatMapLatest { it.signalStrength }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.signalStrength.value)
+
+ override val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean> =
+ activeRepo
+ .flatMapLatest { it.isSatelliteAllowedForCurrentLocation }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ realImpl.isSatelliteAllowedForCurrentLocation.value
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteDataSource.kt
new file mode 100644
index 0000000..7ecc29b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteDataSource.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.pipeline.satellite.data.demo
+
+import android.os.Bundle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Reads the incoming demo commands and emits the satellite-related commands to [satelliteEvents]
+ * for the demo repository to consume.
+ */
+@SysUISingleton
+class DemoDeviceBasedSatelliteDataSource
+@Inject
+constructor(
+ demoModeController: DemoModeController,
+ @Application scope: CoroutineScope,
+) {
+ private val demoCommandStream = demoModeController.demoFlowForCommand(DemoMode.COMMAND_NETWORK)
+ private val _satelliteCommands =
+ demoCommandStream.map { args -> args.toSatelliteEvent() }.filterNotNull()
+
+ /** A flow that emits the demo commands that are satellite-related. */
+ val satelliteEvents =
+ _satelliteCommands.stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_VALUE)
+
+ private fun Bundle.toSatelliteEvent(): DemoSatelliteEvent? {
+ val satellite = getString("satellite") ?: return null
+ if (satellite != "show") {
+ return null
+ }
+
+ return DemoSatelliteEvent(
+ connectionState = getString("connection").toConnectionState(),
+ signalStrength = getString("level")?.toInt() ?: 0,
+ )
+ }
+
+ data class DemoSatelliteEvent(
+ val connectionState: SatelliteConnectionState,
+ val signalStrength: Int,
+ )
+
+ private fun String?.toConnectionState(): SatelliteConnectionState {
+ if (this == null) {
+ return SatelliteConnectionState.Unknown
+ }
+ return try {
+ // Lets people use "connected" on the command line and have it be correctly converted
+ // to [SatelliteConnectionState.Connected] with a capital C.
+ SatelliteConnectionState.valueOf(this.replaceFirstChar { it.uppercase() })
+ } catch (e: IllegalArgumentException) {
+ SatelliteConnectionState.Unknown
+ }
+ }
+
+ private companion object {
+ val DEFAULT_VALUE = DemoSatelliteEvent(SatelliteConnectionState.Unknown, signalStrength = 0)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
new file mode 100644
index 0000000..56034f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.pipeline.satellite.data.demo
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+
+/** A satellite repository that represents the latest satellite values sent via demo mode. */
+@SysUISingleton
+class DemoDeviceBasedSatelliteRepository
+@Inject
+constructor(
+ private val dataSource: DemoDeviceBasedSatelliteDataSource,
+ @Application private val scope: CoroutineScope,
+) : DeviceBasedSatelliteRepository {
+ private var demoCommandJob: Job? = null
+
+ override val connectionState = MutableStateFlow(SatelliteConnectionState.Unknown)
+ override val signalStrength = MutableStateFlow(0)
+ override val isSatelliteAllowedForCurrentLocation = MutableStateFlow(true)
+
+ fun startProcessingCommands() {
+ demoCommandJob =
+ scope.launch { dataSource.satelliteEvents.collect { event -> processEvent(event) } }
+ }
+
+ fun stopProcessingCommands() {
+ demoCommandJob?.cancel()
+ }
+
+ private fun processEvent(event: DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent) {
+ connectionState.value = event.connectionState
+ signalStrength.value = event.signalStrength
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 3e3ea85..0739b836 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -31,7 +31,7 @@
import com.android.systemui.log.core.MessageInitializer
import com.android.systemui.log.core.MessagePrinter
import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
-import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Supported
@@ -50,12 +50,14 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -134,7 +136,7 @@
@Application private val scope: CoroutineScope,
@OemSatelliteInputLog private val logBuffer: LogBuffer,
private val systemClock: SystemClock,
-) : DeviceBasedSatelliteRepository {
+) : RealDeviceBasedSatelliteRepository {
private val satelliteManager: SatelliteManager?
@@ -200,10 +202,12 @@
}
override val connectionState =
- satelliteSupport.whenSupported(
- supported = ::connectionStateFlow,
- orElse = flowOf(SatelliteConnectionState.Off)
- )
+ satelliteSupport
+ .whenSupported(
+ supported = ::connectionStateFlow,
+ orElse = flowOf(SatelliteConnectionState.Off)
+ )
+ .stateIn(scope, SharingStarted.Eagerly, SatelliteConnectionState.Off)
// By using the SupportedSatelliteManager here, we expect registration never to fail
private fun connectionStateFlow(sm: SupportedSatelliteManager): Flow<SatelliteConnectionState> =
@@ -227,7 +231,9 @@
.flowOn(bgDispatcher)
override val signalStrength =
- satelliteSupport.whenSupported(supported = ::signalStrengthFlow, orElse = flowOf(0))
+ satelliteSupport
+ .whenSupported(supported = ::signalStrengthFlow, orElse = flowOf(0))
+ .stateIn(scope, SharingStarted.Eagerly, 0)
// By using the SupportedSatelliteManager here, we expect registration never to fail
private fun signalStrengthFlow(sm: SupportedSatelliteManager) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 325e7bf..6b1d39a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -241,7 +241,7 @@
.thenReturn(mock(Flow.class));
when(mDreamViewModel.getTransitionEnded())
.thenReturn(mock(Flow.class));
- when(mCommunalTransitionViewModel.getShowByDefault())
+ when(mCommunalTransitionViewModel.getShowCommunalFromOccluded())
.thenReturn(mock(Flow.class));
when(mCommunalTransitionViewModel.getTransitionFromOccludedEnded())
.thenReturn(mock(Flow.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 691d48f..1dc58d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -26,8 +26,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.dock.DockManager
import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -1229,23 +1229,22 @@
}
@Test
- fun occludedToGlanceableHubWhenDocked() =
+ fun occludedToGlanceableHubWhenInitiallyOnHub() =
testScope.runTest {
- // GIVEN a device on lockscreen
+ // GIVEN a device on lockscreen and communal is available
keyguardRepository.setKeyguardShowing(true)
+ kosmos.setCommunalAvailable(true)
runCurrent()
- // GIVEN a prior transition has run to OCCLUDED
+ // GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB
runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- // GIVEN device is docked/communal is available
- dockManager.setIsDocked(true)
- dockManager.setDockEvent(DockManager.STATE_DOCKED)
+ // GIVEN on blank scene
val idleTransitionState =
MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
+ ObservableTransitionState.Idle(CommunalScenes.Blank)
)
communalInteractor.setTransitionState(idleTransitionState)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
new file mode 100644
index 0000000..7ca3b1c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.pipeline.satellite.data
+
+import android.telephony.satellite.SatelliteManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.statusbar.pipeline.satellite.data.demo.DemoDeviceBasedSatelliteDataSource
+import com.android.systemui.statusbar.pipeline.satellite.data.demo.DemoDeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+class DeviceBasedSatelliteRepositorySwitcherTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val demoModeController =
+ mock<DemoModeController>().apply { whenever(this.isInDemoMode).thenReturn(false) }
+ private val satelliteManager = mock<SatelliteManager>()
+ private val systemClock = FakeSystemClock()
+
+ private val realImpl =
+ DeviceBasedSatelliteRepositoryImpl(
+ Optional.of(satelliteManager),
+ testDispatcher,
+ testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
+ systemClock,
+ )
+ private val demoDataSource =
+ mock<DemoDeviceBasedSatelliteDataSource>().also {
+ whenever(it.satelliteEvents)
+ .thenReturn(
+ MutableStateFlow(
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.Unknown,
+ signalStrength = 0,
+ )
+ )
+ )
+ }
+ private val demoImpl =
+ DemoDeviceBasedSatelliteRepository(demoDataSource, testScope.backgroundScope)
+
+ private val underTest =
+ DeviceBasedSatelliteRepositorySwitcher(
+ realImpl,
+ demoImpl,
+ demoModeController,
+ testScope.backgroundScope,
+ )
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun switcherActiveRepo_updatesWhenDemoModeChanges() =
+ testScope.runTest {
+ assertThat(underTest.activeRepo.value).isSameInstanceAs(realImpl)
+
+ val latest by collectLastValue(underTest.activeRepo)
+ runCurrent()
+
+ startDemoMode()
+
+ assertThat(latest).isSameInstanceAs(demoImpl)
+
+ finishDemoMode()
+
+ assertThat(latest).isSameInstanceAs(realImpl)
+ }
+
+ private fun startDemoMode() {
+ whenever(demoModeController.isInDemoMode).thenReturn(true)
+ getDemoModeCallback().onDemoModeStarted()
+ }
+
+ private fun finishDemoMode() {
+ whenever(demoModeController.isInDemoMode).thenReturn(false)
+ getDemoModeCallback().onDemoModeFinished()
+ }
+
+ private fun getDemoModeCallback(): DemoMode {
+ val captor = kotlinArgumentCaptor<DemoMode>()
+ verify(demoModeController).addCallback(captor.capture())
+ return captor.value
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
new file mode 100644
index 0000000..f77fd19
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.pipeline.satellite.data.demo
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+
+@SmallTest
+class DemoDeviceBasedSatelliteRepositoryTest : SysuiTestCase() {
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val fakeSatelliteEvents =
+ MutableStateFlow(
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.Unknown,
+ signalStrength = 0,
+ )
+ )
+
+ private lateinit var dataSource: DemoDeviceBasedSatelliteDataSource
+
+ private lateinit var underTest: DemoDeviceBasedSatelliteRepository
+
+ @Before
+ fun setUp() {
+ dataSource =
+ mock<DemoDeviceBasedSatelliteDataSource>().also {
+ whenever(it.satelliteEvents).thenReturn(fakeSatelliteEvents)
+ }
+
+ underTest = DemoDeviceBasedSatelliteRepository(dataSource, testScope.backgroundScope)
+ }
+
+ @Test
+ fun startProcessing_getsNewUpdates() =
+ testScope.runTest {
+ val latestConnection by collectLastValue(underTest.connectionState)
+ val latestSignalStrength by collectLastValue(underTest.signalStrength)
+
+ underTest.startProcessingCommands()
+
+ fakeSatelliteEvents.value =
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.On,
+ signalStrength = 3,
+ )
+
+ assertThat(latestConnection).isEqualTo(SatelliteConnectionState.On)
+ assertThat(latestSignalStrength).isEqualTo(3)
+
+ fakeSatelliteEvents.value =
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.Connected,
+ signalStrength = 4,
+ )
+
+ assertThat(latestConnection).isEqualTo(SatelliteConnectionState.Connected)
+ assertThat(latestSignalStrength).isEqualTo(4)
+ }
+
+ @Test
+ fun stopProcessing_stopsGettingUpdates() =
+ testScope.runTest {
+ val latestConnection by collectLastValue(underTest.connectionState)
+ val latestSignalStrength by collectLastValue(underTest.signalStrength)
+
+ underTest.startProcessingCommands()
+
+ fakeSatelliteEvents.value =
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.On,
+ signalStrength = 3,
+ )
+ assertThat(latestConnection).isEqualTo(SatelliteConnectionState.On)
+ assertThat(latestSignalStrength).isEqualTo(3)
+
+ underTest.stopProcessingCommands()
+
+ // WHEN new values are emitted
+ fakeSatelliteEvents.value =
+ DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent(
+ connectionState = SatelliteConnectionState.Connected,
+ signalStrength = 4,
+ )
+
+ // THEN they're not collected because we stopped processing commands, so the old values
+ // are still present
+ assertThat(latestConnection).isEqualTo(SatelliteConnectionState.On)
+ assertThat(latestSignalStrength).isEqualTo(3)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 77e48bff..6b0ad4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -156,7 +156,7 @@
verify(satelliteManager).registerForNtnSignalStrengthChanged(any(), capture())
}
- assertThat(latest).isNull()
+ assertThat(latest).isEqualTo(0)
callback.onNtnSignalStrengthChanged(NtnSignalStrength(1))
assertThat(latest).isEqualTo(1)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 3dd382f..3fe6973 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -23,14 +23,15 @@
import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
-import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -42,6 +43,7 @@
val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
applicationScope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
broadcastDispatcher = broadcastDispatcher,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
@@ -49,13 +51,13 @@
mediaRepository = communalMediaRepository,
smartspaceRepository = smartspaceRepository,
keyguardInteractor = keyguardInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
communalSettingsInteractor = communalSettingsInteractor,
appWidgetHost = mock(),
editWidgetsActivityStarter = editWidgetsActivityStarter,
userTracker = userTracker,
activityStarter = activityStarter,
userManager = userManager,
- dockManager = fakeDockManager,
sceneInteractor = sceneInteractor,
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index a654d6f..e0f60e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -21,7 +21,6 @@
import com.android.systemui.settings.brightness.MirrorController
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
@@ -30,8 +29,16 @@
override val qqsHeight: Int = 0,
override val qsHeight: Int = 0,
) : QSSceneAdapter {
+ private val _customizerState = MutableStateFlow<CustomizerState>(CustomizerState.Hidden)
+
+ private val _customizerShowing = MutableStateFlow(false)
+ override val isCustomizerShowing = _customizerShowing.asStateFlow()
+
private val _customizing = MutableStateFlow(false)
- override val isCustomizing: StateFlow<Boolean> = _customizing.asStateFlow()
+ override val isCustomizing = _customizing.asStateFlow()
+
+ private val _animationDuration = MutableStateFlow(0)
+ override val customizerAnimationDuration = _animationDuration.asStateFlow()
private val _view = MutableStateFlow<View?>(null)
override val qsView: Flow<View> = _view.filterNotNull()
@@ -58,7 +65,7 @@
}
fun setCustomizing(value: Boolean) {
- _customizing.value = value
+ updateCustomizerFlows(if (value) CustomizerState.Showing else CustomizerState.Hidden)
}
override suspend fun applyBottomNavBarPadding(padding: Int) {
@@ -66,10 +73,18 @@
}
override fun requestCloseCustomizer() {
- _customizing.value = false
+ updateCustomizerFlows(CustomizerState.Hidden)
}
override fun setBrightnessMirrorController(mirrorController: MirrorController?) {
brightnessMirrorController = mirrorController
}
+
+ private fun updateCustomizerFlows(customizerState: CustomizerState) {
+ _customizerState.value = customizerState
+ _customizing.value = customizerState.isCustomizing
+ _customizerShowing.value = customizerState.isShowing
+ _animationDuration.value =
+ (customizerState as? CustomizerState.Animating)?.animationDuration?.toInt() ?: 0
+ }
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index e66fe1b..1ba47e4 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -128,16 +128,6 @@
}
flag {
- name: "manager_avoid_receiver_timeout"
- namespace: "accessibility"
- description: "Avoid broadcast receiver timeout by offloading potentially slow operations to the background thread."
- bug: "333890389"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "pinch_zoom_zero_min_span"
namespace: "accessibility"
description: "Whether to set min span of ScaleGestureDetector to zero."
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 2bece6c..726a01c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -994,10 +994,43 @@
"context=" + context + ";intent=" + intent);
}
- if (com.android.server.accessibility.Flags.managerAvoidReceiverTimeout()) {
- BackgroundThread.getHandler().post(() -> processBroadcast(intent));
- } else {
- processBroadcast(intent);
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+ unlockUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
+ final String which = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
+ if (Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(which)) {
+ synchronized (mLock) {
+ restoreEnabledAccessibilityServicesLocked(
+ intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE),
+ intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE),
+ intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT,
+ 0));
+ }
+ } else if (ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED.equals(which)) {
+ synchronized (mLock) {
+ restoreLegacyDisplayMagnificationNavBarIfNeededLocked(
+ intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE),
+ intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT,
+ 0));
+ }
+ } else if (Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS.equals(which)) {
+ synchronized (mLock) {
+ restoreAccessibilityButtonTargetsLocked(
+ intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE),
+ intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
+ }
+ } else if (Settings.Secure.ACCESSIBILITY_QS_TARGETS.equals(which)) {
+ if (!android.view.accessibility.Flags.a11yQsShortcut()) {
+ return;
+ }
+ restoreAccessibilityQsTargets(
+ intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
+ }
}
}
}, UserHandle.ALL, intentFilter, null, null);
@@ -2000,19 +2033,6 @@
mA11yWindowManager.onTouchInteractionEnd();
}
- private void processBroadcast(Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
- unlockUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
- removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
- restoreSetting(intent);
- }
- }
-
@VisibleForTesting
void switchUser(int userId) {
mMagnificationController.updateUserIdIfNeeded(userId);
@@ -2105,38 +2125,6 @@
getMagnificationController().onUserRemoved(userId);
}
- private void restoreSetting(Intent intent) {
- final String which = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
- if (Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(which)) {
- synchronized (mLock) {
- restoreEnabledAccessibilityServicesLocked(
- intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE),
- intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE),
- intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT,
- 0));
- }
- } else if (ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED.equals(which)) {
- synchronized (mLock) {
- restoreLegacyDisplayMagnificationNavBarIfNeededLocked(
- intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE),
- intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT,
- 0));
- }
- } else if (Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS.equals(which)) {
- synchronized (mLock) {
- restoreAccessibilityButtonTargetsLocked(
- intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE),
- intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
- }
- } else if (Settings.Secure.ACCESSIBILITY_QS_TARGETS.equals(which)) {
- if (!android.view.accessibility.Flags.a11yQsShortcut()) {
- return;
- }
- restoreAccessibilityQsTargets(
- intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
- }
- }
-
// Called only during settings restore; currently supports only the owner user
// TODO: http://b/22388012
void restoreEnabledAccessibilityServicesLocked(String oldSetting, String newSetting,
diff --git a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
index 28e8e30..4f95893 100644
--- a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
@@ -34,6 +34,7 @@
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.IntDef;
+import android.os.SystemClock;
import android.util.Slog;
import com.android.internal.util.FrameworkStatsLog;
@@ -45,7 +46,7 @@
/**
* Helper class to log Autofill Save event stats.
*/
-public final class SaveEventLogger {
+public class SaveEventLogger {
private static final String TAG = "SaveEventLogger";
/**
@@ -112,19 +113,21 @@
public static final int NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG =
AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG;
+ public static final long UNINITIATED_TIMESTAMP = Long.MIN_VALUE;
+
private final int mSessionId;
private Optional<SaveEventInternal> mEventInternal;
+ private long mSessionStartTimestamp;
- private SaveEventLogger(int sessionId) {
- mSessionId = sessionId;
- mEventInternal = Optional.of(new SaveEventInternal());
+ private SaveEventLogger(int sessionId, long sessionStartTimestamp) {
+ mSessionId = sessionId;
+ mEventInternal = Optional.of(new SaveEventInternal());
+ mSessionStartTimestamp = sessionStartTimestamp;
}
- /**
- * A factory constructor to create FillRequestEventLogger.
- */
- public static SaveEventLogger forSessionId(int sessionId) {
- return new SaveEventLogger(sessionId);
+ /** A factory constructor to create FillRequestEventLogger. */
+ public static SaveEventLogger forSessionId(int sessionId, long sessionStartTimestamp) {
+ return new SaveEventLogger(sessionId, sessionStartTimestamp);
}
/**
@@ -224,6 +227,15 @@
});
}
+ /* Returns timestamp (relative to mSessionStartTimestamp) or UNINITIATED_TIMESTAMP if mSessionStartTimestamp is not set */
+ private long getCurrentTimestamp() {
+ long timestamp = UNINITIATED_TIMESTAMP;
+ if (mSessionStartTimestamp != UNINITIATED_TIMESTAMP) {
+ timestamp = SystemClock.elapsedRealtime() - mSessionStartTimestamp;
+ }
+ return timestamp;
+ }
+
/**
* Set latency_save_ui_display_millis as long as mEventInternal presents.
*/
@@ -233,6 +245,11 @@
});
}
+ /** Set latency_save_ui_display_millis as long as mEventInternal presents. */
+ public void maybeSetLatencySaveUiDisplayMillis() {
+ maybeSetLatencySaveUiDisplayMillis(getCurrentTimestamp());
+ }
+
/**
* Set latency_save_request_millis as long as mEventInternal presents.
*/
@@ -242,6 +259,11 @@
});
}
+ /** Set latency_save_request_millis as long as mEventInternal presents. */
+ public void maybeSetLatencySaveRequestMillis() {
+ maybeSetLatencySaveRequestMillis(getCurrentTimestamp());
+ }
+
/**
* Set latency_save_finish_millis as long as mEventInternal presents.
*/
@@ -251,6 +273,11 @@
});
}
+ /** Set latency_save_finish_millis as long as mEventInternal presents. */
+ public void maybeSetLatencySaveFinishMillis() {
+ maybeSetLatencySaveFinishMillis(getCurrentTimestamp());
+ }
+
/**
* Set is_framework_created_save_info as long as mEventInternal presents.
*/
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index cd1ef88..ba6b067 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1531,7 +1531,7 @@
mFillResponseEventLogger = FillResponseEventLogger.forSessionId(sessionId);
mSessionCommittedEventLogger = SessionCommittedEventLogger.forSessionId(sessionId);
mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid);
- mSaveEventLogger = SaveEventLogger.forSessionId(sessionId);
+ mSaveEventLogger = SaveEventLogger.forSessionId(sessionId, mLatencyBaseTime);
mIsPrimaryCredential = isPrimaryCredential;
mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty();
@@ -3931,13 +3931,10 @@
return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_NONE);
}
- final long saveUiDisplayStartTimestamp = SystemClock.elapsedRealtime();
getUiForShowing().showSaveUi(serviceLabel, serviceIcon,
mService.getServicePackageName(), saveInfo, this,
mComponentName, this, mContext, mPendingSaveUi, isUpdate, mCompatMode,
response.getShowSaveDialogIcon(), mSaveEventLogger);
- mSaveEventLogger.maybeSetLatencySaveUiDisplayMillis(
- SystemClock.elapsedRealtime()- saveUiDisplayStartTimestamp);
if (client != null) {
try {
client.setSaveUiState(id, true);
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 602855d..3b9c54f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -413,6 +413,8 @@
callback.startIntentSender(intentSender, intent);
}
}, mUiModeMgr.isNightMode(), isUpdate, compatMode, showServiceIcon);
+
+ mSaveEventLogger.maybeSetLatencySaveUiDisplayMillis();
});
}
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 64bca33..04edb57 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -27,6 +27,7 @@
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION;
+import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -210,6 +211,12 @@
}
};
+ private final OnWindowRemovedListener mOnWindowRemovedListener = token -> {
+ synchronized (mSensitiveContentProtectionLock) {
+ mPackagesShowingSensitiveContent.removeIf(pkgInfo -> pkgInfo.getWindowToken() == token);
+ }
+ };
+
public SensitiveContentProtectionManagerService(@NonNull Context context) {
super(context);
if (sensitiveNotificationAppProtection()) {
@@ -265,6 +272,10 @@
// Intra-process call, should never happen.
}
}
+
+ if (sensitiveContentAppProtection()) {
+ mWindowManager.registerOnWindowRemovedListener(mOnWindowRemovedListener);
+ }
}
/** Cleanup any callbacks and listeners */
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index babb6c2..13cc99c 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -108,16 +108,25 @@
return (flags & mask) != 0;
}
- public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
+ /**
+ * Called when a notification is newly posted. Checks whether that notification, and all other
+ * active notifications should be grouped or ungrouped atuomatically, and returns whether.
+ * @param sbn The posted notification.
+ * @param autogroupSummaryExists Whether a summary for this notification already exists.
+ * @return Whether the provided notification should be autogrouped synchronously.
+ */
+ public boolean onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
+ boolean sbnToBeAutogrouped = false;
try {
if (!sbn.isAppGroup()) {
- maybeGroup(sbn, autogroupSummaryExists);
+ sbnToBeAutogrouped = maybeGroup(sbn, autogroupSummaryExists);
} else {
maybeUngroup(sbn, false, sbn.getUserId());
}
} catch (Exception e) {
Slog.e(TAG, "Failure processing new notification", e);
}
+ return sbnToBeAutogrouped;
}
public void onNotificationRemoved(StatusBarNotification sbn) {
@@ -137,20 +146,22 @@
*
* And stores the list of upgrouped notifications & their flags
*/
- private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
+ private boolean maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
int flags = 0;
List<String> notificationsToGroup = new ArrayList<>();
List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ // Indicates whether the provided sbn should be autogrouped by the caller.
+ boolean sbnToBeAutogrouped = false;
synchronized (mUngroupedNotifications) {
- String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
+ String packageKey = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
final ArrayMap<String, NotificationAttributes> children =
- mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+ mUngroupedNotifications.getOrDefault(packageKey, new ArrayMap<>());
NotificationAttributes attr = new NotificationAttributes(sbn.getNotification().flags,
sbn.getNotification().getSmallIcon(), sbn.getNotification().color,
sbn.getNotification().visibility);
children.put(sbn.getKey(), attr);
- mUngroupedNotifications.put(key, children);
+ mUngroupedNotifications.put(packageKey, children);
if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
flags = getAutogroupSummaryFlags(children);
@@ -187,10 +198,20 @@
mCallback.addAutoGroupSummary(sbn.getUserId(), sbn.getPackageName(), sbn.getKey(),
attr);
}
- for (String key : notificationsToGroup) {
- mCallback.addAutoGroup(key);
+ for (String keyToGroup : notificationsToGroup) {
+ if (android.app.Flags.checkAutogroupBeforePost()) {
+ if (keyToGroup.equals(sbn.getKey())) {
+ // Autogrouping for the provided notification is to be done synchronously.
+ sbnToBeAutogrouped = true;
+ } else {
+ mCallback.addAutoGroup(keyToGroup, /*requestSort=*/true);
+ }
+ } else {
+ mCallback.addAutoGroup(keyToGroup, /*requestSort=*/true);
+ }
}
}
+ return sbnToBeAutogrouped;
}
/**
@@ -406,7 +427,7 @@
}
protected interface Callback {
- void addAutoGroup(String key);
+ void addAutoGroup(String key, boolean requestSort);
void removeAutoGroup(String key);
void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ca6ae63..3dd2f1e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2793,10 +2793,10 @@
return new GroupHelper(getContext(), getContext().getPackageManager(),
mAutoGroupAtCount, new GroupHelper.Callback() {
@Override
- public void addAutoGroup(String key) {
- synchronized (mNotificationLock) {
- addAutogroupKeyLocked(key);
- }
+ public void addAutoGroup(String key, boolean requestSort) {
+ synchronized (mNotificationLock) {
+ addAutogroupKeyLocked(key, requestSort);
+ }
}
@Override
@@ -6538,7 +6538,7 @@
}
@GuardedBy("mNotificationLock")
- void addAutogroupKeyLocked(String key) {
+ void addAutogroupKeyLocked(String key, boolean requestSort) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r == null) {
return;
@@ -6546,7 +6546,9 @@
if (r.getSbn().getOverrideGroupKey() == null) {
addAutoGroupAdjustment(r, GroupHelper.AUTOGROUP_KEY);
EventLogTags.writeNotificationAutogrouped(key);
- mRankingHandler.requestSort();
+ if (!android.app.Flags.checkAutogroupBeforePost() || requestSort) {
+ mRankingHandler.requestSort();
+ }
}
}
@@ -8609,6 +8611,29 @@
notification.flags |= FLAG_NO_CLEAR;
}
+ // Posts the notification if it has a small icon, and potentially autogroup
+ // the new notification.
+ if (android.app.Flags.checkAutogroupBeforePost()) {
+ if (notification.getSmallIcon() != null && !isCritical(r)) {
+ StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
+ if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())
+ || oldSbn.getNotification().flags
+ != n.getNotification().flags) {
+ synchronized (mNotificationLock) {
+ boolean willBeAutogrouped = mGroupHelper.onNotificationPosted(n,
+ hasAutoGroupSummaryLocked(n));
+ if (willBeAutogrouped) {
+ // The newly posted notification will be autogrouped, but
+ // was not autogrouped onPost, to avoid an unnecessary sort.
+ // We add the autogroup key to the notification without a
+ // sort here, and it'll be sorted below with extractSignals.
+ addAutogroupKeyLocked(key, /*requestSort=*/false);
+ }
+ }
+ }
+ }
+ }
+
mRankingHelper.extractSignals(r);
mRankingHelper.sort(mNotificationList);
final int position = mRankingHelper.indexOf(mNotificationList, r);
@@ -8629,17 +8654,20 @@
notifyListenersPostedAndLogLocked(r, old, mTracker, maybeReport);
posted = true;
- StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
- if (oldSbn == null
- || !Objects.equals(oldSbn.getGroup(), n.getGroup())
- || oldSbn.getNotification().flags != n.getNotification().flags) {
- if (!isCritical(r)) {
- mHandler.post(() -> {
- synchronized (mNotificationLock) {
- mGroupHelper.onNotificationPosted(
- n, hasAutoGroupSummaryLocked(n));
- }
- });
+ if (!android.app.Flags.checkAutogroupBeforePost()) {
+ StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
+ if (oldSbn == null
+ || !Objects.equals(oldSbn.getGroup(), n.getGroup())
+ || oldSbn.getNotification().flags
+ != n.getNotification().flags) {
+ if (!isCritical(r)) {
+ mHandler.post(() -> {
+ synchronized (mNotificationLock) {
+ mGroupHelper.onNotificationPosted(
+ n, hasAutoGroupSummaryLocked(n));
+ }
+ });
+ }
}
}
} else {
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 6c93fe7..56e4590 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -89,6 +89,7 @@
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.pm.KnownPackages;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.pkg.PackageState;
@@ -289,6 +290,9 @@
getContext().registerReceiverAsUser(new UserReceiver(), UserHandle.ALL,
userFilter, null, null);
+ UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ umi.addUserLifecycleListener(new UserLifecycleListener());
+
restoreSettings();
// Wipe all shell overlays on boot, to recover from a potentially broken device
@@ -339,6 +343,7 @@
if (newUserId == mPrevStartedUserId) {
return;
}
+ Slog.i(TAG, "Updating overlays for starting user " + newUserId);
try {
traceBegin(TRACE_TAG_RRO, "OMS#onStartUser " + newUserId);
// ensure overlays in the settings are up-to-date, and propagate
@@ -515,14 +520,46 @@
}
}
+ /**
+ * Indicates that the given user is of great importance so that when it is created, we quickly
+ * update its overlays by using a Listener mechanism rather than a Broadcast mechanism. This
+ * is especially important for {@link UserManager#isHeadlessSystemUserMode() HSUM}'s MainUser,
+ * which is created and switched-to immediately on first boot.
+ */
+ private static boolean isHighPriorityUserCreation(UserInfo user) {
+ // TODO: Consider extending this to all created users (guarded behind a flag in that case).
+ return user != null && user.isMain();
+ }
+
+ private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener {
+ @Override
+ public void onUserCreated(UserInfo user, Object token) {
+ if (isHighPriorityUserCreation(user)) {
+ final int userId = user.id;
+ try {
+ Slog.i(TAG, "Updating overlays for onUserCreated " + userId);
+ traceBegin(TRACE_TAG_RRO, "OMS#onUserCreated " + userId);
+ synchronized (mLock) {
+ updatePackageManagerLocked(mImpl.updateOverlaysForUser(userId));
+ }
+ } finally {
+ traceEnd(TRACE_TAG_RRO);
+ }
+ }
+ }
+ }
+
private final class UserReceiver extends BroadcastReceiver {
@Override
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
switch (intent.getAction()) {
case ACTION_USER_ADDED:
- if (userId != UserHandle.USER_NULL) {
+ UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umi.getUserInfo(userId);
+ if (userId != UserHandle.USER_NULL && !isHighPriorityUserCreation(userInfo)) {
try {
+ Slog.i(TAG, "Updating overlays for added user " + userId);
traceBegin(TRACE_TAG_RRO, "OMS ACTION_USER_ADDED");
synchronized (mLock) {
updatePackageManagerLocked(mImpl.updateOverlaysForUser(userId));
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2f37e88..4147249 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2738,6 +2738,10 @@
if (!mVisibleBackgroundUserEnabled) {
return true;
}
+ if (isPrivate()) {
+ // UserManager doesn't track the user visibility for private displays.
+ return true;
+ }
final int userId = UserHandle.getUserId(uid);
return userId == UserHandle.USER_SYSTEM
|| mWmService.mUmInternal.isUserVisible(userId, mDisplayId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index eeedec3..19053f7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -306,6 +306,18 @@
}
/**
+ * An interface to be notified on window removal.
+ */
+ public interface OnWindowRemovedListener {
+ /**
+ * Called when a window is removed.
+ *
+ * @param token the client token
+ */
+ void onWindowRemoved(IBinder token);
+ }
+
+ /**
* An interface to be notified when keyguard exit animation should start.
*/
public interface KeyguardExitAnimationStartListener {
@@ -1076,6 +1088,20 @@
public abstract void clearBlockedApps();
/**
+ * Register a listener to receive a callback on window removal.
+ *
+ * @param listener the listener to be registered.
+ */
+ public abstract void registerOnWindowRemovedListener(OnWindowRemovedListener listener);
+
+ /**
+ * Removes the listener.
+ *
+ * @param listener the listener to be removed.
+ */
+ public abstract void unregisterOnWindowRemovedListener(OnWindowRemovedListener listener);
+
+ /**
* Moves the current focus to the adjacent activity if it has the latest created window.
*/
public abstract boolean moveFocusToAdjacentEmbeddedActivityIfNeeded();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index feede01..5d4c201 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -53,6 +53,7 @@
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -147,6 +148,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_VERBOSE_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener;
import static com.android.server.wm.WindowManagerServiceDumpProto.BACK_NAVIGATION;
import static com.android.server.wm.WindowManagerServiceDumpProto.DISPLAY_FROZEN;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
@@ -489,6 +491,9 @@
private final RemoteCallbackList<IKeyguardLockedStateListener> mKeyguardLockedStateListeners =
new RemoteCallbackList<>();
+
+ private final List<OnWindowRemovedListener> mOnWindowRemovedListeners = new ArrayList<>();
+
private boolean mDispatchedKeyguardLockedState = false;
// VR Vr2d Display Id.
@@ -2073,7 +2078,11 @@
*/
void postWindowRemoveCleanupLocked(WindowState win) {
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "postWindowRemoveCleanupLocked: %s", win);
- mWindowMap.remove(win.mClient.asBinder());
+ final IBinder client = win.mClient.asBinder();
+ mWindowMap.remove(client);
+ if (sensitiveContentAppProtection()) {
+ notifyWindowRemovedListeners(client);
+ }
final DisplayContent dc = win.getDisplayContent();
dc.getDisplayRotation().markForSeamlessRotation(win, false /* seamlesslyRotated */);
@@ -5335,6 +5344,23 @@
}
}
+ private void notifyWindowRemovedListeners(IBinder client) {
+ OnWindowRemovedListener[] windowRemovedListeners;
+ synchronized (mGlobalLock) {
+ if (mOnWindowRemovedListeners.isEmpty()) {
+ return;
+ }
+ windowRemovedListeners = new OnWindowRemovedListener[mOnWindowRemovedListeners.size()];
+ mOnWindowRemovedListeners.toArray(windowRemovedListeners);
+ }
+ mH.post(() -> {
+ int size = windowRemovedListeners.length;
+ for (int i = 0; i < size; i++) {
+ windowRemovedListeners[i].onWindowRemoved(client);
+ }
+ });
+ }
+
private void notifyWindowsChanged() {
WindowChangeListener[] windowChangeListeners;
synchronized (mGlobalLock) {
@@ -8868,6 +8894,20 @@
}
@Override
+ public void registerOnWindowRemovedListener(OnWindowRemovedListener listener) {
+ synchronized (mGlobalLock) {
+ mOnWindowRemovedListeners.add(listener);
+ }
+ }
+
+ @Override
+ public void unregisterOnWindowRemovedListener(OnWindowRemovedListener listener) {
+ synchronized (mGlobalLock) {
+ mOnWindowRemovedListeners.remove(listener);
+ }
+ }
+
+ @Override
public boolean moveFocusToAdjacentEmbeddedActivityIfNeeded() {
synchronized (mGlobalLock) {
final WindowState focusedWindow = getFocusedWindow();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 85ab562..f553a5a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -455,10 +455,10 @@
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection;
-import android.security.KeyStore;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keystore.AttestationUtils;
import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.telecom.TelecomManager;
@@ -6248,7 +6248,7 @@
try (KeyChainConnection keyChainConnection =
KeyChain.bindAsUser(mContext, caller.getUserHandle())) {
IKeyChainService keyChain = keyChainConnection.getService();
- if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyStore.UID_SELF)) {
+ if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyProperties.UID_SELF)) {
logInstallKeyPairFailure(caller, isCredentialManagementApp);
return false;
}
@@ -6583,7 +6583,7 @@
}
// As the caller will be granted access to the key, ensure no UID was specified, as
// it will not have the desired effect.
- if (keySpec.getUid() != KeyStore.UID_SELF) {
+ if (keySpec.getUid() != KeyProperties.UID_SELF) {
Slogf.e(LOG_TAG, "Only the caller can be granted access to the generated keypair.");
logGenerateKeyPairFailure(caller, isCredentialManagementApp);
return false;
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
index 7aafa8e..5ddd8a5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
@@ -26,7 +26,6 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.pm.PackageManagerInternal;
import android.media.projection.MediaProjectionInfo;
@@ -108,7 +107,7 @@
mMediaPorjectionCallback.onStart(exemptedRecorderPackage);
mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -135,7 +134,7 @@
// when screen sharing is not active, no app window should be blocked.
mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -158,8 +157,7 @@
mMediaPorjectionCallback.onStart(mMediaProjectionInfo);
mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
- verify(mWindowManager, never())
- .addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -168,7 +166,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(mMediaProjectionInfo);
mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
private void mockDisabledViaDeveloperOption() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
index a20d935..8b65337 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
@@ -30,7 +30,6 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.pm.PackageManagerInternal;
@@ -102,6 +101,8 @@
@Captor
ArgumentCaptor<MediaProjectionManager.Callback> mMediaProjectionCallbackCaptor;
+ @Captor
+ private ArgumentCaptor<ArraySet<PackageInfo>> mPackageInfoCaptor;
@Mock
private MediaProjectionManager mProjectionManager;
@@ -309,7 +310,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -469,7 +470,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -480,7 +481,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -495,7 +496,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -519,7 +520,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -530,7 +531,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -541,7 +542,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -557,7 +558,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -574,7 +575,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -586,7 +587,7 @@
mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -598,7 +599,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verifyNoBlockOrClearInteractionWithWindowManager();
}
@Test
@@ -614,7 +615,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verifyNoBlockOrClearInteractionWithWindowManager();
}
@Test
@@ -640,7 +641,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -652,7 +653,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -666,7 +667,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(null);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -684,7 +685,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -702,7 +703,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -715,7 +716,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -727,7 +728,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verifyNoBlockOrClearInteractionWithWindowManager();
}
@Test
@@ -743,7 +744,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verifyNoBlockOrClearInteractionWithWindowManager();
}
@Test
@@ -773,7 +774,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification2, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -787,7 +788,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(null, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -801,7 +802,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, null);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -816,7 +817,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
@Test
@@ -829,7 +830,14 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, mRankingMap);
- verifyZeroInteractions(mWindowManager);
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
+ }
+
+ private void verifyNoBlockOrClearInteractionWithWindowManager() {
+ verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
+ verify(mWindowManager, never()).clearBlockedApps();
+ verify(mWindowManager, never())
+ .removeBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
}
private void mockDisabledViaDevelopOption() {
diff --git a/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java
new file mode 100644
index 0000000..0bca59d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.server.autofill;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+@RunWith(JUnit4.class)
+public class SaveEventLoggerTest {
+
+ @Test
+ public void testTimestampsInitialized() {
+ SaveEventLogger mLogger = spy(SaveEventLogger.forSessionId(1, 1));
+
+ mLogger.maybeSetLatencySaveUiDisplayMillis();
+ mLogger.maybeSetLatencySaveRequestMillis();
+ mLogger.maybeSetLatencySaveFinishMillis();
+
+ ArgumentCaptor<Long> latencySaveUiDisplayMillis = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<Long> latencySaveRequestMillis = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<Long> latencySaveFinishMillis = ArgumentCaptor.forClass(Long.class);
+
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveUiDisplayMillis(latencySaveUiDisplayMillis.capture());
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveRequestMillis(latencySaveRequestMillis.capture());
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveFinishMillis(latencySaveFinishMillis.capture());
+
+ assertThat(latencySaveUiDisplayMillis.getValue())
+ .isNotEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ assertThat(latencySaveRequestMillis.getValue())
+ .isNotEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ assertThat(latencySaveFinishMillis.getValue())
+ .isNotEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ }
+
+ @Test
+ public void testTimestampsNotInitialized() {
+ SaveEventLogger mLogger =
+ spy(SaveEventLogger.forSessionId(1, SaveEventLogger.UNINITIATED_TIMESTAMP));
+
+ mLogger.maybeSetLatencySaveUiDisplayMillis();
+ mLogger.maybeSetLatencySaveRequestMillis();
+ mLogger.maybeSetLatencySaveFinishMillis();
+ ArgumentCaptor<Long> latencySaveUiDisplayMillis = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<Long> latencySaveRequestMillis = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<Long> latencySaveFinishMillis = ArgumentCaptor.forClass(Long.class);
+
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveUiDisplayMillis(latencySaveUiDisplayMillis.capture());
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveRequestMillis(latencySaveRequestMillis.capture());
+ verify(mLogger, times(1))
+ .maybeSetLatencySaveFinishMillis(latencySaveFinishMillis.capture());
+
+ assertThat(latencySaveUiDisplayMillis.getValue())
+ .isEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ assertThat(latencySaveRequestMillis.getValue())
+ .isEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ assertThat(latencySaveFinishMillis.getValue())
+ .isEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 1194973..c7c97e4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -34,6 +34,7 @@
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
@@ -54,6 +55,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.StatusBarNotification;
@@ -271,7 +273,8 @@
}
@Test
- public void testAddSummary() {
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary_alwaysAutogroup() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
mGroupHelper.onNotificationPosted(
@@ -279,13 +282,52 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ assertThat(mGroupHelper.onNotificationPosted(
+ getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false)).isFalse();
+ }
+ assertThat(mGroupHelper.onNotificationPosted(
+ getSbn(pkg, AUTOGROUP_AT_COUNT - 1, String.valueOf(AUTOGROUP_AT_COUNT - 1),
+ UserHandle.SYSTEM), false)).isTrue();
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary_oneChildOngoing_summaryOngoing_alwaysAutogroup() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ if (i == 0) {
+ sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ }
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_oneChildOngoing_summaryOngoing() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -297,13 +339,33 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel_alwaysAutogroup() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ if (i == 0) {
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ }
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -315,13 +377,31 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel_alwaysAutogroup() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -331,13 +411,34 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddSummary_summaryAutoCancelNoClear_alwaysAutogroup() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ if (i == 0) {
+ sbn.getNotification().flags |= FLAG_NO_CLEAR;
+ }
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_summaryAutoCancelNoClear() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -350,7 +451,7 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
@@ -617,7 +718,7 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
Mockito.reset(mCallback);
@@ -645,7 +746,7 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
Mockito.reset(mCallback);
@@ -664,7 +765,8 @@
}
@Test
- public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_alwaysGroup() {
final String pkg = "package";
List<StatusBarNotification> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -674,7 +776,7 @@
}
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
Mockito.reset(mCallback);
@@ -693,8 +795,8 @@
// < AUTOGROUP_AT_COUNT
final StatusBarNotification sbn = getSbn(pkg, 5, String.valueOf(5), UserHandle.SYSTEM);
posted.add(sbn);
- mGroupHelper.onNotificationPosted(sbn, true);
- verify(mCallback, times(1)).addAutoGroup(sbn.getKey());
+ assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isFalse();
+ verify(mCallback, times(1)).addAutoGroup(sbn.getKey(), true);
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
@@ -703,7 +805,84 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
+ final String pkg = "package";
+ List<StatusBarNotification> posted = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ posted.add(sbn);
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ Mockito.reset(mCallback);
+
+ for (int i = posted.size() - 2; i >= 0; i--) {
+ mGroupHelper.onNotificationRemoved(posted.remove(i));
+ }
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ Mockito.reset(mCallback);
+
+ // only one child remains
+ assertEquals(1, mGroupHelper.getNotGroupedByAppCount(UserHandle.USER_SYSTEM, pkg));
+
+ // Add new notification; it should be autogrouped even though the total count is
+ // < AUTOGROUP_AT_COUNT
+ final StatusBarNotification sbn = getSbn(pkg, 5, String.valueOf(5), UserHandle.SYSTEM);
+ posted.add(sbn);
+ assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isTrue();
+ // addAutoGroup not called on sbn, because the autogrouping is expected to be done
+ // synchronously.
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(), any());
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
@EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAddSummary_sameIcon_sameColor_alwaysAutogroup() {
+ final String pkg = "package";
+ final Icon icon = mock(Icon.class);
+ when(icon.sameAs(icon)).thenReturn(true);
+ final int iconColor = Color.BLUE;
+ final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
+ DEFAULT_VISIBILITY);
+
+ // Add notifications with same icon and color
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ icon, iconColor);
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ // Check that the summary would have the same icon and color
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(attr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+ // After auto-grouping, add new notification with the same color
+ StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
+ mGroupHelper.onNotificationPosted(sbn, true);
+
+ // Check that the summary was updated
+ //NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(attr));
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
public void testAddSummary_sameIcon_sameColor() {
final String pkg = "package";
final Icon icon = mock(Icon.class);
@@ -721,7 +900,7 @@
// Check that the summary would have the same icon and color
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(attr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -761,7 +940,7 @@
// Check that the summary would have the same icon and color
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(initialAttr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -780,8 +959,9 @@
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
@EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
- public void testAddSummary_diffVisibility() {
+ public void testAddSummary_diffVisibility_alwaysAutogroup() {
final String pkg = "package";
final Icon icon = mock(Icon.class);
when(icon.sameAs(icon)).thenReturn(true);
@@ -798,7 +978,8 @@
// Check that the summary has private visibility
verify(mCallback, times(1)).addAutoGroupSummary(
anyInt(), eq(pkg), anyString(), eq(attr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -815,6 +996,48 @@
}
@Test
+ @EnableFlags({Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testAddSummary_diffVisibility() {
+ final String pkg = "package";
+ final Icon icon = mock(Icon.class);
+ when(icon.sameAs(icon)).thenReturn(true);
+ final int iconColor = Color.BLUE;
+ final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
+ VISIBILITY_PRIVATE);
+
+ // Add notifications with same icon and color and default visibility (private)
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ icon, iconColor);
+ assertThat(mGroupHelper.onNotificationPosted(sbn, false)).isFalse();
+ }
+ // The last notification added will reach the autogroup threshold.
+ StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT - 1,
+ String.valueOf(AUTOGROUP_AT_COUNT - 1), UserHandle.SYSTEM, null, icon, iconColor);
+ assertThat(mGroupHelper.onNotificationPosted(sbn, false)).isTrue();
+
+ // Check that the summary has private visibility
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(attr));
+ // The last sbn is expected to be added to autogroup synchronously.
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+ // After auto-grouping, add new notification with public visibility
+ sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
+ sbn.getNotification().visibility = VISIBILITY_PUBLIC;
+ assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isTrue();
+
+ // Check that the summary visibility was updated
+ NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
+ VISIBILITY_PUBLIC);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr));
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
public void testAutoGrouped_diffIcon_diffColor_removeChild_updateTo_sameIcon_sameColor() {
final String pkg = "package";
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3a0eba1..b366f92 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -311,6 +311,7 @@
import com.google.android.collect.Lists;
import com.google.common.collect.ImmutableList;
+
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -331,8 +332,6 @@
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -348,6 +347,9 @@
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
@@ -5542,7 +5544,7 @@
public void testAddAutogroup_requestsSort() throws Exception {
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.addAutogroupKeyLocked(r.getKey());
+ mService.addAutogroupKeyLocked(r.getKey(), true);
verify(mRankingHandler, times(1)).requestSort();
}
@@ -5562,12 +5564,30 @@
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
r.setOverrideGroupKey("TEST");
mService.addNotification(r);
- mService.addAutogroupKeyLocked(r.getKey());
+ mService.addAutogroupKeyLocked(r.getKey(), true);
verify(mRankingHandler, never()).requestSort();
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAutogroupSuppressSort_noSort() throws Exception {
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ mService.addNotification(r);
+ mService.addAutogroupKeyLocked(r.getKey(), false);
+
+ verify(mRankingHandler, never()).requestSort();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAutogroupOnPost_skipManualSort() throws Exception {
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ mService.addNotification(r);
+ verify(mRankingHandler, never()).requestSort();
+ }
+
+ @Test
public void testHandleRankingSort_sendsUpdateOnSignalExtractorChange() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
NotificationManagerService.WorkerHandler handler = mock(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 27d9d13..44d1b54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -114,6 +114,8 @@
import android.os.Binder;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -2813,6 +2815,41 @@
mDisplayContent.getKeepClearAreas());
}
+ @Test
+ public void testHasAccessConsidersUserVisibilityForBackgroundVisibleUsers() {
+ doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
+ final int appId = 1234;
+ final int userId1 = 11;
+ final int userId2 = 12;
+ final int uid1 = UserHandle.getUid(userId1, appId);
+ final int uid2 = UserHandle.getUid(userId2, appId);
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ final DisplayContent dc = createNewDisplay(displayInfo);
+ int displayId = dc.getDisplayId();
+ doReturn(true).when(mWm.mUmInternal).isUserVisible(userId1, displayId);
+ doReturn(false).when(mWm.mUmInternal).isUserVisible(userId2, displayId);
+
+ assertTrue(dc.hasAccess(uid1));
+ assertFalse(dc.hasAccess(uid2));
+ }
+
+ @Test
+ public void testHasAccessIgnoresUserVisibilityForPrivateDisplay() {
+ doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
+ final int appId = 1234;
+ final int userId2 = 12;
+ final int uid2 = UserHandle.getUid(userId2, appId);
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.flags = FLAG_PRIVATE;
+ displayInfo.ownerUid = uid2;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+ int displayId = dc.getDisplayId();
+
+ assertTrue(dc.hasAccess(uid2));
+
+ verify(mWm.mUmInternal, never()).isUserVisible(userId2, displayId);
+ }
+
private void removeRootTaskTests(Runnable runnable) {
final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 25e2d82..03ba8fa 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13787,11 +13787,11 @@
* <p>This method returns valid data on devices with {@link
* android.content.pm.PackageManager#FEATURE_TELEPHONY_CARRIERLOCK} enabled.
*
- * @deprecated Apps should use {@link getCarriersRestrictionRules} to retrieve the list of
+ * @deprecated Apps should use {@link #getCarrierRestrictionRules} to retrieve the list of
* allowed and excliuded carriers, as the result of this API is valid only when the excluded
* list is empty. This API could return an empty list, even if some restrictions are present.
*
- * @return List of {@link android.telephony.CarrierIdentifier}; empty list
+ * @return List of {@link android.service.carrier.CarrierIdentifier}; empty list
* means all carriers are allowed.
*
* @throws UnsupportedOperationException If the device does not have